Skip to main content

src/etui@widgets@scroll_view.erl

-module(etui@widgets@scroll_view).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/widgets/scroll_view.gleam").
-export([scroll_view_new/2, sv_state_new/0, scroll_to/3, scroll_down/2, scroll_up/2, scroll_right/2, scroll_left/2, clamp/4, render/5, scroll_pct_y/3, scroll_pct_x/3]).
-export_type([scroll_view/0, scroll_view_state/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 scroll_view() :: {scroll_view, integer(), integer()}.

-type scroll_view_state() :: {scroll_view_state, integer(), integer()}.

-file("src/etui/widgets/scroll_view.gleam", 39).
-spec scroll_view_new(integer(), integer()) -> scroll_view().
scroll_view_new(Virtual_width, Virtual_height) ->
    {scroll_view,
        gleam@int:max(1, Virtual_width),
        gleam@int:max(1, Virtual_height)}.

-file("src/etui/widgets/scroll_view.gleam", 46).
-spec sv_state_new() -> scroll_view_state().
sv_state_new() ->
    {scroll_view_state, 0, 0}.

-file("src/etui/widgets/scroll_view.gleam", 50).
-spec scroll_to(scroll_view_state(), integer(), integer()) -> scroll_view_state().
scroll_to(_, X, Y) ->
    {scroll_view_state, gleam@int:max(0, X), gleam@int:max(0, Y)}.

-file("src/etui/widgets/scroll_view.gleam", 54).
-spec scroll_down(scroll_view_state(), integer()) -> scroll_view_state().
scroll_down(State, Lines) ->
    {scroll_view_state,
        erlang:element(2, State),
        erlang:element(3, State) + gleam@int:max(0, Lines)}.

-file("src/etui/widgets/scroll_view.gleam", 58).
-spec scroll_up(scroll_view_state(), integer()) -> scroll_view_state().
scroll_up(State, Lines) ->
    {scroll_view_state,
        erlang:element(2, State),
        gleam@int:max(0, erlang:element(3, State) - Lines)}.

-file("src/etui/widgets/scroll_view.gleam", 62).
-spec scroll_right(scroll_view_state(), integer()) -> scroll_view_state().
scroll_right(State, Cols) ->
    {scroll_view_state,
        erlang:element(2, State) + gleam@int:max(0, Cols),
        erlang:element(3, State)}.

-file("src/etui/widgets/scroll_view.gleam", 66).
-spec scroll_left(scroll_view_state(), integer()) -> scroll_view_state().
scroll_left(State, Cols) ->
    {scroll_view_state,
        gleam@int:max(0, erlang:element(2, State) - Cols),
        erlang:element(3, State)}.

-file("src/etui/widgets/scroll_view.gleam", 71).
?DOC(" Clamp scroll offsets so the viewport never goes past the virtual canvas.\n").
-spec clamp(scroll_view_state(), scroll_view(), integer(), integer()) -> scroll_view_state().
clamp(State, Sv, Visible_w, Visible_h) ->
    Max_x = gleam@int:max(0, erlang:element(2, Sv) - Visible_w),
    Max_y = gleam@int:max(0, erlang:element(3, Sv) - Visible_h),
    {scroll_view_state,
        gleam@int:min(erlang:element(2, State), Max_x),
        gleam@int:min(erlang:element(3, State), Max_y)}.

-file("src/etui/widgets/scroll_view.gleam", 146).
-spec blit_row(
    etui@buffer:buffer(),
    etui@buffer:buffer(),
    etui@geometry:rect(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer()
) -> etui@buffer:buffer().
blit_row(Buf, Virtual_buf, Area, Ox, Oy, Vis_w, Row, Col) ->
    case Col >= Vis_w of
        true ->
            Buf;

        false ->
            Src_pos = {position, Ox + Col, Oy + Row},
            Dst_pos = {position,
                erlang:element(2, erlang:element(2, Area)) + Col,
                erlang:element(3, erlang:element(2, Area)) + Row},
            Cell = etui@buffer:get_cell(Virtual_buf, Src_pos),
            blit_row(
                etui@buffer:set_cell(Buf, Dst_pos, Cell),
                Virtual_buf,
                Area,
                Ox,
                Oy,
                Vis_w,
                Row,
                Col + 1
            )
    end.

-file("src/etui/widgets/scroll_view.gleam", 120).
-spec blit(
    etui@buffer:buffer(),
    etui@buffer:buffer(),
    etui@geometry:rect(),
    integer(),
    integer(),
    integer(),
    integer(),
    integer()
) -> etui@buffer:buffer().
blit(Buf, Virtual_buf, Area, Ox, Oy, Vis_w, Vis_h, Row) ->
    case Row >= Vis_h of
        true ->
            Buf;

        false ->
            blit(
                blit_row(Buf, Virtual_buf, Area, Ox, Oy, Vis_w, Row, 0),
                Virtual_buf,
                Area,
                Ox,
                Oy,
                Vis_w,
                Vis_h,
                Row + 1
            )
    end.

-file("src/etui/widgets/scroll_view.gleam", 93).
?DOC(
    " Render the scroll view.\n"
    "\n"
    " `render_inner` is called with a virtual buffer sized to\n"
    " `(sv.virtual_width × sv.virtual_height)`. The visible window at\n"
    " `(state.scroll_x, state.scroll_y)` is then blitted into `buf` at `area`.\n"
).
-spec render(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    scroll_view(),
    scroll_view_state(),
    fun((etui@buffer:buffer(), etui@geometry:rect()) -> etui@buffer:buffer())
) -> etui@buffer:buffer().
render(Buf, Area, Sv, State, Render_inner) ->
    case (erlang:element(2, erlang:element(3, Area)) =< 0) orelse (erlang:element(
        3,
        erlang:element(3, Area)
    )
    =< 0) of
        true ->
            Buf;

        false ->
            Virtual_area = etui@geometry:rect_new(
                0,
                0,
                erlang:element(2, Sv),
                erlang:element(3, Sv)
            ),
            Virtual_buf = begin
                _pipe = etui@buffer:buffer_new(Virtual_area),
                Render_inner(_pipe, Virtual_area)
            end,
            Vis_w = erlang:element(2, erlang:element(3, Area)),
            Vis_h = erlang:element(3, erlang:element(3, Area)),
            Ox = gleam@int:max(0, erlang:element(2, State)),
            Oy = gleam@int:max(0, erlang:element(3, State)),
            blit(Buf, Virtual_buf, Area, Ox, Oy, Vis_w, Vis_h, 0)
    end.

-file("src/etui/widgets/scroll_view.gleam", 182).
?DOC(
    " How far into the virtual canvas is the viewport (0.0–1.0 × 100).\n"
    " Returns an integer percentage (0–100). Useful for driving scrollbar widgets.\n"
).
-spec scroll_pct_y(scroll_view_state(), scroll_view(), integer()) -> integer().
scroll_pct_y(State, Sv, Visible_h) ->
    Max_scroll = gleam@int:max(1, erlang:element(3, Sv) - Visible_h),
    gleam@int:min(100, case Max_scroll of
            0 -> 0;
            Gleam@denominator -> erlang:element(3, State) * 100 div Gleam@denominator
        end).

-file("src/etui/widgets/scroll_view.gleam", 191).
-spec scroll_pct_x(scroll_view_state(), scroll_view(), integer()) -> integer().
scroll_pct_x(State, Sv, Visible_w) ->
    Max_scroll = gleam@int:max(1, erlang:element(2, Sv) - Visible_w),
    gleam@int:min(100, case Max_scroll of
            0 -> 0;
            Gleam@denominator -> erlang:element(2, State) * 100 div Gleam@denominator
        end).