diff --git a/app-proto/sketch-outline/Cargo.toml b/app-proto/sketch-outline/Cargo.toml index 7d1d608..35d199b 100644 --- a/app-proto/sketch-outline/Cargo.toml +++ b/app-proto/sketch-outline/Cargo.toml @@ -11,6 +11,7 @@ default = ["console_error_panic_hook"] itertools = "0.13.0" js-sys = "0.3.70" nalgebra = "0.33.0" +rustc-hash = "2.0.0" sycamore = "0.9.0-beta.3" # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/app-proto/sketch-outline/src/assembly.rs b/app-proto/sketch-outline/src/assembly.rs index dded2a1..fa7fc3e 100644 --- a/app-proto/sketch-outline/src/assembly.rs +++ b/app-proto/sketch-outline/src/assembly.rs @@ -1,4 +1,5 @@ use nalgebra::DVector; +use rustc_hash::FxHashMap; use sycamore::reactive::Signal; #[derive(Clone, PartialEq)] @@ -6,16 +7,12 @@ pub struct Element { pub id: String, pub label: String, pub color: [f32; 3], - pub rep: DVector, - - /* TO DO */ - // does this belong in the element data? - pub selected: Signal + pub rep: DVector } // a complete, view-independent description of an assembly #[derive(Clone)] pub struct Assembly { // the order of the elements is arbitrary, and it could change at any time - pub elements: Signal> + pub elements: Signal> } \ No newline at end of file diff --git a/app-proto/sketch-outline/src/display.rs b/app-proto/sketch-outline/src/display.rs index 373c857..2d0900d 100644 --- a/app-proto/sketch-outline/src/display.rs +++ b/app-proto/sketch-outline/src/display.rs @@ -102,8 +102,7 @@ pub fn Display() -> View { // change listener let scene_changed = create_signal(true); create_effect(move || { - let elements = state.assembly.elements.get_clone(); - for elt in elements { elt.selected.track(); } + state.selection.track(); scene_changed.set(true); }); @@ -289,17 +288,21 @@ pub fn Display() -> View { // get the assembly let elements = state.assembly.elements.get_clone(); - let element_iter = (&elements).into_iter(); + let element_iter = (&elements).values(); let reps_world: Vec<_> = element_iter.clone().map(|elt| &assembly_to_world * &elt.rep).collect(); let colors: Vec<_> = element_iter.clone().map(|elt| - if elt.selected.get() { + if state.selection.with(|sel| sel.contains(&elt.id)) { elt.color.map(|ch| 0.2 + 0.8*ch) } else { elt.color } ).collect(); let highlights: Vec<_> = element_iter.map(|elt| - if elt.selected.get() { 1.0_f32 } else { HIGHLIGHT } + if state.selection.with(|sel| sel.contains(&elt.id)) { + 1.0_f32 + } else { + HIGHLIGHT + } ).collect(); // set the resolution diff --git a/app-proto/sketch-outline/src/main.rs b/app-proto/sketch-outline/src/main.rs index 06cc43d..0d1b0c7 100644 --- a/app-proto/sketch-outline/src/main.rs +++ b/app-proto/sketch-outline/src/main.rs @@ -3,6 +3,7 @@ mod display; mod outline; use nalgebra::DVector; +use rustc_hash::{FxHashMap, FxHashSet}; use sycamore::prelude::*; use assembly::{Assembly, Element}; @@ -11,40 +12,52 @@ use outline::Outline; #[derive(Clone)] struct AppState { - assembly: Assembly + assembly: Assembly, + selection: Signal> } fn main() { sycamore::render(|| { - provide_context( - AppState { - assembly: Assembly { - elements: create_signal(vec![ - Element { - id: String::from("wing_a"), - label: String::from("Wing A"), - color: [1.00_f32, 0.25_f32, 0.00_f32], - rep: DVector::::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25]), - selected: create_signal(false) - }, - Element { - id: String::from("wing_b"), - label: String::from("Wing B"), - color: [0.00_f32, 0.25_f32, 1.00_f32], - rep: DVector::::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25]), - selected: create_signal(false) - }, - Element { - id: String::from("central"), - label: String::from("Central"), - color: [0.75_f32, 0.75_f32, 0.75_f32], - rep: DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.4, -0.625]), - selected: create_signal(false) - } - ]) + let state = AppState { + assembly: Assembly { + elements: create_signal(FxHashMap::default()) + }, + selection: create_signal(FxHashSet::default()) + }; + state.assembly.elements.update( + |elts| elts.insert( + "wing_a".to_string(), + Element { + id: String::from("wing_a"), + label: String::from("Wing A"), + color: [1.00_f32, 0.25_f32, 0.00_f32], + rep: DVector::::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25]) } - } + ) ); + state.assembly.elements.update( + |elts| elts.insert( + "wing_b".to_string(), + Element { + id: String::from("wing_b"), + label: String::from("Wing B"), + color: [0.00_f32, 0.25_f32, 1.00_f32], + rep: DVector::::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25]) + }, + ) + ); + state.assembly.elements.update( + |elts| elts.insert( + "central".to_string(), + Element { + id: String::from("central"), + label: String::from("Central"), + color: [0.75_f32, 0.75_f32, 0.75_f32], + rep: DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.4, -0.625]) + } + ) + ); + provide_context(state); view! { Outline {} diff --git a/app-proto/sketch-outline/src/outline.rs b/app-proto/sketch-outline/src/outline.rs index 5d650b3..13e506e 100644 --- a/app-proto/sketch-outline/src/outline.rs +++ b/app-proto/sketch-outline/src/outline.rs @@ -6,25 +6,33 @@ use crate::AppState; #[component] pub fn Outline() -> View { - let state = use_context::(); - // sort the elements alphabetically by ID - let elements_sorted = create_memo(move || + let elements_sorted = create_memo(|| { + let state = use_context::(); state.assembly.elements .get_clone() .into_iter() - .sorted_by_key(|elt| elt.id.clone()) + .sorted_by_key(|(id, _)| id.clone()) + .map(|(_, elt)| elt) .collect() - ); + }); view! { ul { Keyed( list=elements_sorted, view=|elt| { - let class = create_memo(move || - if elt.selected.get() { "selected" } else { "" } - ); + let state = use_context::(); + let class = create_memo({ + let id = elt.id.clone(); + move || { + if state.selection.with(|sel| sel.contains(&id)) { + "selected" + } else { + "" + } + } + }); let label = elt.label.clone(); let rep_components = elt.rep.iter().map(|u| { let u_coord = u.to_string().replace("-", "\u{2212}"); @@ -36,13 +44,27 @@ pub fn Outline() -> View { li( class=class.get(), tabindex="0", - on:click=move |_| { - elt.selected.set_fn(|sel| !sel); + on:click={ + let id = elt.id.clone(); + move |_| { + state.selection.update(|sel| { + if !sel.remove(&id) { + sel.insert(id.clone()); + } + }); + } }, - on:keydown=move |event: KeyboardEvent| { - if event.key() == "Enter" { - elt.selected.set_fn(|sel| !sel); - event.prevent_default(); + on:keydown={ + let id = elt.id.clone(); + move |event: KeyboardEvent| { + if event.key() == "Enter" { + state.selection.update(|sel| { + if !sel.remove(&id) { + sel.insert(id.clone()); + } + }); + event.prevent_default(); + } } } ) {