-module(etui@widgets@canvas).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/widgets/canvas.gleam").
-export([canvas_new/1, series_new/1, with_series_fill/2, with_max/2, with_bg/2, with_period/2, render/4]).
-export_type([series_fill/0, series/0, canvas/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type series_fill() :: {series_solid, etui@style:color()} |
{series_gradient, list(etui@style:color())} |
series_rainbow |
series_animated_rainbow.
-type series() :: {series, list(integer()), series_fill()}.
-type canvas() :: {canvas,
list(series()),
integer(),
etui@style:color(),
integer()}.
-file("src/etui/widgets/canvas.gleam", 45).
-spec canvas_new(list(series())) -> canvas().
canvas_new(Series) ->
{canvas, Series, 0, default, 60}.
-file("src/etui/widgets/canvas.gleam", 49).
-spec series_new(list(integer())) -> series().
series_new(Data) ->
{series, Data, series_rainbow}.
-file("src/etui/widgets/canvas.gleam", 53).
-spec with_series_fill(series(), series_fill()) -> series().
with_series_fill(S, Fill) ->
{series, erlang:element(2, S), Fill}.
-file("src/etui/widgets/canvas.gleam", 57).
-spec with_max(canvas(), integer()) -> canvas().
with_max(C, Max) ->
{canvas,
erlang:element(2, C),
gleam@int:max(1, Max),
erlang:element(4, C),
erlang:element(5, C)}.
-file("src/etui/widgets/canvas.gleam", 61).
-spec with_bg(canvas(), etui@style:color()) -> canvas().
with_bg(C, Bg) ->
{canvas,
erlang:element(2, C),
erlang:element(3, C),
Bg,
erlang:element(5, C)}.
-file("src/etui/widgets/canvas.gleam", 65).
-spec with_period(canvas(), integer()) -> canvas().
with_period(C, Period) ->
{canvas,
erlang:element(2, C),
erlang:element(3, C),
erlang:element(4, C),
gleam@int:max(1, Period)}.
-file("src/etui/widgets/canvas.gleam", 219).
-spec series_color(series_fill(), integer(), integer(), integer(), integer()) -> etui@style:color().
series_color(Fill, Px, Pw, Frame, Period) ->
P = gleam@int:max(1, Period),
W = gleam@int:max(1, Pw),
case Fill of
{series_solid, C} ->
C;
{series_gradient, Stops} ->
etui@color:gradient(Stops, Px, W - 1);
series_rainbow ->
etui@color:hue_to_rgb(case W of
0 -> 0;
Gleam@denominator -> Px * 360 div Gleam@denominator
end);
series_animated_rainbow ->
etui@color:hue_to_rgb(((case W of
0 -> 0;
Gleam@denominator@1 -> Px * 360 div Gleam@denominator@1
end) + (case P of
0 -> 0;
Gleam@denominator@2 -> Frame * 360 div Gleam@denominator@2
end)) rem 360)
end.
-file("src/etui/widgets/canvas.gleam", 186).
-spec bresenham_loop(
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
integer(),
list({integer(), integer()})
) -> list({integer(), integer()}).
bresenham_loop(X0, Y0, X1, Y1, Sx, Sy, Dx, Dy, Err, Acc) ->
Acc@1 = [{X0, Y0} | Acc],
case (X0 =:= X1) andalso (Y0 =:= Y1) of
true ->
Acc@1;
false ->
E2 = 2 * Err,
{Err@1, X0@1} = case E2 >= Dy of
true ->
{Err + Dy, X0 + Sx};
false ->
{Err, X0}
end,
{Err@2, Y0@1} = case E2 =< Dx of
true ->
{Err@1 + Dx, Y0 + Sy};
false ->
{Err@1, Y0}
end,
bresenham_loop(X0@1, Y0@1, X1, Y1, Sx, Sy, Dx, Dy, Err@2, Acc@1)
end.
-file("src/etui/widgets/canvas.gleam", 172).
-spec bresenham(integer(), integer(), integer(), integer()) -> list({integer(),
integer()}).
bresenham(X0, Y0, X1, Y1) ->
Dx = gleam@int:absolute_value(X1 - X0),
Dy = 0 - gleam@int:absolute_value(Y1 - Y0),
Sx = case X0 < X1 of
true ->
1;
false ->
-1
end,
Sy = case Y0 < Y1 of
true ->
1;
false ->
-1
end,
bresenham_loop(X0, Y0, X1, Y1, Sx, Sy, Dx, Dy, Dx + Dy, []).
-file("src/etui/widgets/canvas.gleam", 143).
-spec draw_segments(
gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}),
series(),
list({integer(), integer()}),
integer(),
integer(),
integer()
) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}).
draw_segments(Pixels, Ser, Coords, Pw, Frame, Period) ->
case Coords of
[] ->
Pixels;
[_] ->
Pixels;
[P0, P1 | Rest] ->
{X0, Y0} = P0,
{X1, Y1} = P1,
Pts = bresenham(X0, Y0, X1, Y1),
Pixels@1 = gleam@list:fold(
Pts,
Pixels,
fun(Px_dict, Pt) ->
{Px, Py} = Pt,
Fg = series_color(
erlang:element(3, Ser),
Px,
Pw,
Frame,
Period
),
etui@braille:put(Px_dict, Px, Py, Fg)
end
),
draw_segments(Pixels@1, Ser, [P1 | Rest], Pw, Frame, Period)
end.
-file("src/etui/widgets/canvas.gleam", 122).
-spec data_to_coords(
list(integer()),
integer(),
integer(),
integer(),
integer()
) -> list({integer(), integer()}).
data_to_coords(Data, N, Pw, Ph, Max) ->
Range = gleam@int:max(1, Max),
Max_px = gleam@int:max(0, Pw - 1),
Max_py = gleam@int:max(0, Ph - 1),
gleam@list:index_map(
Data,
fun(Val, I) ->
Px = case N =< 1 of
true ->
0;
false ->
case (N - 1) of
0 -> 0;
Gleam@denominator -> I * Max_px div Gleam@denominator
end
end,
Clamped = gleam@int:clamp(Val, 0, Range),
Py = Max_py - (case Range of
0 -> 0;
Gleam@denominator@1 -> Clamped * Max_py div Gleam@denominator@1
end),
{Px, Py}
end
).
-file("src/etui/widgets/canvas.gleam", 103).
-spec draw_series(
gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}),
series(),
integer(),
integer(),
integer(),
integer(),
integer()
) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}).
draw_series(Pixels, Ser, Pw, Ph, Max, Frame, Period) ->
N = erlang:length(erlang:element(2, Ser)),
case N of
0 ->
Pixels;
_ ->
Coords = data_to_coords(erlang:element(2, Ser), N, Pw, Ph, Max),
draw_segments(Pixels, Ser, Coords, Pw, Frame, Period)
end.
-file("src/etui/widgets/canvas.gleam", 73).
?DOC(" Render canvas into `area`. `frame` drives animated fills.\n").
-spec render(etui@buffer:buffer(), etui@geometry:rect(), canvas(), integer()) -> etui@buffer:buffer().
render(Buf, Area, C, Frame) ->
case (erlang:element(2, erlang:element(3, Area)) =< 0) orelse (erlang:element(
3,
erlang:element(3, Area)
)
=< 0) of
true ->
Buf;
false ->
Pw = erlang:element(2, erlang:element(3, Area)) * 2,
Ph = erlang:element(3, erlang:element(3, Area)) * 4,
Max = case erlang:element(3, C) of
0 ->
gleam@list:fold(
erlang:element(2, C),
1,
fun(Acc, Ser) ->
gleam@list:fold(
erlang:element(2, Ser),
Acc,
fun gleam@int:max/2
)
end
);
M ->
M
end,
Pixels = gleam@list:index_fold(
erlang:element(2, C),
etui@braille:new(),
fun(Px_dict, Ser@1, _) ->
draw_series(
Px_dict,
Ser@1,
Pw,
Ph,
Max,
Frame,
erlang:element(5, C)
)
end
),
etui@braille:flush(Buf, Area, Pixels, erlang:element(4, C))
end.