Skip to main content

src/etui@widgets@multi_select.erl

-module(etui@widgets@multi_select).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/widgets/multi_select.gleam").
-export([multi_select_new/1, with_max/2, with_marks/3, with_cursor_mark/2, with_cursor_style/2, with_selected_style/2, with_colors/3, state_new/0, select_next/2, select_prev/1, toggle/2, is_selected/2, selected_indices/1, selected_values/2, clear_selection/1, effective_offset/2, render/4]).
-export_type([multi_select_widget/0, multi_select_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 multi_select_widget() :: {multi_select_widget,
        list(binary()),
        etui@style:style(),
        etui@style:style(),
        binary(),
        binary(),
        binary(),
        integer(),
        etui@style:color(),
        etui@style:color()}.

-type multi_select_state() :: {multi_select_state,
        integer(),
        list(integer()),
        integer()}.

-file("src/etui/widgets/multi_select.gleam", 37).
-spec multi_select_new(list(binary())) -> multi_select_widget().
multi_select_new(Items) ->
    {multi_select_widget,
        Items,
        {style, default, default, etui@style:reverse()},
        {style, default, default, etui@style:bold()},
        <<"[x] "/utf8>>,
        <<"[ ] "/utf8>>,
        <<"â–¶ "/utf8>>,
        0,
        default,
        default}.

-file("src/etui/widgets/multi_select.gleam", 60).
?DOC(" Cap the number of selected items. 0 = unlimited.\n").
-spec with_max(multi_select_widget(), integer()) -> multi_select_widget().
with_max(W, M) ->
    {multi_select_widget,
        erlang:element(2, W),
        erlang:element(3, W),
        erlang:element(4, W),
        erlang:element(5, W),
        erlang:element(6, W),
        erlang:element(7, W),
        gleam@int:max(0, M),
        erlang:element(9, W),
        erlang:element(10, W)}.

-file("src/etui/widgets/multi_select.gleam", 64).
-spec with_marks(multi_select_widget(), binary(), binary()) -> multi_select_widget().
with_marks(W, Checked, Unchecked) ->
    {multi_select_widget,
        erlang:element(2, W),
        erlang:element(3, W),
        erlang:element(4, W),
        Checked,
        Unchecked,
        erlang:element(7, W),
        erlang:element(8, W),
        erlang:element(9, W),
        erlang:element(10, W)}.

-file("src/etui/widgets/multi_select.gleam", 72).
-spec with_cursor_mark(multi_select_widget(), binary()) -> multi_select_widget().
with_cursor_mark(W, M) ->
    {multi_select_widget,
        erlang:element(2, W),
        erlang:element(3, W),
        erlang:element(4, W),
        erlang:element(5, W),
        erlang:element(6, W),
        M,
        erlang:element(8, W),
        erlang:element(9, W),
        erlang:element(10, W)}.

-file("src/etui/widgets/multi_select.gleam", 76).
-spec with_cursor_style(multi_select_widget(), etui@style:style()) -> multi_select_widget().
with_cursor_style(W, S) ->
    {multi_select_widget,
        erlang:element(2, W),
        S,
        erlang:element(4, W),
        erlang:element(5, W),
        erlang:element(6, W),
        erlang:element(7, W),
        erlang:element(8, W),
        erlang:element(9, W),
        erlang:element(10, W)}.

-file("src/etui/widgets/multi_select.gleam", 83).
-spec with_selected_style(multi_select_widget(), etui@style:style()) -> multi_select_widget().
with_selected_style(W, S) ->
    {multi_select_widget,
        erlang:element(2, W),
        erlang:element(3, W),
        S,
        erlang:element(5, W),
        erlang:element(6, W),
        erlang:element(7, W),
        erlang:element(8, W),
        erlang:element(9, W),
        erlang:element(10, W)}.

-file("src/etui/widgets/multi_select.gleam", 90).
-spec with_colors(multi_select_widget(), etui@style:color(), etui@style:color()) -> multi_select_widget().
with_colors(W, Fg, Bg) ->
    {multi_select_widget,
        erlang:element(2, W),
        erlang:element(3, W),
        erlang:element(4, W),
        erlang:element(5, W),
        erlang:element(6, W),
        erlang:element(7, W),
        erlang:element(8, W),
        Fg,
        Bg}.

-file("src/etui/widgets/multi_select.gleam", 101).
-spec state_new() -> multi_select_state().
state_new() ->
    {multi_select_state, 0, [], 0}.

-file("src/etui/widgets/multi_select.gleam", 105).
-spec select_next(multi_select_state(), integer()) -> multi_select_state().
select_next(State, Item_count) ->
    Max_idx = gleam@int:max(0, Item_count - 1),
    {multi_select_state,
        gleam@int:min(erlang:element(2, State) + 1, Max_idx),
        erlang:element(3, State),
        erlang:element(4, State)}.

-file("src/etui/widgets/multi_select.gleam", 113).
-spec select_prev(multi_select_state()) -> multi_select_state().
select_prev(State) ->
    {multi_select_state,
        gleam@int:max(erlang:element(2, State) - 1, 0),
        erlang:element(3, State),
        erlang:element(4, State)}.

-file("src/etui/widgets/multi_select.gleam", 263).
-spec insert_sorted(list(integer()), integer()) -> list(integer()).
insert_sorted(Lst, N) ->
    case Lst of
        [] ->
            [N];

        [H | _] when N < H ->
            [N | Lst];

        [H@1 | _] when N =:= H@1 ->
            Lst;

        [H@2 | T] ->
            [H@2 | insert_sorted(T, N)]
    end.

-file("src/etui/widgets/multi_select.gleam", 118).
?DOC(" Toggle the cursor item. Respects `max` from the widget config.\n").
-spec toggle(multi_select_state(), integer()) -> multi_select_state().
toggle(State, Max) ->
    case gleam@list:contains(erlang:element(3, State), erlang:element(2, State)) of
        true ->
            {multi_select_state,
                erlang:element(2, State),
                gleam@list:filter(
                    erlang:element(3, State),
                    fun(I) -> I /= erlang:element(2, State) end
                ),
                erlang:element(4, State)};

        false ->
            case (Max > 0) andalso (erlang:length(erlang:element(3, State)) >= Max) of
                true ->
                    State;

                false ->
                    {multi_select_state,
                        erlang:element(2, State),
                        insert_sorted(
                            erlang:element(3, State),
                            erlang:element(2, State)
                        ),
                        erlang:element(4, State)}
            end
    end.

-file("src/etui/widgets/multi_select.gleam", 137).
-spec is_selected(multi_select_state(), integer()) -> boolean().
is_selected(State, Idx) ->
    gleam@list:contains(erlang:element(3, State), Idx).

-file("src/etui/widgets/multi_select.gleam", 141).
-spec selected_indices(multi_select_state()) -> list(integer()).
selected_indices(State) ->
    erlang:element(3, State).

-file("src/etui/widgets/multi_select.gleam", 146).
?DOC(" Pull the selected item strings in original order.\n").
-spec selected_values(list(binary()), multi_select_state()) -> list(binary()).
selected_values(Items, State) ->
    _pipe = Items,
    _pipe@1 = gleam@list:index_map(_pipe, fun(Item, I) -> {I, Item} end),
    gleam@list:filter_map(
        _pipe@1,
        fun(Pair) ->
            case gleam@list:contains(
                erlang:element(3, State),
                erlang:element(1, Pair)
            ) of
                true ->
                    {ok, erlang:element(2, Pair)};

                false ->
                    {error, nil}
            end
        end
    ).

-file("src/etui/widgets/multi_select.gleam", 160).
-spec clear_selection(multi_select_state()) -> multi_select_state().
clear_selection(State) ->
    {multi_select_state, erlang:element(2, State), [], erlang:element(4, State)}.

-file("src/etui/widgets/multi_select.gleam", 248).
-spec scroll_offset(integer(), integer(), integer()) -> integer().
scroll_offset(Cursor, Offset, Height) ->
    case Cursor < Offset of
        true ->
            Cursor;

        false ->
            case Height =< 0 of
                true ->
                    Offset;

                false ->
                    case Cursor >= (Offset + Height) of
                        true ->
                            (Cursor - Height) + 1;

                        false ->
                            Offset
                    end
            end
    end.

-file("src/etui/widgets/multi_select.gleam", 165).
?DOC(" Effective scroll offset for a viewport of `height` rows.\n").
-spec effective_offset(multi_select_state(), integer()) -> integer().
effective_offset(State, Height) ->
    scroll_offset(erlang:element(2, State), erlang:element(4, State), Height).

-file("src/etui/widgets/multi_select.gleam", 187).
-spec render_rows(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    multi_select_widget(),
    multi_select_state(),
    integer(),
    integer()
) -> etui@buffer:buffer().
render_rows(Buf, Area, W, State, Offset, Row_off) ->
    case Row_off >= erlang:element(3, erlang:element(3, Area)) of
        true ->
            Buf;

        false ->
            Item_idx = Offset + Row_off,
            case gleam@list:drop(erlang:element(2, W), Item_idx) of
                [] ->
                    Buf;

                [Item | _] ->
                    Y = erlang:element(3, erlang:element(2, Area)) + Row_off,
                    Is_cursor = Item_idx =:= erlang:element(2, State),
                    Is_sel = gleam@list:contains(
                        erlang:element(3, State),
                        Item_idx
                    ),
                    Prefix = case Is_cursor of
                        true ->
                            erlang:element(7, W);

                        false ->
                            gleam@string:repeat(
                                <<" "/utf8>>,
                                etui@text:cell_width(erlang:element(7, W))
                            )
                    end,
                    Mark = case Is_sel of
                        true ->
                            erlang:element(5, W);

                        false ->
                            erlang:element(6, W)
                    end,
                    Raw = <<<<Prefix/binary, Mark/binary>>/binary, Item/binary>>,
                    Truncated = etui@text:truncate(
                        Raw,
                        erlang:element(2, erlang:element(3, Area)),
                        <<""/utf8>>
                    ),
                    Padded = etui@text:pad_right(
                        Truncated,
                        erlang:element(2, erlang:element(3, Area))
                    ),
                    {Fg, Bg, Modifier} = case {Is_cursor, Is_sel} of
                        {true, _} ->
                            {erlang:element(2, erlang:element(3, W)),
                                erlang:element(3, erlang:element(3, W)),
                                erlang:element(4, erlang:element(3, W))};

                        {false, true} ->
                            {erlang:element(2, erlang:element(4, W)),
                                erlang:element(3, erlang:element(4, W)),
                                erlang:element(4, erlang:element(4, W))};

                        {false, false} ->
                            {erlang:element(9, W),
                                erlang:element(10, W),
                                etui@style:none()}
                    end,
                    Buf2 = etui@buffer:set_string(
                        Buf,
                        {position,
                            erlang:element(2, erlang:element(2, Area)),
                            Y},
                        Padded,
                        Fg,
                        Bg,
                        Modifier
                    ),
                    render_rows(Buf2, Area, W, State, Offset, Row_off + 1)
            end
    end.

-file("src/etui/widgets/multi_select.gleam", 172).
-spec render(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    multi_select_widget(),
    multi_select_state()
) -> etui@buffer:buffer().
render(Buf, Area, W, State) ->
    case (erlang:element(3, erlang:element(3, Area)) =< 0) orelse (erlang:element(
        2,
        erlang:element(3, Area)
    )
    =< 0) of
        true ->
            Buf;

        false ->
            Offset = scroll_offset(
                erlang:element(2, State),
                erlang:element(4, State),
                erlang:element(3, erlang:element(3, Area))
            ),
            render_rows(Buf, Area, W, State, Offset, 0)
    end.