-module(etui@widgets@scene). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/etui/widgets/scene.gleam"). -export([scene_new/1, with_bg/2, render/4]). -export_type([scene_fill/0, shape/0, scene/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 scene_fill() :: {scene_solid, etui@style:color()} | {scene_gradient, list(etui@style:color())} | scene_rainbow | scene_animated_rainbow. -type shape() :: {circle_outline, integer(), integer(), integer(), scene_fill()} | {disc, integer(), integer(), integer(), scene_fill()} | {planet, integer(), integer(), integer(), integer(), scene_fill(), integer()} | {mandelbrot, integer()}. -type scene() :: {scene, list(shape()), etui@style:color()}. -file("src/etui/widgets/scene.gleam", 47). -spec scene_new(list(shape())) -> scene(). scene_new(Shapes) -> {scene, Shapes, default}. -file("src/etui/widgets/scene.gleam", 51). -spec with_bg(scene(), etui@style:color()) -> scene(). with_bg(S, Bg) -> {scene, erlang:element(2, S), Bg}. -file("src/etui/widgets/scene.gleam", 372). -spec mandelbrot_iter( integer(), integer(), integer(), integer(), integer(), integer() ) -> integer(). mandelbrot_iter(Cr, Ci, Zr, Zi, Iter, Max_iter) -> case Iter >= Max_iter of true -> Iter; false -> Zr2 = (Zr * Zr) div 1024, Zi2 = (Zi * Zi) div 1024, case (Zr2 + Zi2) > 4096 of true -> Iter; false -> mandelbrot_iter( Cr, Ci, (Zr2 - Zi2) + Cr, (((2 * Zr) * Zi) div 1024) + Ci, Iter + 1, Max_iter ) end end. -file("src/etui/widgets/scene.gleam", 344). -spec mandelbrot_cols( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). mandelbrot_cols(Pixels, Pw, Pw1, Max_iter, By, Ci, Bx) -> case Bx >= Pw of true -> Pixels; false -> Cr = -2560 + (case Pw1 of 0 -> 0; Gleam@denominator -> Bx * 3584 div Gleam@denominator end), Iter = mandelbrot_iter(Cr, Ci, 0, 0, 0, Max_iter), Pixels@1 = case Iter >= Max_iter of true -> Pixels; false -> Hue = case Max_iter of 0 -> 0; Gleam@denominator@1 -> Iter * 300 div Gleam@denominator@1 end, Fg = etui@color:hue_to_rgb(Hue), etui@braille:put(Pixels, Bx, By, Fg) end, mandelbrot_cols(Pixels@1, Pw, Pw1, Max_iter, By, Ci, Bx + 1) end. -file("src/etui/widgets/scene.gleam", 324). -spec mandelbrot_rows( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). mandelbrot_rows(Pixels, Pw, Ph, Pw1, Ph1, Max_iter, By) -> case By >= Ph of true -> Pixels; false -> Ci = 1280 - (case Ph1 of 0 -> 0; Gleam@denominator -> By * 2560 div Gleam@denominator end), Pixels@1 = mandelbrot_cols(Pixels, Pw, Pw1, Max_iter, By, Ci, 0), mandelbrot_rows(Pixels@1, Pw, Ph, Pw1, Ph1, Max_iter, By + 1) end. -file("src/etui/widgets/scene.gleam", 310). -spec draw_mandelbrot( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). draw_mandelbrot(Pixels, Pw, Ph, Max_iter) -> Pw1 = gleam@int:max(1, Pw - 1), Ph1 = gleam@int:max(1, Ph - 1), mandelbrot_rows(Pixels, Pw, Ph, Pw1, Ph1, Max_iter, 0). -file("src/etui/widgets/scene.gleam", 405). -spec scene_color(scene_fill(), integer(), integer(), integer(), integer()) -> etui@style:color(). scene_color(Fill, Px, Pw, Frame, Period) -> P = gleam@int:max(1, Period), W = gleam@int:max(1, Pw), case Fill of {scene_solid, C} -> C; {scene_gradient, Stops} -> etui@color:gradient(Stops, Px, W - 1); scene_rainbow -> etui@color:hue_to_rgb(case W of 0 -> 0; Gleam@denominator -> Px * 360 div Gleam@denominator end); scene_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/scene.gleam", 203). -spec disc_dy_loop( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), scene_fill(), integer(), integer(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). disc_dy_loop(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, Dx, Dy) -> case Dy > R of true -> Pixels; false -> Pixels@1 = case ((Dx * Dx) + (Dy * Dy)) =< (R * R) of false -> Pixels; true -> Bx = Cx + Dx, By = Cy + Dy, case (((Bx >= 0) andalso (By >= 0)) andalso (Bx < Pw)) andalso (By < Ph) of false -> Pixels; true -> Fg = scene_color(Fill, Bx, Pw, Frame, Period), etui@braille:put(Pixels, Bx, By, Fg) end end, disc_dy_loop( Pixels@1, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, Dx, Dy + 1 ) end. -file("src/etui/widgets/scene.gleam", 181). -spec disc_dx_loop( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), scene_fill(), integer(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). disc_dx_loop(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, Dx) -> case Dx > R of true -> Pixels; false -> Pixels@1 = disc_dy_loop( Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, Dx, 0 - R ), disc_dx_loop( Pixels@1, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, Dx + 1 ) end. -file("src/etui/widgets/scene.gleam", 167). -spec draw_disc( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), scene_fill(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). draw_disc(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period) -> disc_dx_loop(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period, 0 - R). -file("src/etui/widgets/scene.gleam", 270). -spec cos_sin_256(integer()) -> {integer(), integer()}. cos_sin_256(Step) -> case Step rem 32 of 0 -> {256, 0}; 1 -> {251, 50}; 2 -> {236, 98}; 3 -> {213, 142}; 4 -> {181, 181}; 5 -> {142, 213}; 6 -> {98, 236}; 7 -> {50, 251}; 8 -> {0, 256}; 9 -> {-50, 251}; 10 -> {-98, 236}; 11 -> {-142, 213}; 12 -> {-181, 181}; 13 -> {-213, 142}; 14 -> {-236, 98}; 15 -> {-251, 50}; 16 -> {-256, 0}; 17 -> {-251, -50}; 18 -> {-236, -98}; 19 -> {-213, -142}; 20 -> {-181, -181}; 21 -> {-142, -213}; 22 -> {-98, -236}; 23 -> {-50, -251}; 24 -> {0, -256}; 25 -> {50, -251}; 26 -> {98, -236}; 27 -> {142, -213}; 28 -> {181, -181}; 29 -> {213, -142}; 30 -> {236, -98}; _ -> {251, -50} end. -file("src/etui/widgets/scene.gleam", 258). ?DOC(" 32-step integer orbit using precomputed cos/sin × 256.\n"). -spec orbit_position(integer(), integer(), integer(), integer(), integer()) -> {integer(), integer()}. orbit_position(Cx, Cy, R, Frame, Period) -> Step = (case gleam@int:max(1, Period) of 0 -> 0; Gleam@denominator -> Frame * 32 div Gleam@denominator end) rem 32, {C256, S256} = cos_sin_256(Step), {Cx + ((R * C256) div 256), Cy + ((R * S256) div 256)}. -file("src/etui/widgets/scene.gleam", 241). -spec draw_planet( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), integer(), scene_fill(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). draw_planet(Pixels, Cx, Cy, Orbit_r, Dot_r, Fill, Pw, Ph, Frame, Period) -> {Px, Py} = orbit_position(Cx, Cy, Orbit_r, Frame, Period), draw_disc(Pixels, Px, Py, Dot_r, Fill, Pw, Ph, Frame, Period). -file("src/etui/widgets/scene.gleam", 132). -spec midpoint_loop( integer(), integer(), integer(), integer(), integer(), list({integer(), integer()}) ) -> list({integer(), integer()}). midpoint_loop(Cx, Cy, Y, X, D, Acc) -> case Y > X of true -> Acc; false -> Pts = [{Cx + X, Cy + Y}, {Cx - X, Cy + Y}, {Cx + X, Cy - Y}, {Cx - X, Cy - Y}, {Cx + Y, Cy + X}, {Cx - Y, Cy + X}, {Cx + Y, Cy - X}, {Cx - Y, Cy - X}], Acc@1 = lists:append(Acc, Pts), Y@1 = Y + 1, {X@1, D@1} = case D < 0 of true -> {X, (D + (2 * Y@1)) + 1}; false -> {X - 1, (D + (2 * (Y@1 - X))) + 1} end, midpoint_loop(Cx, Cy, Y@1, X@1, D@1, Acc@1) end. -file("src/etui/widgets/scene.gleam", 125). -spec circle_outline_pts(integer(), integer(), integer()) -> list({integer(), integer()}). circle_outline_pts(Cx, Cy, R) -> case R =< 0 of true -> [{Cx, Cy}]; false -> midpoint_loop(Cx, Cy, 0, R, 1 - R, []) end. -file("src/etui/widgets/scene.gleam", 100). -spec draw_circle_outline( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), integer(), integer(), integer(), scene_fill(), integer(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). draw_circle_outline(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, Period) -> Pts = circle_outline_pts(Cx, Cy, R), N = erlang:length(Pts), gleam@list:index_fold( Pts, Pixels, fun(Px_dict, Pt, I) -> {Bx, By} = Pt, case (((Bx >= 0) andalso (By >= 0)) andalso (Bx < Pw)) andalso (By < Ph) of false -> Px_dict; true -> Fg = scene_color(Fill, I, N, Frame, Period), etui@braille:put(Px_dict, Bx, By, Fg) end end ). -file("src/etui/widgets/scene.gleam", 79). -spec render_shape( gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}), shape(), integer(), integer(), integer() ) -> gleam@dict:dict({integer(), integer()}, {integer(), etui@style:color()}). render_shape(Pixels, Shape, Pw, Ph, Frame) -> case Shape of {circle_outline, Cx, Cy, R, Fill} -> draw_circle_outline(Pixels, Cx, Cy, R, Fill, Pw, Ph, Frame, 60); {disc, Cx@1, Cy@1, R@1, Fill@1} -> draw_disc(Pixels, Cx@1, Cy@1, R@1, Fill@1, Pw, Ph, Frame, 60); {planet, Cx@2, Cy@2, Orbit_r, Dot_r, Fill@2, Period} -> draw_planet( Pixels, Cx@2, Cy@2, Orbit_r, Dot_r, Fill@2, Pw, Ph, Frame, Period ); {mandelbrot, Max_iter} -> draw_mandelbrot(Pixels, Pw, Ph, Max_iter) end. -file("src/etui/widgets/scene.gleam", 59). ?DOC(" Render scene into `area`. `frame` drives animated shapes.\n"). -spec render(etui@buffer:buffer(), etui@geometry:rect(), scene(), integer()) -> etui@buffer:buffer(). render(Buf, Area, Scene, 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, Pixels = gleam@list:fold( erlang:element(2, Scene), etui@braille:new(), fun(Px_dict, Shape) -> render_shape(Px_dict, Shape, Pw, Ph, Frame) end ), etui@braille:flush(Buf, Area, Pixels, erlang:element(3, Scene)) end.