ccab39f140
Re-sincroniza las fuentes desde el monorepo (estaba en vello 0.5/wgpu 24 y con la estructura vieja de eventloop) y suma el 3D: - bump del workspace a vello 0.7 / wgpu 27 / parley 0.6, + accesskit 0.24 / accesskit_winit 0.33 / vello_hybrid 0.0.9. - nuevos crates: llimphi-3d (voxels ray-march + mallas en un depth compartido, montable dentro de un View 2D vía set_viewport+scissor) y llimphi-voxel (world-gen, personajes, director de escenas) + shared/foreign-vox (puente .vox). - README: sección "Not just 2D — a 3D voxel engine" + GIF (docs/llimphi_voxel.gif). - excluido modules/allichay (arrastra deps fuera del alcance del front-door). - cargo check --workspace: verde. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
269 lines
9.0 KiB
Rust
269 lines
9.0 KiB
Rust
//! Demo del renderizador `allichay`: una config de juguete editable.
|
|
//!
|
|
//! Ejercita el módulo sin levantar ninguna app de dominio: un `DemoConfig`
|
|
//! implementa [`allichay::Configurable`], el renderizador lo pinta con dientes
|
|
//! y controles, y cada cambio se aplica + se loguea por consola.
|
|
//!
|
|
//! ```bash
|
|
//! cargo run -p llimphi-module-allichay --example settings_demo --release
|
|
//! ```
|
|
|
|
use allichay::{Column, Configurable, EnumOption, Field, FieldPath, FieldValue, Schema, Section};
|
|
use llimphi_module_allichay::{allichay_view, AllichayMsg, AllichayState};
|
|
use llimphi_theme::Theme;
|
|
use llimphi_ui::llimphi_layout::taffy::prelude::{percent, Size, Style};
|
|
use llimphi_ui::{App, Handle, KeyEvent, View};
|
|
|
|
/// Config de juguete con un campo de cada tipo.
|
|
#[derive(Clone)]
|
|
struct DemoConfig {
|
|
oscuro: bool,
|
|
gap: f64,
|
|
columnas: i64,
|
|
idioma: String,
|
|
acento: [u8; 4],
|
|
nombre: String,
|
|
auto: bool,
|
|
/// Lista de textos (ejercita [`Control::List`]).
|
|
rutas: Vec<String>,
|
|
/// Tabla (etiqueta, comando) — ejercita [`Control::Table`].
|
|
accesos: Vec<(String, String)>,
|
|
}
|
|
|
|
impl Default for DemoConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
oscuro: true,
|
|
gap: 8.0,
|
|
columnas: 2,
|
|
idioma: "es-PE".into(),
|
|
acento: [92, 143, 235, 255],
|
|
nombre: "mi escritorio".into(),
|
|
auto: false,
|
|
rutas: vec!["~/proyectos".into(), "/usr/share".into()],
|
|
accesos: vec![
|
|
("Editor".into(), "nada".into()),
|
|
("Terminal".into(), "alacritty".into()),
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Configurable for DemoConfig {
|
|
fn schema(&self) -> Schema {
|
|
Schema::new()
|
|
.section(
|
|
Section::new("apariencia", "Apariencia")
|
|
.icon("◐")
|
|
.help("Cómo se ve el escritorio")
|
|
.field(Field::toggle("oscuro", "Modo oscuro", self.oscuro))
|
|
.field(
|
|
Field::slider("gap", "Margen entre ventanas", self.gap, 0.0, 32.0, 1.0)
|
|
.help("En píxeles"),
|
|
)
|
|
.field(Field::color("acento", "Color de acento", self.acento))
|
|
.subsection(
|
|
Section::new("teselado", "Teselado").field(Field::slider_int(
|
|
"columnas",
|
|
"Columnas",
|
|
self.columnas,
|
|
1,
|
|
6,
|
|
)),
|
|
),
|
|
)
|
|
.section(
|
|
Section::new("general", "General")
|
|
.icon("≡")
|
|
.field(Field::dropdown(
|
|
"idioma",
|
|
"Idioma",
|
|
self.idioma.clone(),
|
|
vec![
|
|
EnumOption::new("es-PE", "Español"),
|
|
EnumOption::new("en-US", "English"),
|
|
EnumOption::new("qu-PE", "Runasimi"),
|
|
],
|
|
))
|
|
.field(Field::text("nombre", "Nombre del equipo", self.nombre.clone()))
|
|
.field(Field::toggle("auto", "Arrancar al inicio", self.auto)),
|
|
)
|
|
.section(
|
|
Section::new("agregados", "Listas y tablas")
|
|
.icon("≣")
|
|
.help("Los controles v2")
|
|
.field(Field::list(
|
|
"rutas",
|
|
"Rutas de búsqueda",
|
|
self.rutas.clone(),
|
|
"ruta",
|
|
))
|
|
.field(Field::table(
|
|
"accesos",
|
|
"Accesos directos",
|
|
vec![Column::new("label", "Nombre"), Column::new("cmd", "Comando")],
|
|
self.accesos
|
|
.iter()
|
|
.map(|(l, c)| vec![l.clone(), c.clone()])
|
|
.collect(),
|
|
)),
|
|
)
|
|
}
|
|
|
|
fn apply(
|
|
&mut self,
|
|
path: &FieldPath,
|
|
value: FieldValue,
|
|
) -> Result<(), allichay::AllichayError> {
|
|
match path.leaf() {
|
|
Some("oscuro") => self.oscuro = value.as_bool().unwrap_or(self.oscuro),
|
|
Some("gap") => self.gap = value.as_float().unwrap_or(self.gap),
|
|
Some("columnas") => self.columnas = value.as_int().unwrap_or(self.columnas),
|
|
Some("idioma") => {
|
|
if let Some(s) = value.as_str() {
|
|
self.idioma = s.to_string();
|
|
}
|
|
}
|
|
Some("acento") => self.acento = value.as_color().unwrap_or(self.acento),
|
|
Some("nombre") => {
|
|
if let Some(s) = value.as_str() {
|
|
self.nombre = s.to_string();
|
|
}
|
|
}
|
|
Some("auto") => self.auto = value.as_bool().unwrap_or(self.auto),
|
|
Some("rutas") => {
|
|
if let Some(items) = value.as_list() {
|
|
self.rutas = items.to_vec();
|
|
}
|
|
}
|
|
Some("accesos") => {
|
|
if let Some(rows) = value.as_table() {
|
|
self.accesos = rows
|
|
.iter()
|
|
.map(|r| {
|
|
(
|
|
r.first().cloned().unwrap_or_default(),
|
|
r.get(1).cloned().unwrap_or_default(),
|
|
)
|
|
})
|
|
.collect();
|
|
}
|
|
}
|
|
other => {
|
|
return Err(allichay::AllichayError::UnknownPath(
|
|
other.unwrap_or("").into(),
|
|
))
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct Model {
|
|
cfg: DemoConfig,
|
|
state: AllichayState,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum Msg {
|
|
Allichay(AllichayMsg),
|
|
Key(KeyEvent),
|
|
}
|
|
|
|
struct Demo;
|
|
|
|
impl App for Demo {
|
|
type Model = Model;
|
|
type Msg = Msg;
|
|
|
|
fn title() -> &'static str {
|
|
"allichay · demo"
|
|
}
|
|
|
|
fn initial_size() -> (u32, u32) {
|
|
(760, 560)
|
|
}
|
|
|
|
fn init(_handle: &Handle<Msg>) -> Model {
|
|
Model {
|
|
cfg: DemoConfig::default(),
|
|
state: AllichayState::new(),
|
|
}
|
|
}
|
|
|
|
fn update(model: Model, msg: Msg, _handle: &Handle<Msg>) -> Model {
|
|
let mut m = model;
|
|
match msg {
|
|
Msg::Allichay(AllichayMsg::SelectSection(i)) => m.state.select(i),
|
|
Msg::Allichay(AllichayMsg::Focus(path)) => {
|
|
// Sembrar el buffer con el valor actual del campo.
|
|
let seed = m
|
|
.cfg
|
|
.schema()
|
|
.find_field(&path)
|
|
.and_then(|f| f.value.as_str().map(str::to_string))
|
|
.unwrap_or_default();
|
|
m.state.focus(&path, &seed);
|
|
}
|
|
Msg::Allichay(AllichayMsg::FocusCell(path, row, col)) => {
|
|
// El estado siembra el buffer leyendo la celda del valor actual.
|
|
if let Some(f) = m.cfg.schema().find_field(&path) {
|
|
m.state.focus_cell(&path, f.value.clone(), row, col);
|
|
}
|
|
}
|
|
Msg::Allichay(AllichayMsg::FocusHex(path)) => {
|
|
let seed = m
|
|
.cfg
|
|
.schema()
|
|
.find_field(&path)
|
|
.and_then(|f| f.value.as_color())
|
|
.map(llimphi_module_allichay::color_hex)
|
|
.unwrap_or_default();
|
|
m.state.focus_hex(&path, &seed);
|
|
}
|
|
Msg::Allichay(AllichayMsg::Change(path, value)) => {
|
|
println!("cambio: {path} = {value:?}");
|
|
if let Err(e) = m.cfg.apply(&path, value) {
|
|
eprintln!(" error: {e}");
|
|
}
|
|
}
|
|
Msg::Allichay(AllichayMsg::ScrollTo(offset)) => m.state.set_scroll(offset),
|
|
Msg::Key(event) => {
|
|
if let Some((path, value)) = m.state.apply_key(&event) {
|
|
println!("texto: {path} = {value:?}");
|
|
let _ = m.cfg.apply(&path, value);
|
|
}
|
|
}
|
|
}
|
|
m
|
|
}
|
|
|
|
fn on_key(model: &Model, event: &KeyEvent) -> Option<Msg> {
|
|
// Sólo capturamos teclas cuando hay un campo de texto en edición.
|
|
if model.state.is_editing() {
|
|
Some(Msg::Key(event.clone()))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn view(model: &Model) -> View<Msg> {
|
|
let theme = Theme::dark();
|
|
let schema = model.cfg.schema();
|
|
let body = allichay_view(&schema, &model.state, &theme, Msg::Allichay);
|
|
View::new(Style {
|
|
size: Size {
|
|
width: percent(1.0_f32),
|
|
height: percent(1.0_f32),
|
|
},
|
|
..Default::default()
|
|
})
|
|
.fill(theme.bg_app)
|
|
.children(vec![body])
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
llimphi_ui::run::<Demo>();
|
|
}
|