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//! Showcase de [`llimphi_widget_list::reorderable_list_view`] (Bloque
2//! 14 de PARIDAD-FLUTTER, primera variante de Tier 5 backlog). Una
3//! lista de tareas con drag-handle al borde izquierdo (`⋮⋮`); arrastra
4//! una fila y soltala sobre otra para intercambiarlas. El destino se
5//! ilumina con `bg_drop_hover` mientras está bajo el cursor.
6//!
7//! Cliquear una fila la marca como "completada" (cambia a tachado en el
8//! label de su descripción de abajo) — sirve para ver que `on_click`
9//! coexiste con el drag sin pelearse.
10//!
11//! `cargo run -p llimphi-widget-list --example reorderable_list_demo --release`
12
13use std::sync::Arc;
14
15use llimphi_theme::Theme;
16use llimphi_ui::llimphi_layout::taffy::{
17 prelude::{length, percent, FlexDirection, Size, Style},
18 AlignItems, JustifyContent, Rect,
19};
20use llimphi_ui::{App, Handle, View};
21use llimphi_widget_list::{
22 reorderable_list_view, ListPalette, ReorderableListRow, ReorderableListSpec,
23};
24
25#[derive(Clone)]
26enum Msg {
27 Reorder { from: usize, to: usize },
28 Toggle(usize),
29}
30
31struct Model {
32 items: Vec<(String, bool)>,
33}
34
35struct Showcase;
36
37impl App for Showcase {
38 type Model = Model;
39 type Msg = Msg;
40
41 fn title() -> &'static str {
42 "llimphi · reorderable list (drag las filas para reordenar)"
43 }
44
45 fn initial_size() -> (u32, u32) {
46 (560, 520)
47 }
48
49 fn init(_: &Handle<Msg>) -> Model {
50 Model {
51 items: vec![
52 ("Cerrar Tier 1 del roadmap (backdrop blur)".into(), true),
53 ("Closeout Tier 2: RichText spans".into(), true),
54 ("ImageFit Contain/Cover/Fill/None".into(), true),
55 ("Reorderable list widget".into(), false),
56 ("Texto seleccionable fuera del editor".into(), false),
57 ("RepaintBoundary (Tier 8)".into(), false),
58 ("Cross-fade real entre identidades".into(), false),
59 ],
60 }
61 }
62
63 fn update(model: Model, msg: Msg, _: &Handle<Msg>) -> Model {
64 let mut m = model;
65 match msg {
66 Msg::Reorder { from, to } => {
67 if from != to && from < m.items.len() && to < m.items.len() {
68 let item = m.items.remove(from);
69 let dest = if to > from { to - 1 } else { to };
70 m.items.insert(dest.min(m.items.len()), item);
71 }
72 }
73 Msg::Toggle(i) => {
74 if let Some(it) = m.items.get_mut(i) {
75 it.1 = !it.1;
76 }
77 }
78 }
79 m
80 }
81
82 fn view(model: &Model) -> View<Msg> {
83 let theme = Theme::dark();
84 let palette = ListPalette::from_theme(&theme);
85
86 let rows: Vec<ReorderableListRow<Msg>> = model
87 .items
88 .iter()
89 .enumerate()
90 .map(|(i, (label, done))| {
91 let prefix = if *done { "✓ " } else { "○ " };
92 ReorderableListRow {
93 label: format!("{prefix}{label}"),
94 selected: *done,
95 on_click: Some(Msg::Toggle(i)),
96 }
97 })
98 .collect();
99
100 let panel = reorderable_list_view(ReorderableListSpec {
101 rows,
102 caption: Some(format!("{} tareas — drag para reordenar, click para marcar", model.items.len())),
103 row_height: 36.0,
104 palette,
105 on_reorder: Arc::new(|from, to| Some(Msg::Reorder { from, to })),
106 });
107
108 // Marco exterior con padding para que el panel no toque los
109 // bordes de la ventana.
110 View::new(Style {
111 flex_direction: FlexDirection::Column,
112 size: Size {
113 width: percent(1.0_f32),
114 height: percent(1.0_f32),
115 },
116 align_items: Some(AlignItems::STRETCH),
117 justify_content: Some(JustifyContent::CENTER),
118 padding: Rect {
119 left: length(20.0_f32),
120 right: length(20.0_f32),
121 top: length(20.0_f32),
122 bottom: length(20.0_f32),
123 },
124 ..Default::default()
125 })
126 .fill(theme.bg_app)
127 .children(vec![panel])
128 }
129}
130
131fn main() {
132 llimphi_ui::run::<Showcase>();
133}