tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 132 miembros, 79 widgets, la galería vuelve y licencia dual
1use super::*;
2
3pub(crate) fn selectable_row(label: String, selected: bool, msg: Msg, theme: &Theme) -> View<Msg> {
4 View::new(Style {
5 size: Size { width: percent(1.0_f32), height: length(30.0_f32) },
6 align_items: Some(AlignItems::CENTER),
7 padding: pad(10.0, 0.0),
8 ..Default::default()
9 })
10 .fill(if selected { theme.bg_selected } else { theme.bg_panel })
11 .radius(6.0)
12 .text(label, 15.0, if selected { theme.fg_text } else { theme.fg_muted })
13 .on_click(msg)
14}
15
16pub(crate) fn section_title(text: &str, theme: &Theme) -> View<Msg> {
17 View::new(Style {
18 size: Size { width: percent(1.0_f32), height: length(20.0_f32) },
19 ..Default::default()
20 })
21 .text(text.to_string(), 12.0, theme.accent)
22 .bold()
23}
24
25pub(crate) fn body_text(s: String, color: Color, _theme: &Theme) -> View<Msg> {
26 View::new(Style {
27 size: Size { width: percent(1.0_f32), height: Dimension::auto() },
28 ..Default::default()
29 })
30 .text(s, 13.0, color)
31 .max_lines(2)
32}
33
34pub(crate) fn spacer(h: f32) -> View<Msg> {
35 View::new(Style {
36 size: Size { width: percent(1.0_f32), height: length(h) },
37 ..Default::default()
38 })
39}
40
41pub(crate) fn pad(
42 x: f32,
43 y: f32,
44) -> llimphi_ui::llimphi_layout::taffy::prelude::Rect<llimphi_ui::llimphi_layout::taffy::prelude::LengthPercentage> {
45 use llimphi_ui::llimphi_layout::taffy::prelude::Rect;
46 Rect { left: length(x), right: length(x), top: length(y), bottom: length(y) }
47}
48
49pub(crate) fn gap_y(h: f32) -> Size<llimphi_ui::llimphi_layout::taffy::prelude::LengthPercentage> {
50 Size { width: length(0.0_f32), height: length(h) }
51}
52
53// =============================================================================
54// Persistencia RON
55// =============================================================================
56
57// Guardar/cargar el proyecto en el path por defecto usan el mismo `Documento`
58// (con marca + versión) que abrir/guardar-como/combinar — un solo formato.
59pub(crate) fn save_project(project: &Project) -> Result<(), String> {
60 Documento::nuevo(project.clone())
61 .guardar(PROJECT_PATH)
62 .map_err(|e| e.to_string())
63}
64
65pub(crate) fn load_project() -> Result<Project, String> {
66 Documento::abrir(PROJECT_PATH)
67 .map(|d| d.proyecto)
68 .map_err(|e| e.to_string())
69}
70
71/// Estado de arranque (sin `Handle`) — reusado por `init` y por el pantallazo.
72pub(crate) fn demo_model() -> Model {
73 let project = Project::starter();
74 // Orientación de los dientes (rail Adentro/Afuera) heredada del SO.
75 let dientes_outside = wawa_config::WawaConfig::load().dientes_outside;
76 let mut model = Model {
77 theme: Theme::dark(),
78 level: Level::Mundos,
79 sel: [0; 7],
80 tool_tab: 0,
81 gen: 1,
82 yaw: 35_f32.to_radians(),
83 pitch: 26_f32.to_radians(),
84 dist: default_dist(),
85 preview: Arc::new(Mutex::new(None)),
86 mapa: None,
87 status: "creador de mundos por niveles".into(),
88 name_input: TextInputState::new(),
89 name_focused: false,
90 seed_input: TextInputState::new(),
91 seed_focused: false,
92 ai_input: TextInputState::new(),
93 ai_focused: false,
94 ai_busy: false,
95 file_input: {
96 let mut t = TextInputState::new();
97 t.set_text(PROJECT_PATH.to_string());
98 t
99 },
100 file_focused: false,
101 historia: Historia::abrir(HISTORY_PATH).unwrap_or_default(),
102 jugando: false,
103 jugador: Arc::new(Mutex::new(Player::new(Vec3::ZERO))),
104 input_juego: InputJuego::default(),
105 volar: false,
106 respawn_jugador: Arc::new(Mutex::new(false)),
107 time: 0.0,
108 playing: false,
109 script_cam: true,
110 exporting: false,
111 simulating: false,
112 andar_estado: 1, // caminar
113 manada: false,
114 actor_sel: 0,
115 utileria_sel: 0,
116 decorado_sel: 0,
117 key_sel: 0,
118 rng: 0x1234_5678,
119 mueble_brush: Mueble::Mesa,
120 mueble_facing: 0,
121 pueblo_brush: PuebloTipo::Aldea,
122 // Los dos sidebars unificados: selectores fijos, abiertos sobre el nivel/tool
123 // activos. El eje "Rail Adentro/Afuera" hereda el viejo `dientes_outside` del
124 // SO. El izquierdo lista items (más angosto); el derecho, el editor de tools.
125 sidebar_left: {
126 let mut s = RagSidebarState {
127 open: Some(Level::Mundos.index() as u64),
128 selected: Some(Level::Mundos.index() as u64),
129 panel_w: 214.0,
130 rail_outside: dientes_outside,
131 ..Default::default()
132 };
133 // El ancho al que quedó el divisor la última vez (si lo hubo).
134 llimphi_widget_rag_sidebar::persistencia::restaurar(&mut s, "voxel-studio", "izq");
135 s
136 },
137 sidebar_right: {
138 let mut s = RagSidebarState {
139 open: Some(0),
140 selected: Some(0),
141 panel_w: 256.0,
142 rail_outside: dientes_outside,
143 ..Default::default()
144 };
145 llimphi_widget_rag_sidebar::persistencia::restaurar(&mut s, "voxel-studio", "der");
146 s
147 },
148 lab: None,
149 lab_gen: 1,
150 formula_input: TextInputState::new(),
151 formula_focused: false,
152 formula_target: 0,
153 cond_formula_input: TextInputState::new(),
154 cond_formula_focused: false,
155 cond_formula_target: 0,
156 leyes_node_mode: false,
157 eq_graph: None,
158 eq_graph_gen: 0,
159 clipboard: SystemClipboard::new(),
160 project,
161 };
162 reselect_all(&mut model);
163 sync_inputs(&mut model);
164 model
165}
166