feat: llimphi standalone — framework UI soberano extraído del monorepo
Motor gráfico Llimphi como workspace independiente: bucle Elm (input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley. Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets + módulos, sin dependencias al resto del monorepo. cargo check --workspace pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
//! Showcase de `llimphi-widget-nodegraph`. Cuatro nodos pre-conectados
|
||||
//! representando una cadena de audio (`Source → Filter → Mixer →
|
||||
//! Output`) y un `LFO` huérfano para que el usuario lo conecte
|
||||
//! arrastrando desde su pin de salida hasta el `mod` del filtro.
|
||||
//!
|
||||
//! - Arrastrá la title bar de cualquier nodo para moverlo.
|
||||
//! - Arrastrá desde un pin de salida (lado derecho) y soltá sobre un
|
||||
//! pin de entrada (lado izquierdo) de otro nodo para conectar.
|
||||
//!
|
||||
//! Corré con: `cargo run -p llimphi-widget-nodegraph --example
|
||||
//! nodegraph_demo --release`.
|
||||
|
||||
use llimphi_theme::Theme;
|
||||
use llimphi_ui::{App, DragPhase, Handle, View};
|
||||
use llimphi_widget_nodegraph::{
|
||||
nodegraph_view, NodeId, NodeSpec, NodegraphMetrics, NodegraphPalette, PinIdx, Wire,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Msg {
|
||||
DragNode {
|
||||
id: NodeId,
|
||||
// El demo no diferencia Move/End; lo dejamos en el Msg por si
|
||||
// un caller real quiere persistir layout solo en End.
|
||||
#[allow(dead_code)]
|
||||
phase: DragPhase,
|
||||
dx: f32,
|
||||
dy: f32,
|
||||
},
|
||||
Connect {
|
||||
from_node: NodeId,
|
||||
from_pin: PinIdx,
|
||||
to_node: NodeId,
|
||||
to_pin: PinIdx,
|
||||
},
|
||||
}
|
||||
|
||||
struct Model {
|
||||
nodes: Vec<NodeSpec>,
|
||||
wires: Vec<Wire>,
|
||||
}
|
||||
|
||||
const ID_SOURCE: NodeId = 1;
|
||||
const ID_FILTER: NodeId = 2;
|
||||
const ID_MIXER: NodeId = 3;
|
||||
const ID_OUTPUT: NodeId = 4;
|
||||
const ID_LFO: NodeId = 5;
|
||||
|
||||
struct Showcase;
|
||||
|
||||
impl App for Showcase {
|
||||
type Model = Model;
|
||||
type Msg = Msg;
|
||||
|
||||
fn title() -> &'static str {
|
||||
"llimphi · nodegraph showcase (drag títulos, arrastrá pin → pin)"
|
||||
}
|
||||
|
||||
fn initial_size() -> (u32, u32) {
|
||||
(1100, 720)
|
||||
}
|
||||
|
||||
fn init(_: &Handle<Msg>) -> Model {
|
||||
Model {
|
||||
nodes: vec![
|
||||
NodeSpec {
|
||||
id: ID_SOURCE,
|
||||
label: "Source".into(),
|
||||
x: 60.0,
|
||||
y: 80.0,
|
||||
inputs: vec![],
|
||||
outputs: vec!["out".into()],
|
||||
},
|
||||
NodeSpec {
|
||||
id: ID_FILTER,
|
||||
label: "Filter".into(),
|
||||
x: 290.0,
|
||||
y: 80.0,
|
||||
inputs: vec!["in".into(), "mod".into()],
|
||||
outputs: vec!["out".into()],
|
||||
},
|
||||
NodeSpec {
|
||||
id: ID_MIXER,
|
||||
label: "Mixer".into(),
|
||||
x: 520.0,
|
||||
y: 80.0,
|
||||
inputs: vec!["a".into(), "b".into()],
|
||||
outputs: vec!["out".into()],
|
||||
},
|
||||
NodeSpec {
|
||||
id: ID_OUTPUT,
|
||||
label: "Output".into(),
|
||||
x: 750.0,
|
||||
y: 80.0,
|
||||
inputs: vec!["in".into()],
|
||||
outputs: vec![],
|
||||
},
|
||||
NodeSpec {
|
||||
id: ID_LFO,
|
||||
label: "LFO".into(),
|
||||
x: 290.0,
|
||||
y: 260.0,
|
||||
inputs: vec![],
|
||||
outputs: vec!["out".into()],
|
||||
},
|
||||
],
|
||||
wires: vec![
|
||||
Wire {
|
||||
from_node: ID_SOURCE,
|
||||
from_output: 0,
|
||||
to_node: ID_FILTER,
|
||||
to_input: 0,
|
||||
},
|
||||
Wire {
|
||||
from_node: ID_FILTER,
|
||||
from_output: 0,
|
||||
to_node: ID_MIXER,
|
||||
to_input: 0,
|
||||
},
|
||||
Wire {
|
||||
from_node: ID_MIXER,
|
||||
from_output: 0,
|
||||
to_node: ID_OUTPUT,
|
||||
to_input: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn update(model: Model, msg: Msg, _: &Handle<Msg>) -> Model {
|
||||
let mut m = model;
|
||||
match msg {
|
||||
Msg::DragNode { id, phase: _, dx, dy } => {
|
||||
if let Some(n) = m.nodes.iter_mut().find(|n| n.id == id) {
|
||||
n.x += dx;
|
||||
n.y += dy;
|
||||
if n.x < 0.0 {
|
||||
n.x = 0.0;
|
||||
}
|
||||
if n.y < 0.0 {
|
||||
n.y = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::Connect {
|
||||
from_node,
|
||||
from_pin,
|
||||
to_node,
|
||||
to_pin,
|
||||
} => {
|
||||
if from_node == to_node {
|
||||
return m;
|
||||
}
|
||||
let exists = m.wires.iter().any(|w| {
|
||||
w.from_node == from_node
|
||||
&& w.from_output == from_pin
|
||||
&& w.to_node == to_node
|
||||
&& w.to_input == to_pin
|
||||
});
|
||||
if !exists {
|
||||
m.wires.push(Wire {
|
||||
from_node,
|
||||
from_output: from_pin,
|
||||
to_node,
|
||||
to_input: to_pin,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
fn view(model: &Model) -> View<Msg> {
|
||||
let theme = Theme::dark();
|
||||
let palette = NodegraphPalette::from_theme(&theme);
|
||||
let metrics = NodegraphMetrics::default();
|
||||
nodegraph_view(
|
||||
&model.nodes,
|
||||
&model.wires,
|
||||
&palette,
|
||||
&metrics,
|
||||
|id, phase, dx, dy| Some(Msg::DragNode { id, phase, dx, dy }),
|
||||
|from_node, from_pin, to_node, to_pin| {
|
||||
Some(Msg::Connect {
|
||||
from_node,
|
||||
from_pin,
|
||||
to_node,
|
||||
to_pin,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
llimphi_ui::run::<Showcase>();
|
||||
}
|
||||
Reference in New Issue
Block a user