-module(etui@widgets@input). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/etui/widgets/input.gleam"). -export([input_new/1, with_prompt/2, with_password/2, with_mask/2, with_validator/2, with_error_color/2, validate/2, with_max_length/2, with_colors/3, with_style/2, state_new/0, state_from_string/1, insert_char/3, backspace/1, move_cursor_left/1, move_cursor_right/1, move_to_start/1, move_to_end/1, delete_to_end/1, clear_state/1, render/4]). -export_type([input_widget/0, input_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 input_widget() :: {input_widget, integer(), binary(), etui@style:color(), etui@style:color(), fun((binary()) -> {ok, nil} | {error, binary()}), etui@style:color(), binary(), boolean(), binary()}. -type input_state() :: {input_state, binary(), integer()}. -file("src/etui/widgets/input.gleam", 54). ?DOC(" New input widget with placeholder text. Default max length: 256 cells.\n"). -spec input_new(binary()) -> input_widget(). input_new(Placeholder) -> {input_widget, 256, Placeholder, default, default, fun(_) -> {ok, nil} end, {indexed, 1}, <<""/utf8>>, false, <<"*"/utf8>>}. -file("src/etui/widgets/input.gleam", 69). ?DOC(" Set a prefix shown before the value (e.g. `\"> \"`).\n"). -spec with_prompt(input_widget(), binary()) -> input_widget(). with_prompt(I, Prompt) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), erlang:element(6, I), erlang:element(7, I), Prompt, erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 74). ?DOC(" Render each value cell as the mask character. Use for password fields.\n"). -spec with_password(input_widget(), boolean()) -> input_widget(). with_password(I, Password) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), erlang:element(6, I), erlang:element(7, I), erlang:element(8, I), Password, erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 79). ?DOC(" Mask character used when `password` is True. Default `\"*\"`.\n"). -spec with_mask(input_widget(), binary()) -> input_widget(). with_mask(I, Mask) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), erlang:element(6, I), erlang:element(7, I), erlang:element(8, I), erlang:element(9, I), Mask}. -file("src/etui/widgets/input.gleam", 84). ?DOC(" Set a validation function. Call `validate/2` to run it.\n"). -spec with_validator( input_widget(), fun((binary()) -> {ok, nil} | {error, binary()}) ) -> input_widget(). with_validator(I, V) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), V, erlang:element(7, I), erlang:element(8, I), erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 89). ?DOC(" Set the color used to display validation errors.\n"). -spec with_error_color(input_widget(), etui@style:color()) -> input_widget(). with_error_color(I, Fg) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), erlang:element(6, I), Fg, erlang:element(8, I), erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 94). ?DOC(" Run the validator on `value`. Returns Ok(Nil) or Error(message).\n"). -spec validate(input_widget(), binary()) -> {ok, nil} | {error, binary()}. validate(I, Value) -> (erlang:element(6, I))(Value). -file("src/etui/widgets/input.gleam", 99). ?DOC(" Maximum value width in cells (wide characters count as 2).\n"). -spec with_max_length(input_widget(), integer()) -> input_widget(). with_max_length(I, Len) -> {input_widget, Len, erlang:element(3, I), erlang:element(4, I), erlang:element(5, I), erlang:element(6, I), erlang:element(7, I), erlang:element(8, I), erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 103). -spec with_colors(input_widget(), etui@style:color(), etui@style:color()) -> input_widget(). with_colors(I, Fg, Bg) -> {input_widget, erlang:element(2, I), erlang:element(3, I), Fg, Bg, erlang:element(6, I), erlang:element(7, I), erlang:element(8, I), erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 111). -spec with_style(input_widget(), etui@style:style()) -> input_widget(). with_style(I, S) -> {input_widget, erlang:element(2, I), erlang:element(3, I), erlang:element(2, S), erlang:element(3, S), erlang:element(6, I), erlang:element(7, I), erlang:element(8, I), erlang:element(9, I), erlang:element(10, I)}. -file("src/etui/widgets/input.gleam", 119). ?DOC(" Initial state: empty value, cursor at 0.\n"). -spec state_new() -> input_state(). state_new() -> {input_state, <<""/utf8>>, 0}. -file("src/etui/widgets/input.gleam", 124). ?DOC(" State pre-populated with a string; cursor placed at the end.\n"). -spec state_from_string(binary()) -> input_state(). state_from_string(S) -> {input_state, S, etui@text:cell_width(S)}. -file("src/etui/widgets/input.gleam", 132). ?DOC(" Insert character at cursor. Respects widget max_length.\n"). -spec insert_char(input_widget(), input_state(), binary()) -> input_state(). insert_char(Widget, State, Ch) -> case etui@text:cell_width(erlang:element(2, State)) >= erlang:element( 2, Widget ) of true -> State; false -> Before = etui@text:truncate( erlang:element(2, State), erlang:element(3, State), <<""/utf8>> ), After = gleam@string:drop_start( erlang:element(2, State), string:length(Before) ), {input_state, <<<>/binary, After/binary>>, erlang:element(3, State) + etui@text:cell_width(Ch)} end. -file("src/etui/widgets/input.gleam", 151). ?DOC(" Delete the character immediately before the cursor (backspace semantics).\n"). -spec backspace(input_state()) -> input_state(). backspace(State) -> case erlang:element(3, State) =< 0 of true -> State; false -> Before = etui@text:truncate( erlang:element(2, State), erlang:element(3, State) - 1, <<""/utf8>> ), Graphemes_at_cursor = string:length( etui@text:truncate( erlang:element(2, State), erlang:element(3, State), <<""/utf8>> ) ), After = gleam@string:drop_start( erlang:element(2, State), Graphemes_at_cursor ), {input_state, <>, etui@text:cell_width(Before)} end. -file("src/etui/widgets/input.gleam", 165). ?DOC(" Move cursor one cell left, clamped to 0.\n"). -spec move_cursor_left(input_state()) -> input_state(). move_cursor_left(State) -> case erlang:element(3, State) =< 0 of true -> State; false -> New_cursor = etui@text:cell_width( etui@text:truncate( erlang:element(2, State), erlang:element(3, State) - 1, <<""/utf8>> ) ), {input_state, erlang:element(2, State), New_cursor} end. -file("src/etui/widgets/input.gleam", 261). -spec grapheme_width_at(binary(), integer()) -> integer(). grapheme_width_at(S, Cell_pos) -> Prefix = etui@text:truncate(S, Cell_pos, <<""/utf8>>), Rest = gleam@string:drop_start(S, string:length(Prefix)), case gleam@string:to_graphemes(Rest) of [G | _] -> etui@text:cell_width(G); [] -> 1 end. -file("src/etui/widgets/input.gleam", 177). ?DOC(" Move cursor one cell right, clamped to end of value.\n"). -spec move_cursor_right(input_state()) -> input_state(). move_cursor_right(State) -> case erlang:element(3, State) >= etui@text:cell_width( erlang:element(2, State) ) of true -> State; false -> Step = grapheme_width_at( erlang:element(2, State), erlang:element(3, State) ), {input_state, erlang:element(2, State), erlang:element(3, State) + Step} end. -file("src/etui/widgets/input.gleam", 188). ?DOC(" Move cursor to beginning of value.\n"). -spec move_to_start(input_state()) -> input_state(). move_to_start(State) -> {input_state, erlang:element(2, State), 0}. -file("src/etui/widgets/input.gleam", 193). ?DOC(" Move cursor to end of value.\n"). -spec move_to_end(input_state()) -> input_state(). move_to_end(State) -> {input_state, erlang:element(2, State), etui@text:cell_width(erlang:element(2, State))}. -file("src/etui/widgets/input.gleam", 198). ?DOC(" Delete from cursor to end of value.\n"). -spec delete_to_end(input_state()) -> input_state(). delete_to_end(State) -> Before = etui@text:truncate( erlang:element(2, State), erlang:element(3, State), <<""/utf8>> ), {input_state, Before, erlang:element(3, State)}. -file("src/etui/widgets/input.gleam", 204). ?DOC(" Reset value and cursor to empty.\n"). -spec clear_state(input_state()) -> input_state(). clear_state(_) -> {input_state, <<""/utf8>>, 0}. -file("src/etui/widgets/input.gleam", 214). ?DOC( " Render the input field. Shows `state.value` (bold) or placeholder when empty.\n" " Text is truncated to fit `area.size.width` (minus one cell reserved for the\n" " trailing cursor). When `password` is True the value is masked.\n" ). -spec render( etui@buffer:buffer(), etui@geometry:rect(), input_widget(), input_state() ) -> etui@buffer:buffer(). render(Buf, Area, Widget, State) -> case erlang:element(2, erlang:element(3, Area)) =< 0 of true -> Buf; false -> Has_value = erlang:element(2, State) /= <<""/utf8>>, Value_display = case erlang:element(9, Widget) andalso Has_value of true -> gleam@string:repeat( erlang:element(10, Widget), etui@text:cell_width(erlang:element(2, State)) ); false -> erlang:element(2, State) end, Display_text = case Has_value of true -> <<(erlang:element(8, Widget))/binary, Value_display/binary>>; false -> <<(erlang:element(8, Widget))/binary, (erlang:element(3, Widget))/binary>> end, C = etui@text:cell_width(erlang:element(8, Widget)) + erlang:element( 3, State ), W_avail = gleam@int:max( 1, erlang:element(2, erlang:element(3, Area)) ), View_start = case C >= W_avail of true -> (C - W_avail) + 1; false -> 0 end, Prefix = etui@text:truncate(Display_text, View_start, <<""/utf8>>), Suffix = gleam@string:drop_start( Display_text, string:length(Prefix) ), Truncated = etui@text:truncate(Suffix, W_avail, <<""/utf8>>), Padded = etui@text:pad_right(Truncated, W_avail), Modifier = case Has_value of true -> etui@style:bold(); false -> etui@style:none() end, etui@buffer:set_string( Buf, erlang:element(2, Area), Padded, erlang:element(4, Widget), erlang:element(5, Widget), Modifier ) end.