diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml
index e5bc05e..e623b26 100644
--- a/app-proto/Cargo.toml
+++ b/app-proto/Cargo.toml
@@ -1,7 +1,7 @@
[package]
-name = "sketch-outline"
+name = "dyna3"
version = "0.1.0"
-authors = ["Aaron"]
+authors = ["Aaron Fenyes", "Glen Whitney"]
edition = "2021"
[features]
diff --git a/app-proto/index.html b/app-proto/index.html
index 5474fe9..92238f4 100644
--- a/app-proto/index.html
+++ b/app-proto/index.html
@@ -2,8 +2,10 @@
- Sketch outline
+ dyna3
+
+
diff --git a/app-proto/main.css b/app-proto/main.css
index 32ae5bf..b9fc0a1 100644
--- a/app-proto/main.css
+++ b/app-proto/main.css
@@ -1,7 +1,20 @@
+:root {
+ --text: #fcfcfc; /* almost white */
+ --text-bright: white;
+ --text-invalid: #f58fc2; /* bright pink */
+ --border: #555; /* light gray */
+ --border-focus: #aaa; /* bright gray */
+ --border-invalid: #70495c; /* dusky pink */
+ --selection-highlight: #444; /* medium gray */
+ --page-background: #222; /* dark gray */
+ --display-background: #020202; /* almost black */
+}
+
body {
margin: 0px;
- color: #fcfcfc;
- background-color: #222;
+ color: var(--text);
+ background-color: var(--page-background);
+ font-family: 'Fira Sans', sans-serif;
}
/* sidebar */
@@ -16,7 +29,7 @@ body {
padding: 0px;
border-width: 0px 1px 0px 0px;
border-style: solid;
- border-color: #555;
+ border-color: var(--border);
}
/* add-remove */
@@ -33,6 +46,15 @@ body {
font-size: large;
}
+/* KLUDGE */
+/*
+ for convenience, we're using emoji as temporary icons for some buttons. these
+ buttons need to be displayed in an emoji font
+*/
+#add-remove > button.emoji {
+ font-family: 'Noto Emoji', sans-serif;
+}
+
/* outline */
#outline {
@@ -51,81 +73,103 @@ summary {
}
summary.selected {
- color: #fff;
- background-color: #444;
+ color: var(--text-bright);
+ background-color: var(--selection-highlight);
}
-summary > div, .cst {
+summary > div, .constraint {
padding-top: 4px;
padding-bottom: 4px;
}
-.elt, .cst {
+.element, .constraint {
display: flex;
flex-grow: 1;
padding-left: 8px;
padding-right: 8px;
}
-.elt-switch {
+.element-switch {
width: 18px;
padding-left: 2px;
text-align: center;
}
-details:has(li) .elt-switch::after {
+details:has(li) .element-switch::after {
content: '▸';
}
-details[open]:has(li) .elt-switch::after {
+details[open]:has(li) .element-switch::after {
content: '▾';
}
-.elt-label {
+.element-label {
flex-grow: 1;
}
-.cst-label {
+.constraint-label {
flex-grow: 1;
}
-.elt-rep {
+.element-representation {
display: flex;
}
-.elt-rep > div {
+.element-representation > div {
padding: 2px 0px 0px 0px;
font-size: 10pt;
- text-align: center;
+ font-variant-numeric: tabular-nums;
+ text-align: right;
width: 56px;
}
-.cst {
+.constraint {
font-style: italic;
}
-.cst > input[type=checkbox] {
+.constraint.invalid {
+ color: var(--text-invalid);
+}
+
+.constraint > input[type=checkbox] {
margin: 0px 8px 0px 0px;
}
-.cst > input[type=text] {
- color: #fcfcfc;
+.constraint > input[type=text] {
+ color: inherit;
background-color: inherit;
- border: 1px solid #555;
+ border: 1px solid var(--border);
border-radius: 2px;
}
+.constraint.invalid > input[type=text] {
+ border-color: var(--border-invalid);
+}
+
+.status {
+ width: 20px;
+ padding-left: 4px;
+ text-align: center;
+ font-family: 'Noto Emoji';
+ font-style: normal;
+}
+
+.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
+ content: '⚠';
+ color: var(--text-invalid);
+}
+
/* display */
canvas {
float: left;
margin-left: 20px;
margin-top: 20px;
- background-color: #020202;
- border: 1px solid #555;
+ background-color: var(--display-background);
+ border: 1px solid var(--border);
border-radius: 16px;
}
canvas:focus {
- border-color: #aaa;
+ border-color: var(--border-focus);
}
\ No newline at end of file
diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs
index 19b4b8d..ba02e65 100644
--- a/app-proto/src/add_remove.rs
+++ b/app-proto/src/add_remove.rs
@@ -1,155 +1,130 @@
-use std::collections::BTreeSet; /* DEBUG */
use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue};
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}};
/* DEBUG */
+// load an example assembly for testing. this code will be removed once we've
+// built a more formal test assembly system
fn load_gen_assemb(assembly: &Assembly) {
let _ = assembly.try_insert_element(
- Element {
- id: String::from("gemini_a"),
- label: String::from("Castor"),
- color: [1.00_f32, 0.25_f32, 0.00_f32],
- representation: engine::sphere(0.5, 0.5, 0.0, 1.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("gemini_a"),
+ String::from("Castor"),
+ [1.00_f32, 0.25_f32, 0.00_f32],
+ engine::sphere(0.5, 0.5, 0.0, 1.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("gemini_b"),
- label: String::from("Pollux"),
- color: [0.00_f32, 0.25_f32, 1.00_f32],
- representation: engine::sphere(-0.5, -0.5, 0.0, 1.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("gemini_b"),
+ String::from("Pollux"),
+ [0.00_f32, 0.25_f32, 1.00_f32],
+ engine::sphere(-0.5, -0.5, 0.0, 1.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("ursa_major"),
- label: String::from("Ursa major"),
- color: [0.25_f32, 0.00_f32, 1.00_f32],
- representation: engine::sphere(-0.5, 0.5, 0.0, 0.75),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("ursa_major"),
+ String::from("Ursa major"),
+ [0.25_f32, 0.00_f32, 1.00_f32],
+ engine::sphere(-0.5, 0.5, 0.0, 0.75)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("ursa_minor"),
- label: String::from("Ursa minor"),
- color: [0.25_f32, 1.00_f32, 0.00_f32],
- representation: engine::sphere(0.5, -0.5, 0.0, 0.5),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("ursa_minor"),
+ String::from("Ursa minor"),
+ [0.25_f32, 1.00_f32, 0.00_f32],
+ engine::sphere(0.5, -0.5, 0.0, 0.5)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("moon_deimos"),
- label: String::from("Deimos"),
- color: [0.75_f32, 0.75_f32, 0.00_f32],
- representation: engine::sphere(0.0, 0.15, 1.0, 0.25),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("moon_deimos"),
+ String::from("Deimos"),
+ [0.75_f32, 0.75_f32, 0.00_f32],
+ engine::sphere(0.0, 0.15, 1.0, 0.25)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("moon_phobos"),
- label: String::from("Phobos"),
- color: [0.00_f32, 0.75_f32, 0.50_f32],
- representation: engine::sphere(0.0, -0.15, -1.0, 0.25),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("moon_phobos"),
+ String::from("Phobos"),
+ [0.00_f32, 0.75_f32, 0.50_f32],
+ engine::sphere(0.0, -0.15, -1.0, 0.25)
+ )
);
}
/* DEBUG */
+// load an example assembly for testing. this code will be removed once we've
+// built a more formal test assembly system
fn load_low_curv_assemb(assembly: &Assembly) {
let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element(
- Element {
- id: "central".to_string(),
- label: "Central".to_string(),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: engine::sphere(0.0, 0.0, 0.0, 1.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "central".to_string(),
+ "Central".to_string(),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ engine::sphere(0.0, 0.0, 0.0, 1.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "assemb_plane".to_string(),
- label: "Assembly plane".to_string(),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "assemb_plane".to_string(),
+ "Assembly plane".to_string(),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "side1".to_string(),
- label: "Side 1".to_string(),
- color: [1.00_f32, 0.00_f32, 0.25_f32],
- representation: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "side1".to_string(),
+ "Side 1".to_string(),
+ [1.00_f32, 0.00_f32, 0.25_f32],
+ engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "side2".to_string(),
- label: "Side 2".to_string(),
- color: [0.25_f32, 1.00_f32, 0.00_f32],
- representation: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "side2".to_string(),
+ "Side 2".to_string(),
+ [0.25_f32, 1.00_f32, 0.00_f32],
+ engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "side3".to_string(),
- label: "Side 3".to_string(),
- color: [0.00_f32, 0.25_f32, 1.00_f32],
- representation: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "side3".to_string(),
+ "Side 3".to_string(),
+ [0.00_f32, 0.25_f32, 1.00_f32],
+ engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "corner1".to_string(),
- label: "Corner 1".to_string(),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "corner1".to_string(),
+ "Corner 1".to_string(),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: "corner2".to_string(),
- label: "Corner 2".to_string(),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ "corner2".to_string(),
+ "Corner 2".to_string(),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)
+ )
);
let _ = assembly.try_insert_element(
- Element {
- id: String::from("corner3"),
- label: String::from("Corner 3"),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ String::from("corner3"),
+ String::from("Corner 3"),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0)
+ )
);
}
@@ -202,6 +177,7 @@ pub fn AddRemove() -> View {
}
) { "+" }
button(
+ class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
disabled={
let state = use_context::();
state.selection.with(|sel| sel.len() != 2)
@@ -215,15 +191,15 @@ pub fn AddRemove() -> View {
}
);
let lorentz_prod = create_signal(0.0);
+ let lorentz_prod_valid = create_signal(false);
let active = create_signal(true);
state.assembly.insert_constraint(Constraint {
subjects: subjects,
lorentz_prod: lorentz_prod,
lorentz_prod_text: create_signal(String::new()),
- lorentz_prod_valid: create_signal(false),
+ lorentz_prod_valid: lorentz_prod_valid,
active: active,
});
- state.assembly.realize();
state.selection.update(|sel| sel.clear());
/* DEBUG */
@@ -241,23 +217,23 @@ pub fn AddRemove() -> View {
}
});
- // update the realization when the constraint activated, or
- // edited while active
+ // update the realization when the constraint becomes active
+ // and valid, or is edited while active and valid
create_effect(move || {
+ console::log_1(&JsValue::from(
+ format!("Constraint ({}, {}) updated", subjects.0, subjects.1)
+ ));
lorentz_prod.track();
- console::log_2(
- &JsValue::from("Lorentz product updated to"),
- &JsValue::from(lorentz_prod.get_untracked())
- );
- if active.get() {
+ if active.get() && lorentz_prod_valid.get() {
state.assembly.realize();
}
});
}
) { "🔗" }
- select(bind:value=assembly_name) { /* DEBUG */
+ select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
option(value="general") { "General" }
option(value="low-curv") { "Low-curvature" }
+ option(value="empty") { "Empty" }
}
}
}
diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs
index 0cdf61b..35b4417 100644
--- a/app-proto/src/assembly.rs
+++ b/app-proto/src/assembly.rs
@@ -18,17 +18,33 @@ pub struct Element {
pub id: String,
pub label: String,
pub color: ElementColor,
- pub representation: DVector,
- pub constraints: BTreeSet,
+ pub representation: Signal>,
+ pub constraints: Signal>,
// the configuration matrix column index that was assigned to this element
// last time the assembly was realized
- /* TO DO */
- // this is public, as a kludge, because `Element` doesn't have a constructor
- // yet. it should be made private as soon as the constructor is written
- pub index: usize
+ column_index: usize
}
+impl Element {
+ pub fn new(
+ id: String,
+ label: String,
+ color: ElementColor,
+ representation: DVector
+ ) -> Element {
+ Element {
+ id: id,
+ label: label,
+ color: color,
+ representation: create_signal(representation),
+ constraints: create_signal(BTreeSet::default()),
+ column_index: 0
+ }
+ }
+}
+
+
#[derive(Clone)]
pub struct Constraint {
pub subjects: (ElementKey, ElementKey),
@@ -92,24 +108,23 @@ impl Assembly {
// create and insert a new element
self.insert_element_unchecked(
- Element {
- id: id,
- label: format!("Sphere {}", id_num),
- color: [0.75_f32, 0.75_f32, 0.75_f32],
- representation: DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
- constraints: BTreeSet::default(),
- index: 0
- }
+ Element::new(
+ id,
+ format!("Sphere {}", id_num),
+ [0.75_f32, 0.75_f32, 0.75_f32],
+ DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5])
+ )
);
}
pub fn insert_constraint(&self, constraint: Constraint) {
let subjects = constraint.subjects;
let key = self.constraints.update(|csts| csts.insert(constraint));
- self.elements.update(|elts| {
- elts[subjects.0].constraints.insert(key);
- elts[subjects.1].constraints.insert(key);
- });
+ let subject_constraints = self.elements.with(
+ |elts| (elts[subjects.0].constraints, elts[subjects.1].constraints)
+ );
+ subject_constraints.0.update(|csts| csts.insert(key));
+ subject_constraints.1.update(|csts| csts.insert(key));
}
// --- realization ---
@@ -118,7 +133,7 @@ impl Assembly {
// index the elements
self.elements.update_silent(|elts| {
for (index, (_, elt)) in elts.into_iter().enumerate() {
- elt.index = index;
+ elt.column_index = index;
}
});
@@ -130,8 +145,8 @@ impl Assembly {
for (_, cst) in csts {
if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() {
let subjects = cst.subjects;
- let row = elts[subjects.0].index;
- let col = elts[subjects.1].index;
+ let row = elts[subjects.0].column_index;
+ let col = elts[subjects.1].column_index;
gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked());
}
}
@@ -141,9 +156,9 @@ impl Assembly {
// Gram matrix
let mut guess_to_be = DMatrix::::zeros(5, elts.len());
for (_, elt) in elts {
- let index = elt.index;
+ let index = elt.column_index;
gram_to_be.push_sym(index, index, 1.0);
- guess_to_be.set_column(index, &elt.representation);
+ guess_to_be.set_column(index, &elt.representation.get_clone_untracked());
}
(gram_to_be, guess_to_be)
@@ -185,11 +200,11 @@ impl Assembly {
if success {
// read out the solution
- self.elements.update(|elts| {
- for (_, elt) in elts.iter_mut() {
- elt.representation.set_column(0, &config.column(elt.index));
- }
- });
+ for (_, elt) in self.elements.get_clone_untracked() {
+ elt.representation.update(
+ |rep| rep.set_column(0, &config.column(elt.column_index))
+ );
+ }
}
}
}
\ No newline at end of file
diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs
index 79199ec..ee0af47 100644
--- a/app-proto/src/display.rs
+++ b/app-proto/src/display.rs
@@ -103,7 +103,11 @@ pub fn Display() -> View {
// change listener
let scene_changed = create_signal(true);
create_effect(move || {
- state.assembly.elements.track();
+ state.assembly.elements.with(|elts| {
+ for (_, elt) in elts {
+ elt.representation.track();
+ }
+ });
state.selection.track();
scene_changed.set(true);
});
@@ -295,25 +299,40 @@ pub fn Display() -> View {
let assembly_to_world = &location * &orientation;
// get the assembly
- let elements = state.assembly.elements.get_clone();
- let element_iter = (&elements).into_iter();
- let reps_world: Vec<_> = element_iter.clone().map(
- |(_, elt)| &assembly_to_world * &elt.representation
- ).collect();
- let colors: Vec<_> = element_iter.clone().map(|(key, elt)|
- if state.selection.with(|sel| sel.contains(&key)) {
- elt.color.map(|ch| 0.2 + 0.8*ch)
- } else {
- elt.color
- }
- ).collect();
- let highlights: Vec<_> = element_iter.map(|(key, _)|
- if state.selection.with(|sel| sel.contains(&key)) {
- 1.0_f32
- } else {
- HIGHLIGHT
- }
- ).collect();
+ let (
+ elt_cnt,
+ reps_world,
+ colors,
+ highlights
+ ) = state.assembly.elements.with(|elts| {
+ (
+ // number of elements
+ elts.len() as i32,
+
+ // representation vectors in world coordinates
+ elts.iter().map(
+ |(_, elt)| elt.representation.with(|rep| &assembly_to_world * rep)
+ ).collect::>(),
+
+ // colors
+ elts.iter().map(|(key, elt)| {
+ if state.selection.with(|sel| sel.contains(&key)) {
+ elt.color.map(|ch| 0.2 + 0.8*ch)
+ } else {
+ elt.color
+ }
+ }).collect::>(),
+
+ // highlight levels
+ elts.iter().map(|(key, _)| {
+ if state.selection.with(|sel| sel.contains(&key)) {
+ 1.0_f32
+ } else {
+ HIGHLIGHT
+ }
+ }).collect::>()
+ )
+ });
// set the resolution
let width = canvas.width() as f32;
@@ -322,7 +341,7 @@ pub fn Display() -> View {
ctx.uniform1f(shortdim_loc.as_ref(), width.min(height));
// pass the assembly
- ctx.uniform1i(sphere_cnt_loc.as_ref(), elements.len() as i32);
+ ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt);
for n in 0..reps_world.len() {
let v = &reps_world[n];
ctx.uniform3f(
diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs
index f7c975c..ee1603f 100644
--- a/app-proto/src/outline.rs
+++ b/app-proto/src/outline.rs
@@ -1,7 +1,6 @@
use itertools::Itertools;
-use sycamore::{prelude::*, web::tags::div};
+use sycamore::prelude::*;
use web_sys::{
- Element,
Event,
HtmlInputElement,
KeyboardEvent,
@@ -9,7 +8,7 @@ use web_sys::{
wasm_bindgen::JsCast
};
-use crate::{AppState, assembly::Constraint};
+use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
// an editable view of the Lorentz product representing a constraint
#[component(inline_props)]
@@ -32,6 +31,143 @@ fn LorentzProductInput(constraint: Constraint) -> View {
}
}
+// a list item that shows a constraint in an outline view of an element
+#[component(inline_props)]
+fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View {
+ let state = use_context::();
+ let assembly = &state.assembly;
+ let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone());
+ let other_subject = if constraint.subjects.0 == element_key {
+ constraint.subjects.1
+ } else {
+ constraint.subjects.0
+ };
+ let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
+ let class = constraint.lorentz_prod_valid.map(
+ |&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" }
+ );
+ view! {
+ li(class=class.get()) {
+ input(r#type="checkbox", bind:checked=constraint.active)
+ div(class="constraint-label") { (other_subject_label) }
+ LorentzProductInput(constraint=constraint)
+ div(class="status")
+ }
+ }
+}
+
+// a list item that shows an element in an outline view of an assembly
+#[component(inline_props)]
+fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
+ let state = use_context::();
+ let class = state.selection.map(
+ move |sel| if sel.contains(&key) { "selected" } else { "" }
+ );
+ let label = element.label.clone();
+ let rep_components = element.representation.map(
+ |rep| rep.iter().map(
+ |u| format!("{:.3}", u).replace("-", "\u{2212}")
+ ).collect()
+ );
+ let constrained = element.constraints.map(|csts| csts.len() > 0);
+ let constraint_list = element.constraints.map(
+ |csts| csts.clone().into_iter().collect()
+ );
+ let details_node = create_node_ref();
+ view! {
+ li {
+ details(ref=details_node) {
+ summary(
+ class=class.get(),
+ on:keydown={
+ move |event: KeyboardEvent| {
+ match event.key().as_str() {
+ "Enter" => {
+ if event.shift_key() {
+ state.selection.update(|sel| {
+ if !sel.remove(&key) {
+ sel.insert(key);
+ }
+ });
+ } else {
+ state.selection.update(|sel| {
+ sel.clear();
+ sel.insert(key);
+ });
+ }
+ event.prevent_default();
+ },
+ "ArrowRight" if constrained.get() => {
+ let _ = details_node
+ .get()
+ .unchecked_into::()
+ .set_attribute("open", "");
+ },
+ "ArrowLeft" => {
+ let _ = details_node
+ .get()
+ .unchecked_into::()
+ .remove_attribute("open");
+ },
+ _ => ()
+ }
+ }
+ }
+ ) {
+ div(
+ class="element-switch",
+ on:click=|event: MouseEvent| event.stop_propagation()
+ )
+ div(
+ class="element",
+ on:click={
+ move |event: MouseEvent| {
+ if event.shift_key() {
+ state.selection.update(|sel| {
+ if !sel.remove(&key) {
+ sel.insert(key);
+ }
+ });
+ } else {
+ state.selection.update(|sel| {
+ sel.clear();
+ sel.insert(key);
+ });
+ }
+ event.stop_propagation();
+ event.prevent_default();
+ }
+ }
+ ) {
+ div(class="element-label") { (label) }
+ div(class="element-representation") {
+ Indexed(
+ list=rep_components,
+ view=|coord_str| view! {
+ div { (coord_str) }
+ }
+ )
+ }
+ div(class="status")
+ }
+ }
+ ul(class="constraints") {
+ Keyed(
+ list=constraint_list,
+ view=move |cst_key| view! {
+ ConstraintOutlineItem(
+ constraint_key=cst_key,
+ element_key=key
+ )
+ },
+ key=|cst_key| cst_key.clone()
+ )
+ }
+ }
+ }
+ }
+}
+
// a component that lists the elements of the current assembly, showing the
// constraints on each element as a collapsible sub-list. its implementation
// is based on Kate Morley's HTML + CSS tree views:
@@ -40,15 +176,16 @@ fn LorentzProductInput(constraint: Constraint) -> View {
//
#[component]
pub fn Outline() -> View {
- // sort the elements alphabetically by ID
- let elements_sorted = create_memo(|| {
- let state = use_context::();
- state.assembly.elements
- .get_clone()
+ let state = use_context::();
+
+ // list the elements alphabetically by ID
+ let element_list = state.assembly.elements.map(
+ |elts| elts
+ .clone()
.into_iter()
.sorted_by_key(|(_, elt)| elt.id.clone())
.collect()
- });
+ );
view! {
ul(
@@ -59,128 +196,11 @@ pub fn Outline() -> View {
}
) {
Keyed(
- list=elements_sorted,
- view=|(key, elt)| {
- let state = use_context::();
- let class = create_memo({
- move || {
- if state.selection.with(|sel| sel.contains(&key)) {
- "selected"
- } else {
- ""
- }
- }
- });
- let label = elt.label.clone();
- let rep_components = elt.representation.iter().map(|u| {
- let u_coord = u.to_string().replace("-", "\u{2212}");
- View::from(div().children(u_coord))
- }).collect::>();
- let constrained = elt.constraints.len() > 0;
- let details_node = create_node_ref();
- view! {
- li {
- details(ref=details_node) {
- summary(
- class=class.get(),
- on:keydown={
- move |event: KeyboardEvent| {
- match event.key().as_str() {
- "Enter" => {
- if event.shift_key() {
- state.selection.update(|sel| {
- if !sel.remove(&key) {
- sel.insert(key);
- }
- });
- } else {
- state.selection.update(|sel| {
- sel.clear();
- sel.insert(key);
- });
- }
- event.prevent_default();
- },
- "ArrowRight" if constrained => {
- let _ = details_node
- .get()
- .unchecked_into::()
- .set_attribute("open", "");
- },
- "ArrowLeft" => {
- let _ = details_node
- .get()
- .unchecked_into::()
- .remove_attribute("open");
- },
- _ => ()
- }
- }
- }
- ) {
- div(
- class="elt-switch",
- on:click=|event: MouseEvent| event.stop_propagation()
- )
- div(
- class="elt",
- on:click={
- move |event: MouseEvent| {
- if event.shift_key() {
- state.selection.update(|sel| {
- if !sel.remove(&key) {
- sel.insert(key);
- }
- });
- } else {
- state.selection.update(|sel| {
- sel.clear();
- sel.insert(key);
- });
- }
- event.stop_propagation();
- event.prevent_default();
- }
- }
- ) {
- div(class="elt-label") { (label) }
- div(class="elt-rep") { (rep_components) }
- }
- }
- ul(class="constraints") {
- Keyed(
- list=elt.constraints.into_iter().collect::>(),
- view=move |c_key| {
- let c_state = use_context::();
- let assembly = &c_state.assembly;
- let cst = assembly.constraints.with(|csts| csts[c_key].clone());
- let other_arg = if cst.subjects.0 == key {
- cst.subjects.1
- } else {
- cst.subjects.0
- };
- let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
- view! {
- li(class="cst") {
- input(r#type="checkbox", bind:checked=cst.active)
- div(class="cst-label") { (other_arg_label) }
- LorentzProductInput(constraint=cst)
- }
- }
- },
- key=|c_key| c_key.clone()
- )
- }
- }
- }
- }
+ list=element_list,
+ view=|(key, elt)| view! {
+ ElementOutlineItem(key=key, element=elt)
},
- key=|(key, elt)| (
- key.clone(),
- elt.id.clone(),
- elt.label.clone(),
- elt.constraints.clone()
- )
+ key=|(key, _)| key.clone()
)
}
}