tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1//! Demo de `text_area_view`: el input de texto **multilínea** con paridad real
2//! (mismo motor que el single-line), en vivo.
3//!
4//! Corre con: `cargo run -p llimphi-widget-text-input --example area_demo --release`.
5//!
6//! Qué mirar / probar (esto es lo que el viejo text-area de `String` plano NO
7//! podía — su caret era un bloque fijo al final del texto):
8//! - **Click en cualquier lado** coloca el caret en el renglón + columna exactos
9//! (glifos reales, no ancho fijo, no "el final").
10//! - **↑/↓** navegan entre renglones (preservan la columna deseada); ←/→,
11//! Inicio/Fin, salto de palabra (Ctrl+←/→).
12//! - **Selección**: arrastre a través de líneas, doble-click = palabra,
13//! triple = renglón, Shift+flechas.
14//! - **Copiar/cortar/pegar** (Ctrl+C/X/V), **deshacer/rehacer** (Ctrl+Z/Y),
15//! **acentos/IME** (´ + a → á).
16//! - **Soft-wrap**: la línea larga se **envuelve** sola al ancho de la caja
17//! (redimensiona la ventana y se re-acomoda) — el click sigue cayendo en la
18//! columna correcta del renglón envuelto.
19//! - **Scroll vertical**: hay más líneas que renglones visibles; muévete con ↑/↓
20//! más allá del borde y el bloque **scrollea** para mantener el caret a la vista.
21//! - **Enter** inserta salto de línea (no "submit").
22
23use llimphi_ui::llimphi_layout::taffy::{
24 prelude::{length, percent, FlexDirection, Size, Style},
25 AlignItems, Rect,
26};
27use llimphi_ui::llimphi_raster::peniko::Color;
28use llimphi_ui::llimphi_text::Alignment;
29use llimphi_ui::{App, Handle, KeyEvent, View};
30use llimphi_widget_text_input::{
31 text_area_view, MemClipboard, TextAreaEvent, TextInputPalette, TextInputState,
32};
33
34#[derive(Clone, Debug)]
35enum Msg {
36 Area(TextAreaEvent),
37 Tecla(KeyEvent),
38}
39
40struct Model {
41 texto: TextInputState,
42 focado: bool,
43 clip: MemClipboard,
44}
45
46struct Demo;
47
48impl App for Demo {
49 type Model = Model;
50 type Msg = Msg;
51
52 fn title() -> &'static str {
53 "llimphi · text-area (input multilínea, paridad real)"
54 }
55
56 fn initial_size() -> (u32, u32) {
57 (720, 460)
58 }
59
60 fn init(_: &Handle<Msg>) -> Model {
61 let mut texto = TextInputState::multiline();
62 texto.set_text(
63 "Este es un text-area REAL sobre el mismo motor EditorState que el input de una línea, así que hereda selección, undo, copiar/pegar e IME — y además esta primera línea es deliberadamente larga para que veas el soft-wrap: se parte sola al ancho de la caja.\n\nClickeá en cualquier palabra: el caret cae donde haces click (renglón y columna real), no al final.\n\nArrastrá para seleccionar a través de líneas.\nDoble-click = palabra, triple = renglón.\nCtrl+C / Ctrl+X / Ctrl+V, Ctrl+Z para deshacer.\n↑/↓ navegan; hay más líneas que las visibles,\nasí que al bajar el bloque scrollea solo.\n\nEnter agrega líneas. Prueba redimensionar la\nventana: el wrap se re-acomoda al ancho nuevo.",
64 );
65 Model {
66 texto,
67 focado: true,
68 clip: MemClipboard::new(),
69 }
70 }
71
72 fn on_key(model: &Model, event: &KeyEvent) -> Option<Msg> {
73 model.focado.then(|| Msg::Tecla(event.clone()))
74 }
75
76 fn ime_allowed() -> bool {
77 true
78 }
79
80 fn update(model: Model, msg: Msg, _handle: &Handle<Msg>) -> Model {
81 let mut m = model;
82 let mut clip = std::mem::take(&mut m.clip);
83 match msg {
84 Msg::Area(ev) => {
85 if matches!(ev, TextAreaEvent::Press(..) | TextAreaEvent::Context { .. }) {
86 m.focado = true;
87 }
88 m.texto.handle_area(ev, &mut clip);
89 }
90 Msg::Tecla(e) => {
91 m.texto.handle_area(TextAreaEvent::Key(e), &mut clip);
92 }
93 }
94 m.clip = clip;
95 m
96 }
97
98 fn view(model: &Model) -> View<Msg> {
99 let pal = TextInputPalette::default();
100 let area = text_area_view(
101 &model.texto,
102 "Escribe algo multilínea…",
103 model.focado,
104 7,
105 &pal,
106 Msg::Area,
107 );
108
109 let hint = View::new(Style {
110 size: Size {
111 width: percent(1.0_f32),
112 height: length(22.0_f32),
113 },
114 ..Default::default()
115 })
116 .text_aligned(
117 "click = caret 2D · arrastre/doble-click = selección · ↑↓ renglones · Ctrl+C/V · Enter = salto".to_string(),
118 11.5,
119 Color::from_rgba8(150, 160, 180, 255),
120 Alignment::Start,
121 );
122
123 View::new(Style {
124 flex_direction: FlexDirection::Column,
125 size: Size {
126 width: percent(1.0_f32),
127 height: percent(1.0_f32),
128 },
129 gap: Size {
130 width: length(0.0_f32),
131 height: length(12.0_f32),
132 },
133 padding: Rect {
134 left: length(28.0_f32),
135 right: length(28.0_f32),
136 top: length(24.0_f32),
137 bottom: length(24.0_f32),
138 },
139 align_items: Some(AlignItems::STRETCH),
140 ..Default::default()
141 })
142 .fill(Color::from_rgba8(16, 19, 26, 255))
143 .children(vec![hint, area])
144 }
145}
146
147fn main() {
148 llimphi_ui::run::<Demo>();
149}