refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel

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>
This commit is contained in:
Sergio
2026-06-18 14:40:00 +00:00
parent e74800d9da
commit ccab39f140
202 changed files with 44034 additions and 1811 deletions
+196 -1
View File
@@ -37,13 +37,15 @@
#![forbid(unsafe_code)]
use std::sync::Arc;
use llimphi_ui::llimphi_layout::taffy::{
prelude::{length, percent, FlexDirection, Size, Style},
AlignItems, Rect,
};
use llimphi_ui::llimphi_raster::peniko::Color;
use llimphi_ui::llimphi_text::Alignment;
use llimphi_ui::View;
use llimphi_ui::{DragPhase, View};
/// Paleta de la lista. Los defaults son una variante dark con selección
/// azulada — equivalente conceptual a `nahual_theme` en su tema oscuro.
@@ -53,6 +55,11 @@ pub struct ListPalette {
pub bg_selected: Color,
pub fg_text: Color,
pub fg_muted: Color,
/// Resalte de la fila destino mientras un drag de reorder pasa por
/// encima. Sólo se usa en [`reorderable_list_view`]. Default = accent
/// translúcido (40 %) sobre `bg_selected` — distinguible del hover y
/// la selección estable.
pub bg_drop_hover: Color,
}
impl Default for ListPalette {
@@ -64,11 +71,17 @@ impl Default for ListPalette {
impl ListPalette {
/// Construye la paleta desde un `Theme` semántico.
pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
// Resalte de drop = accent del theme con 40 % de opacidad
// multiplicada — gana sobre `bg_selected` por luminancia para que
// un drag sobre una fila ya seleccionada se note.
let mut drop = t.accent;
drop.components[3] *= 0.40;
Self {
bg_panel: t.bg_panel,
bg_selected: t.bg_selected,
fg_text: t.fg_text,
fg_muted: t.fg_muted,
bg_drop_hover: drop,
}
}
}
@@ -197,5 +210,187 @@ fn row_view<Msg: Clone + 'static>(row: ListRow<Msg>, height: f32, palette: &List
})
.fill(bg)
.text_aligned(row.label, 12.0, palette.fg_text, Alignment::Start)
// Labels largos terminan en `…` (single-line) en vez de cortarse seco.
.ellipsis(1)
.on_click(row.on_click)
}
/// Función que el caller usa para reaccionar a un reorder. Recibe `(from,
/// to)` — índices en `rows` — y devuelve el `Msg` a despachar (o `None`
/// para ignorar el drop, p. ej. si `from == to`).
pub type ReorderFn<Msg> = Arc<dyn Fn(usize, usize) -> Option<Msg> + Send + Sync>;
/// Una fila para [`reorderable_list_view`]. Pesa más que [`ListRow`]
/// porque cada fila acepta `on_click` opcional y siempre lleva drag
/// handle al borde izquierdo (gripper `⋮⋮` en `fg_muted`) — convención
/// kanban/Trello/Flutter `ReorderableListView`.
pub struct ReorderableListRow<Msg> {
pub label: String,
pub selected: bool,
pub on_click: Option<Msg>,
}
/// Especificación de una lista reordenable por drag&drop (Bloque 14 de
/// PARIDAD-FLUTTER, sigue Tier 5). Cada fila lleva un gripper a la
/// izquierda; arrastrar una fila y soltarla sobre otra emite
/// `on_reorder(from, to)`. La fila destino se ilumina con
/// `palette.bg_drop_hover` mientras el cursor está sobre ella durante el
/// drag.
pub struct ReorderableListSpec<Msg> {
pub rows: Vec<ReorderableListRow<Msg>>,
pub caption: Option<String>,
pub row_height: f32,
pub palette: ListPalette,
pub on_reorder: ReorderFn<Msg>,
}
/// Compone una lista reordenable (Bloque 14). Patrón: cada fila exhibe
/// un gripper al borde izquierdo y es `draggable` con `payload = idx`;
/// la **fila entera** (no sólo el gripper) recibe drops con `on_drop` y
/// `drop_hover_fill`. El handler `on_reorder(from, to)` cae al caller
/// que decide qué `Msg` despachar — el widget no muta nada por sí solo.
///
/// Composición pura sobre los primitives `drag_payload` / `on_drop` /
/// `drop_hover_fill` / `draggable` de `llimphi-ui` (ver `tiled` que
/// reordena paneles bajo el mismo idiom).
pub fn reorderable_list_view<Msg>(spec: ReorderableListSpec<Msg>) -> View<Msg>
where
Msg: Clone + Send + Sync + 'static,
{
let ReorderableListSpec {
rows,
caption,
row_height,
palette,
on_reorder,
} = spec;
let mut children: Vec<View<Msg>> = Vec::with_capacity(rows.len() + 1);
if let Some(text) = caption {
children.push(
View::new(Style {
size: Size {
width: percent(1.0_f32),
height: length(20.0_f32),
},
padding: Rect {
left: length(10.0_f32),
right: length(10.0_f32),
top: length(0.0_f32),
bottom: length(0.0_f32),
},
align_items: Some(AlignItems::Center),
..Default::default()
})
.text_aligned(text, 10.0, palette.fg_muted, Alignment::Start),
);
}
for (idx, row) in rows.into_iter().enumerate() {
children.push(reorderable_row_view(idx, row, row_height, &palette, on_reorder.clone()));
}
View::new(Style {
flex_direction: FlexDirection::Column,
size: Size {
width: percent(1.0_f32),
height: percent(1.0_f32),
},
padding: Rect {
left: length(0.0_f32),
right: length(0.0_f32),
top: length(6.0_f32),
bottom: length(6.0_f32),
},
..Default::default()
})
.fill(palette.bg_panel)
.clip(true)
.children(children)
}
fn reorderable_row_view<Msg>(
idx: usize,
row: ReorderableListRow<Msg>,
height: f32,
palette: &ListPalette,
on_reorder: ReorderFn<Msg>,
) -> View<Msg>
where
Msg: Clone + Send + Sync + 'static,
{
let bg = if row.selected {
palette.bg_selected
} else {
palette.bg_panel
};
// Gripper `⋮⋮` al borde izquierdo, en `fg_muted` y arrastrable. El
// drag entrega `payload = idx`. Devolvemos `None` por evento de drag
// (no usamos dx/dy aquí — el destino se decide en el `on_drop` del
// otro nodo).
let gripper = View::new(Style {
size: Size {
width: length(20.0_f32),
height: percent(1.0_f32),
},
align_items: Some(AlignItems::Center),
..Default::default()
})
.text_aligned("⋮⋮", 14.0, palette.fg_muted, Alignment::Center)
.draggable(|_phase: DragPhase, _dx: f32, _dy: f32| None)
.drag_payload(idx as u64)
.cursor(llimphi_ui::Cursor::Grab);
// Etiqueta: ocupa el resto de la fila, con ellipsis y click opcional.
let mut label = View::new(Style {
size: Size {
width: percent(1.0_f32),
height: percent(1.0_f32),
},
align_items: Some(AlignItems::Center),
..Default::default()
})
.text_aligned(row.label, 12.0, palette.fg_text, Alignment::Start)
.ellipsis(1);
if let Some(msg) = row.on_click {
label = label.on_click(msg);
}
// El **row entero** es target de drop. Cuando el cursor pasa por
// encima durante un drag, `drop_hover_fill` lo ilumina. Al soltar,
// emitimos el reorder si from != to.
let to_idx = idx;
let reorder = on_reorder.clone();
View::new(Style {
flex_direction: FlexDirection::Row,
size: Size {
width: percent(1.0_f32),
height: length(height),
},
padding: Rect {
left: length(4.0_f32),
right: length(10.0_f32),
top: length(0.0_f32),
bottom: length(0.0_f32),
},
align_items: Some(AlignItems::Center),
gap: Size {
width: length(6.0_f32),
height: length(0.0_f32),
},
..Default::default()
})
.fill(bg)
.on_drop(move |from: u64| {
let from = from as usize;
if from == to_idx {
None
} else {
(reorder)(from, to_idx)
}
})
.drop_hover_fill(palette.bg_drop_hover)
.children(vec![gripper, label])
}