Skip to main content

src/etui@widgets@block.erl

-module(etui@widgets@block).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/widgets/block.gleam").
-export([block_new/0, with_bg_fill/1, with_border/2, with_title/3, with_title_styled/3, with_padding/5, with_title_alignment/2, with_style/3, with_colors/3, render/3, inner/2]).
-export_type([border/0, title_position/0, block/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 border() :: none | single | double | rounded.

-type title_position() :: top | bottom.

-type block() :: {block,
        border(),
        binary(),
        list(etui@span:span()),
        title_position(),
        etui@text:alignment(),
        integer(),
        integer(),
        integer(),
        integer(),
        etui@style:color(),
        etui@style:color(),
        boolean()}.

-file("src/etui/widgets/block.gleam", 49).
?DOC(" New block with no border, no title, no padding, default colors.\n").
-spec block_new() -> block().
block_new() ->
    {block,
        none,
        <<""/utf8>>,
        [],
        top,
        left,
        0,
        0,
        0,
        0,
        default,
        default,
        false}.

-file("src/etui/widgets/block.gleam", 67).
?DOC(" Fill the inner area with the block's background color.\n").
-spec with_bg_fill(block()) -> block().
with_bg_fill(Blk) ->
    {block,
        erlang:element(2, Blk),
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        true}.

-file("src/etui/widgets/block.gleam", 72).
?DOC(" Set the border style. `Single` draws ┌─┐│└─┘, `Double` ╔═╗║╚═╝, `Rounded` ╭─╮│╰─╯.\n").
-spec with_border(block(), border()) -> block().
with_border(Blk, Border) ->
    {block,
        Border,
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 77).
?DOC(" Set a title string and whether it appears on the top or bottom border.\n").
-spec with_title(block(), binary(), title_position()) -> block().
with_title(Blk, Title, Position) ->
    {block,
        erlang:element(2, Blk),
        Title,
        [],
        Position,
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 92).
?DOC(
    " Set a styled title from a list of `span.Span` values.\n"
    " Takes precedence over `with_title` when non-empty.\n"
    "\n"
    " ```gleam\n"
    " block.block_new()\n"
    " |> block.with_border(block.Rounded)\n"
    " |> block.with_title_styled([\n"
    "   span.span_styled(\"\", style.bold_style() |> style.with_fg(style.Rgb(255,215,0))),\n"
    "   span.span_plain(\"Dashboard\"),\n"
    " ], block.Top)\n"
    " ```\n"
).
-spec with_title_styled(block(), list(etui@span:span()), title_position()) -> block().
with_title_styled(Blk, Spans, Position) ->
    {block,
        erlang:element(2, Blk),
        <<""/utf8>>,
        Spans,
        Position,
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 101).
?DOC(" Set inner padding (cells between border and content).\n").
-spec with_padding(block(), integer(), integer(), integer(), integer()) -> block().
with_padding(Blk, Top, Bottom, Left, Right) ->
    {block,
        erlang:element(2, Blk),
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        erlang:element(6, Blk),
        Top,
        Bottom,
        Left,
        Right,
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 118).
?DOC(" Set the horizontal alignment of the title within the border.\n").
-spec with_title_alignment(block(), etui@text:alignment()) -> block().
with_title_alignment(Blk, Alignment) ->
    {block,
        erlang:element(2, Blk),
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        Alignment,
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        erlang:element(11, Blk),
        erlang:element(12, Blk),
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 123).
?DOC(" Set foreground and background colors for the border and title.\n").
-spec with_style(block(), etui@style:color(), etui@style:color()) -> block().
with_style(Blk, Fg, Bg) ->
    {block,
        erlang:element(2, Blk),
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        Fg,
        Bg,
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 128).
?DOC(" Alias for `with_style(fg, bg)`, consistent with other widget naming.\n").
-spec with_colors(block(), etui@style:color(), etui@style:color()) -> block().
with_colors(Blk, Fg, Bg) ->
    {block,
        erlang:element(2, Blk),
        erlang:element(3, Blk),
        erlang:element(4, Blk),
        erlang:element(5, Blk),
        erlang:element(6, Blk),
        erlang:element(7, Blk),
        erlang:element(8, Blk),
        erlang:element(9, Blk),
        erlang:element(10, Blk),
        Fg,
        Bg,
        erlang:element(13, Blk)}.

-file("src/etui/widgets/block.gleam", 348).
-spec inner_area(etui@geometry:rect(), block()) -> etui@geometry:rect().
inner_area(Area, Blk) ->
    Border_offset = case erlang:element(2, Blk) of
        none ->
            0;

        single ->
            1;

        double ->
            1;

        rounded ->
            1
    end,
    X = (erlang:element(2, erlang:element(2, Area)) + erlang:element(9, Blk)) + Border_offset,
    Y = (erlang:element(3, erlang:element(2, Area)) + erlang:element(7, Blk)) + Border_offset,
    W = gleam@int:max(
        0,
        ((erlang:element(2, erlang:element(3, Area)) - erlang:element(9, Blk)) - erlang:element(
            10,
            Blk
        ))
        - (Border_offset * 2)
    ),
    H = gleam@int:max(
        0,
        ((erlang:element(3, erlang:element(3, Area)) - erlang:element(7, Blk)) - erlang:element(
            8,
            Blk
        ))
        - (Border_offset * 2)
    ),
    {rect, {position, X, Y}, {size, W, H}}.

-file("src/etui/widgets/block.gleam", 310).
-spec fill_bg_area(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    etui@style:color(),
    etui@style:color(),
    integer()
) -> etui@buffer:buffer().
fill_bg_area(Buf, Area, Fg, Bg, Y) ->
    case Y >= (erlang:element(3, erlang:element(2, Area)) + erlang:element(
        3,
        erlang:element(3, Area)
    )) of
        true ->
            Buf;

        false ->
            Row = etui@text:pad_right(
                <<""/utf8>>,
                erlang:element(2, erlang:element(3, Area))
            ),
            Buf2 = etui@buffer:set_string(
                Buf,
                {position, erlang:element(2, erlang:element(2, Area)), Y},
                Row,
                Fg,
                Bg,
                etui@style:none()
            ),
            fill_bg_area(Buf2, Area, Fg, Bg, Y + 1)
    end.

-file("src/etui/widgets/block.gleam", 299).
-spec render_content(etui@buffer:buffer(), etui@geometry:rect(), block()) -> etui@buffer:buffer().
render_content(Buf, Area, Blk) ->
    case erlang:element(13, Blk) of
        false ->
            etui@buffer:clear(Buf, Area);

        true ->
            fill_bg_area(
                Buf,
                Area,
                erlang:element(11, Blk),
                erlang:element(12, Blk),
                erlang:element(3, erlang:element(2, Area))
            )
    end.

-file("src/etui/widgets/block.gleam", 251).
-spec render_title(etui@buffer:buffer(), etui@geometry:rect(), block()) -> etui@buffer:buffer().
render_title(Buf, Area, Blk) ->
    X0 = erlang:element(2, erlang:element(2, Area)) + 1,
    Max_width = erlang:element(2, erlang:element(3, Area)) - 2,
    case erlang:element(4, Blk) of
        [_ | _] ->
            Y = case erlang:element(5, Blk) of
                top ->
                    erlang:element(3, erlang:element(2, Area));

                bottom ->
                    etui@geometry:bottom(Area) - 1
            end,
            Line = etui@span:line_aligned(
                erlang:element(4, Blk),
                erlang:element(6, Blk)
            ),
            etui@span:render_line(Buf, {position, X0, Y}, Line, Max_width);

        [] ->
            case erlang:element(3, Blk) of
                <<""/utf8>> ->
                    Buf;

                Title ->
                    Title_width = etui@text:cell_width(Title),
                    T = case Title_width > Max_width of
                        true ->
                            etui@text:truncate(Title, Max_width, <<"…"/utf8>>);

                        false ->
                            Title
                    end,
                    T_width = etui@text:cell_width(T),
                    X_offset = case erlang:element(6, Blk) of
                        left ->
                            0;

                        right ->
                            gleam@int:max(0, Max_width - T_width);

                        center ->
                            gleam@int:max(0, (Max_width - T_width) div 2)
                    end,
                    Y@1 = case erlang:element(5, Blk) of
                        top ->
                            erlang:element(3, erlang:element(2, Area));

                        bottom ->
                            etui@geometry:bottom(Area) - 1
                    end,
                    etui@buffer:set_string(
                        Buf,
                        {position, X0 + X_offset, Y@1},
                        T,
                        erlang:element(11, Blk),
                        erlang:element(12, Blk),
                        etui@style:none()
                    )
            end
    end.

-file("src/etui/widgets/block.gleam", 397).
-spec draw_vertical_line(
    etui@buffer:buffer(),
    integer(),
    integer(),
    integer(),
    etui@buffer:cell()
) -> etui@buffer:buffer().
draw_vertical_line(Buf, X, Y_start, Y_end, Cell) ->
    case Y_start > Y_end of
        true ->
            Buf;

        false ->
            Buf_new = etui@buffer:set_cell(Buf, {position, X, Y_start}, Cell),
            draw_vertical_line(Buf_new, X, Y_start + 1, Y_end, Cell)
    end.

-file("src/etui/widgets/block.gleam", 380).
-spec draw_horizontal_line(
    etui@buffer:buffer(),
    integer(),
    integer(),
    integer(),
    etui@buffer:cell()
) -> etui@buffer:buffer().
draw_horizontal_line(Buf, X_start, X_end, Y, Cell) ->
    case X_start > X_end of
        true ->
            Buf;

        false ->
            Buf_new = etui@buffer:set_cell(Buf, {position, X_start, Y}, Cell),
            draw_horizontal_line(Buf_new, X_start + 1, X_end, Y, Cell)
    end.

-file("src/etui/widgets/block.gleam", 149).
-spec render_bordered(
    etui@buffer:buffer(),
    etui@geometry:rect(),
    block(),
    binary(),
    binary(),
    binary(),
    binary(),
    binary(),
    binary()
) -> etui@buffer:buffer().
render_bordered(
    Buf,
    Area,
    Blk,
    Border_h,
    Border_v,
    Corner_tl,
    Corner_tr,
    Corner_bl,
    Corner_br
) ->
    Width = erlang:element(2, erlang:element(3, Area)),
    Height = erlang:element(3, erlang:element(3, Area)),
    case (Width < 2) orelse (Height < 2) of
        true ->
            Buf;

        false ->
            X0 = erlang:element(2, erlang:element(2, Area)),
            Y0 = erlang:element(3, erlang:element(2, Area)),
            Y_bottom = etui@geometry:bottom(Area) - 1,
            X_right = etui@geometry:right(Area) - 1,
            Cell_border = {cell,
                {content, Border_h, 1},
                erlang:element(11, Blk),
                erlang:element(12, Blk),
                etui@style:none(),
                <<""/utf8>>},
            Buf1 = etui@buffer:set_cell(
                Buf,
                {position, X0, Y0},
                {cell,
                    {content, Corner_tl, 1},
                    erlang:element(11, Blk),
                    erlang:element(12, Blk),
                    etui@style:none(),
                    <<""/utf8>>}
            ),
            Buf2 = etui@buffer:set_cell(
                Buf1,
                {position, X_right, Y0},
                {cell,
                    {content, Corner_tr, 1},
                    erlang:element(11, Blk),
                    erlang:element(12, Blk),
                    etui@style:none(),
                    <<""/utf8>>}
            ),
            Buf3 = etui@buffer:set_cell(
                Buf2,
                {position, X0, Y_bottom},
                {cell,
                    {content, Corner_bl, 1},
                    erlang:element(11, Blk),
                    erlang:element(12, Blk),
                    etui@style:none(),
                    <<""/utf8>>}
            ),
            Buf4 = etui@buffer:set_cell(
                Buf3,
                {position, X_right, Y_bottom},
                {cell,
                    {content, Corner_br, 1},
                    erlang:element(11, Blk),
                    erlang:element(12, Blk),
                    etui@style:none(),
                    <<""/utf8>>}
            ),
            Buf5 = draw_horizontal_line(
                Buf4,
                X0 + 1,
                X_right - 1,
                Y0,
                Cell_border
            ),
            Buf6 = draw_horizontal_line(
                Buf5,
                X0 + 1,
                X_right - 1,
                Y_bottom,
                Cell_border
            ),
            Cell_v = {cell,
                {content, Border_v, 1},
                erlang:element(11, Blk),
                erlang:element(12, Blk),
                etui@style:none(),
                <<""/utf8>>},
            Buf7 = draw_vertical_line(Buf6, X0, Y0 + 1, Y_bottom - 1, Cell_v),
            Buf8 = draw_vertical_line(
                Buf7,
                X_right,
                Y0 + 1,
                Y_bottom - 1,
                Cell_v
            ),
            Buf9 = render_title(Buf8, Area, Blk),
            render_content(Buf9, inner_area(Area, Blk), Blk)
    end.

-file("src/etui/widgets/block.gleam", 136).
?DOC(" Render block into buffer at given area.\n").
-spec render(etui@buffer:buffer(), etui@geometry:rect(), block()) -> etui@buffer:buffer().
render(Buf, Area, Blk) ->
    case erlang:element(2, Blk) of
        none ->
            render_content(Buf, Area, Blk);

        single ->
            render_bordered(
                Buf,
                Area,
                Blk,
                <<"─"/utf8>>,
                <<"│"/utf8>>,
                <<"┌"/utf8>>,
                <<"┐"/utf8>>,
                <<"└"/utf8>>,
                <<"┘"/utf8>>
            );

        double ->
            render_bordered(
                Buf,
                Area,
                Blk,
                <<"═"/utf8>>,
                <<"║"/utf8>>,
                <<"╔"/utf8>>,
                <<"╗"/utf8>>,
                <<"╚"/utf8>>,
                <<"╝"/utf8>>
            );

        rounded ->
            render_bordered(
                Buf,
                Area,
                Blk,
                <<"─"/utf8>>,
                <<"│"/utf8>>,
                <<"╭"/utf8>>,
                <<"╮"/utf8>>,
                <<"╰"/utf8>>,
                <<"╯"/utf8>>
            )
    end.

-file("src/etui/widgets/block.gleam", 344).
?DOC(
    " Inner area (content region) of a block, accounting for border and padding.\n"
    "\n"
    " Use this to get the Rect to pass to child widgets:\n"
    "\n"
    " ```gleam\n"
    " let b = block.block_new() |> block.with_border(block.Single)\n"
    " block.render(buf, area, b)\n"
    " |> paragraph.render(block.inner(area, b), p)\n"
    " ```\n"
).
-spec inner(etui@geometry:rect(), block()) -> etui@geometry:rect().
inner(Area, Blk) ->
    inner_area(Area, Blk).