tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 124Markdown 82TOML 78Shell 12149 Sutichaykuna

⎇ main widgets/text-input/examples/area_demo.rs Rust · 149 siq'ikuna · 5.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

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}