-module(etui@widgets@table). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/etui/widgets/table.gleam"). -export([table_new/1, with_col_widths/2, with_col_constraints/2, with_header/2, with_colors/3, with_highlight_style/2, with_style/2, with_blink/2, state_new/0, select_row/2, select_next_row/2, select_prev_row/1, clamp_state/2, effective_offset/2, render/3, render_stateful/4, render_animated/5]). -export_type([table_widget/0, table_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 table_widget() :: {table_widget, list(list(binary())), list(integer()), list(etui@geometry:constraint()), boolean(), etui@style:color(), etui@style:color(), etui@style:style(), integer()}. -type table_state() :: {table_state, integer(), integer()}. -file("src/etui/widgets/table.gleam", 51). ?DOC(" New table. Column widths default to 10 cells each.\n"). -spec table_new(list(list(binary()))) -> table_widget(). table_new(Rows) -> Col_widths = case Rows of [] -> []; [First | _] -> gleam@list:repeat(10, erlang:length(First)) end, {table_widget, Rows, Col_widths, [], false, default, default, {style, default, default, etui@style:reverse()}, 0}. -file("src/etui/widgets/table.gleam", 73). ?DOC(" Override column widths (cell counts). Must match number of columns.\n"). -spec with_col_widths(table_widget(), list(integer())) -> table_widget(). with_col_widths(T, Widths) -> {table_widget, erlang:element(2, T), Widths, erlang:element(4, T), erlang:element(5, T), erlang:element(6, T), erlang:element(7, T), erlang:element(8, T), erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 80). ?DOC( " Constraint-based column widths, resolved from area width at render time.\n" " When set, takes precedence over `col_widths`.\n" " Separator cells (│) are subtracted before resolving.\n" ). -spec with_col_constraints(table_widget(), list(etui@geometry:constraint())) -> table_widget(). with_col_constraints(T, Constraints) -> {table_widget, erlang:element(2, T), erlang:element(3, T), Constraints, erlang:element(5, T), erlang:element(6, T), erlang:element(7, T), erlang:element(8, T), erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 90). ?DOC( " When true, `t.rows[0]` is rendered as a bold header row (never selectable).\n" " `selected_row` uses absolute indices: 0 = header, 1 = first data row, etc.\n" " Initialize state with `select_row(state_new(), 1)` for the first data row.\n" ). -spec with_header(table_widget(), boolean()) -> table_widget(). with_header(T, Show) -> {table_widget, erlang:element(2, T), erlang:element(3, T), erlang:element(4, T), Show, erlang:element(6, T), erlang:element(7, T), erlang:element(8, T), erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 94). -spec with_colors(table_widget(), etui@style:color(), etui@style:color()) -> table_widget(). with_colors(T, Fg, Bg) -> {table_widget, erlang:element(2, T), erlang:element(3, T), erlang:element(4, T), erlang:element(5, T), Fg, Bg, erlang:element(8, T), erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 102). -spec with_highlight_style(table_widget(), etui@style:style()) -> table_widget(). with_highlight_style(T, S) -> {table_widget, erlang:element(2, T), erlang:element(3, T), erlang:element(4, T), erlang:element(5, T), erlang:element(6, T), erlang:element(7, T), S, erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 106). -spec with_style(table_widget(), etui@style:style()) -> table_widget(). with_style(T, S) -> {table_widget, erlang:element(2, T), erlang:element(3, T), erlang:element(4, T), erlang:element(5, T), erlang:element(2, S), erlang:element(3, S), erlang:element(8, T), erlang:element(9, T)}. -file("src/etui/widgets/table.gleam", 111). ?DOC(" Blink period in frames. 0 = steady (no blink). Use with `render_animated`.\n"). -spec with_blink(table_widget(), integer()) -> table_widget(). with_blink(T, Period) -> {table_widget, erlang:element(2, T), erlang:element(3, T), erlang:element(4, T), erlang:element(5, T), erlang:element(6, T), erlang:element(7, T), erlang:element(8, T), Period}. -file("src/etui/widgets/table.gleam", 121). ?DOC( " Initial state: selected_row=0, no scroll offset.\n" " When using `with_header(True)`, row 0 is the header (not selectable).\n" " Use `select_row(state_new(), 1)` to start with the first data row highlighted.\n" ). -spec state_new() -> table_state(). state_new() -> {table_state, 0, 0}. -file("src/etui/widgets/table.gleam", 126). ?DOC(" Jump to a specific row (clamped to ≥ 0).\n"). -spec select_row(table_state(), integer()) -> table_state(). select_row(State, Idx) -> {table_state, gleam@int:max(0, Idx), erlang:element(3, State)}. -file("src/etui/widgets/table.gleam", 131). ?DOC(" Move selection down by one, clamped to last row.\n"). -spec select_next_row(table_state(), integer()) -> table_state(). select_next_row(State, Row_count) -> Max_idx = gleam@int:max(0, Row_count - 1), {table_state, gleam@int:min(Max_idx, erlang:element(2, State) + 1), erlang:element(3, State)}. -file("src/etui/widgets/table.gleam", 137). ?DOC(" Move selection up by one, clamped to 0.\n"). -spec select_prev_row(table_state()) -> table_state(). select_prev_row(State) -> {table_state, gleam@int:max(0, erlang:element(2, State) - 1), erlang:element(3, State)}. -file("src/etui/widgets/table.gleam", 143). ?DOC( " Clamp `selected_row` to `[0, row_count - 1]`.\n" " Call after replacing the row list to avoid a stale selection index.\n" ). -spec clamp_state(table_state(), integer()) -> table_state(). clamp_state(State, Row_count) -> Max = gleam@int:max(0, Row_count - 1), {table_state, gleam@int:min(erlang:element(2, State), Max), erlang:element(3, State)}. -file("src/etui/widgets/table.gleam", 285). -spec scroll_offset(integer(), integer(), integer()) -> integer(). scroll_offset(Selected, Offset, Height) -> case Selected < Offset of true -> Selected; false -> case Height =< 0 of true -> Offset; false -> case Selected >= (Offset + Height) of true -> (Selected - Height) + 1; false -> Offset end end end. -file("src/etui/widgets/table.gleam", 151). ?DOC( " Effective scroll offset for a viewport of `visible_data_h` data rows.\n" " When `show_header` is True, pass `area.size.height - 1`; otherwise pass `area.size.height`.\n" " Pass as `offset` to `scrollbar.scrollbar_new`.\n" ). -spec effective_offset(table_state(), integer()) -> integer(). effective_offset(State, Visible_data_h) -> scroll_offset( erlang:element(2, State), erlang:element(3, State), Visible_data_h ). -file("src/etui/widgets/table.gleam", 354). -spec render_empty_line(integer()) -> binary(). render_empty_line(Width) -> etui@text:pad_right(<<""/utf8>>, Width). -file("src/etui/widgets/table.gleam", 337). -spec render_cells(list(binary()), list(integer())) -> list(binary()). render_cells(Row, Widths) -> case {Row, Widths} of {_, []} -> []; {[], [Width | Rest_widths]} -> Formatted = etui@text:pad_right( <<""/utf8>>, gleam@int:max(0, Width - 1) ), [Formatted | render_cells([], Rest_widths)]; {[Cell | Rest_row], [Width@1 | Rest_widths@1]} -> Formatted@1 = begin _pipe = etui@text:truncate(Cell, Width@1 - 1, <<""/utf8>>), etui@text:pad_right(_pipe, Width@1 - 1) end, [Formatted@1 | render_cells(Rest_row, Rest_widths@1)] end. -file("src/etui/widgets/table.gleam", 315). -spec render_row_line(list(binary()), list(integer()), integer(), boolean()) -> binary(). render_row_line(Row, Col_widths, Max_width, Is_selected) -> Cells = render_cells(Row, Col_widths), Line = gleam@list:fold(Cells, <<""/utf8>>, fun(Acc, Cell) -> case Acc of <<""/utf8>> -> Cell; _ -> <<<>/binary, Cell/binary>> end end), Prefix = case Is_selected of true -> <<"▶"/utf8>>; false -> <<" "/utf8>> end, _pipe = etui@text:truncate( <>, Max_width, <<""/utf8>> ), etui@text:pad_right(_pipe, Max_width). -file("src/etui/widgets/table.gleam", 303). -spec get_row_at(list(list(binary())), integer()) -> {ok, list(binary())} | {error, nil}. get_row_at(Rows, Idx) -> case Idx of I when I < 0 -> {error, nil}; 0 -> case Rows of [H | _] -> {ok, H}; [] -> {error, nil} end; _ -> get_row_at(gleam@list:drop(Rows, 1), Idx - 1) end. -file("src/etui/widgets/table.gleam", 218). -spec do_render( etui@buffer:buffer(), etui@geometry:rect(), table_widget(), integer(), integer(), integer() ) -> etui@buffer:buffer(). do_render(Buf, Area, T, Selected, Offset, Y_offset) -> Col_widths = case erlang:element(4, T) of [] -> erlang:element(3, T); Constraints -> etui@geometry:resolve_sizes( erlang:element(2, erlang:element(3, Area)), Constraints ) end, case Y_offset >= erlang:element(3, erlang:element(3, Area)) of true -> Buf; false -> Y = erlang:element(3, erlang:element(2, Area)) + Y_offset, Is_header = erlang:element(5, T) andalso (Y_offset =:= 0), Row_idx = Offset + Y_offset, Is_selected = not Is_header andalso (Row_idx =:= Selected), Row_line = case Is_header of true -> case get_row_at(erlang:element(2, T), 0) of {ok, Row} -> render_row_line( Row, Col_widths, erlang:element(2, erlang:element(3, Area)), false ); {error, _} -> render_empty_line( erlang:element(2, erlang:element(3, Area)) ) end; false -> case get_row_at(erlang:element(2, T), Row_idx) of {ok, Row@1} -> render_row_line( Row@1, Col_widths, erlang:element(2, erlang:element(3, Area)), Is_selected ); {error, _} -> render_empty_line( erlang:element(2, erlang:element(3, Area)) ) end end, {Row_fg, Row_bg, Row_modifier} = case Is_header of true -> {erlang:element(6, T), erlang:element(7, T), etui@style:bold()}; false -> case Is_selected of true -> {erlang:element(2, erlang:element(8, T)), erlang:element(3, erlang:element(8, T)), erlang:element(4, erlang:element(8, T))}; false -> {erlang:element(6, T), erlang:element(7, T), etui@style:none()} end end, Buf_new = etui@buffer:set_string( Buf, {position, erlang:element(2, erlang:element(2, Area)), Y}, Row_line, Row_fg, Row_bg, Row_modifier ), do_render(Buf_new, Area, T, Selected, Offset, Y_offset + 1) end. -file("src/etui/widgets/table.gleam", 159). ?DOC(" Render without selection highlight.\n"). -spec render(etui@buffer:buffer(), etui@geometry:rect(), table_widget()) -> etui@buffer:buffer(). render(Buf, Area, T) -> case erlang:element(3, erlang:element(3, Area)) =< 0 of true -> Buf; false -> do_render(Buf, Area, T, -1, 0, 0) end. -file("src/etui/widgets/table.gleam", 171). ?DOC(" Render with selection and auto-scrolling from state.\n"). -spec render_stateful( etui@buffer:buffer(), etui@geometry:rect(), table_widget(), table_state() ) -> etui@buffer:buffer(). render_stateful(Buf, Area, T, State) -> case erlang:element(3, erlang:element(3, Area)) =< 0 of true -> Buf; false -> Visible_data = case erlang:element(5, T) of true -> gleam@int:max( 0, erlang:element(3, erlang:element(3, Area)) - 1 ); false -> erlang:element(3, erlang:element(3, Area)) end, Offset = scroll_offset( erlang:element(2, State), erlang:element(3, State), Visible_data ), do_render(Buf, Area, T, erlang:element(2, State), Offset, 0) end. -file("src/etui/widgets/table.gleam", 193). ?DOC( " Like `render_stateful` but supports blinking selection via `t.blink_period`.\n" " Pass the current `AnimState.frame`; use `with_blink(t, period)` to configure.\n" ). -spec render_animated( etui@buffer:buffer(), etui@geometry:rect(), table_widget(), table_state(), integer() ) -> etui@buffer:buffer(). render_animated(Buf, Area, T, State, Frame) -> case erlang:element(3, erlang:element(3, Area)) =< 0 of true -> Buf; false -> Visible_data = case erlang:element(5, T) of true -> gleam@int:max( 0, erlang:element(3, erlang:element(3, Area)) - 1 ); false -> erlang:element(3, erlang:element(3, Area)) end, Offset = scroll_offset( erlang:element(2, State), erlang:element(3, State), Visible_data ), Show = etui@anim:blink(Frame, erlang:element(9, T)), Sel = case Show of true -> erlang:element(2, State); false -> -1 end, do_render(Buf, Area, T, Sel, Offset, 0) end.