From fb8e391587f3e3ffde9e7c7d68940fd24dc80a6f Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sat, 25 Jan 2025 13:00:18 -0800 Subject: [PATCH 1/7] Generalize constraints to observables --- app-proto/main.css | 8 +++---- app-proto/src/add_remove.rs | 44 ++++++++++++++++++++++++++----------- app-proto/src/assembly.rs | 18 ++++++++++----- app-proto/src/outline.rs | 42 +++++++++++++++++++++++++---------- 4 files changed, 77 insertions(+), 35 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index b9fc0a1..9c45c29 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -131,10 +131,6 @@ details[open]:has(li) .element-switch::after { color: var(--text-invalid); } -.constraint > input[type=checkbox] { - margin: 0px 8px 0px 0px; -} - .constraint > input[type=text] { color: inherit; background-color: inherit; @@ -154,6 +150,10 @@ details[open]:has(li) .element-switch::after { font-style: normal; } +.constrained > .status::after, details:has(.constrained):not([open]) .status::after { + content: '🔗'; +} + .invalid > .status::after, details:has(.invalid):not([open]) .status::after { content: '⚠'; color: var(--text-invalid); diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index ba02e65..360a12d 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,7 +1,17 @@ use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; -use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}}; +use crate::{ + engine, + AppState, + assembly::{ + Assembly, + Constraint, + ConstraintRole, + Element + }, + engine::Q +}; /* DEBUG */ // load an example assembly for testing. this code will be removed once we've @@ -190,15 +200,23 @@ pub fn AddRemove() -> View { (subject_vec[0].clone(), subject_vec[1].clone()) } ); - let lorentz_prod = create_signal(0.0); - let lorentz_prod_valid = create_signal(false); - let active = create_signal(true); + let measured = state.assembly.elements.map( + move |elts| { + let reps = ( + elts[subjects.0].representation.get_clone(), + elts[subjects.1].representation.get_clone() + ); + reps.0.dot(&(&*Q * reps.1)) + } + ); + let desired = create_signal(0.0); + let role = create_signal(ConstraintRole::Measure); state.assembly.insert_constraint(Constraint { subjects: subjects, - lorentz_prod: lorentz_prod, - lorentz_prod_text: create_signal(String::new()), - lorentz_prod_valid: lorentz_prod_valid, - active: active, + measured: measured, + desired: desired, + desired_text: create_signal(String::new()), + role: role, }); state.selection.update(|sel| sel.clear()); @@ -212,19 +230,19 @@ pub fn AddRemove() -> View { &JsValue::from(cst.subjects.0), &JsValue::from(cst.subjects.1), &JsValue::from(":"), - &JsValue::from(cst.lorentz_prod.get_untracked()) + &JsValue::from(cst.desired.get_untracked()) ); } }); - // update the realization when the constraint becomes active - // and valid, or is edited while active and valid + // update the realization when the observable becomes + // constrained, or is edited while constrained create_effect(move || { console::log_1(&JsValue::from( format!("Constraint ({}, {}) updated", subjects.0, subjects.1) )); - lorentz_prod.track(); - if active.get() && lorentz_prod_valid.get() { + desired.track(); + if role.with(|r| matches!(r, ConstraintRole::Constrain)) { state.assembly.realize(); } }); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 7073c9e..37bd484 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -111,13 +111,19 @@ impl Element { } } +pub enum ConstraintRole { + Measure, + Constrain, + Invalid +} + #[derive(Clone)] pub struct Constraint { pub subjects: (ElementKey, ElementKey), - pub lorentz_prod: Signal, - pub lorentz_prod_text: Signal, - pub lorentz_prod_valid: Signal, - pub active: Signal + pub measured: ReadSignal, + pub desired: Signal, + pub desired_text: Signal, + pub role: Signal } // the velocity is expressed in uniform coordinates @@ -230,11 +236,11 @@ impl Assembly { let mut gram_to_be = PartialMatrix::new(); self.constraints.with_untracked(|csts| { for (_, cst) in csts { - if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() { + if cst.role.with_untracked(|role| matches!(role, ConstraintRole::Constrain)) { let subjects = cst.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked()); + gram_to_be.push_sym(row, col, cst.desired.get_untracked()); } } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index a6e968d..8459262 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -8,7 +8,16 @@ use web_sys::{ wasm_bindgen::JsCast }; -use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}}; +use crate::{ + AppState, + assembly, + assembly::{ + Constraint, + ConstraintKey, + ConstraintRole::*, + ElementKey + } +}; // an editable view of the Lorentz product representing a constraint #[component(inline_props)] @@ -16,16 +25,22 @@ fn LorentzProductInput(constraint: Constraint) -> View { view! { input( r#type="text", - bind:value=constraint.lorentz_prod_text, + placeholder=constraint.measured.with(|result| result.to_string()), + bind:value=constraint.desired_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); - match target.value().parse::() { - Ok(lorentz_prod) => batch(|| { - constraint.lorentz_prod.set(lorentz_prod); - constraint.lorentz_prod_valid.set(true); - }), - Err(_) => constraint.lorentz_prod_valid.set(false) - }; + let value = target.value(); + if value.is_empty() { + constraint.role.set(Measure); + } else { + match target.value().parse::() { + Ok(desired) => batch(|| { + constraint.desired.set(desired); + constraint.role.set(Constrain); + }), + Err(_) => constraint.role.set(Invalid) + }; + } } ) } @@ -43,12 +58,15 @@ fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) 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" } + let class = constraint.role.map( + |role| match role { + Measure => "constraint", + Constrain => "constraint constrained", + Invalid => "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") -- 2.43.0 From 677ef47544d1b99cfd82989dcdb83b9b6176d838 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 26 Jan 2025 10:50:15 -0800 Subject: [PATCH 2/7] Rename constraints to observables --- app-proto/main.css | 14 ++++---- app-proto/src/add_remove.rs | 26 +++++++------- app-proto/src/assembly.rs | 46 ++++++++++++------------ app-proto/src/outline.rs | 70 ++++++++++++++++++------------------- 4 files changed, 78 insertions(+), 78 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index 9c45c29..e41fcd9 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -77,12 +77,12 @@ summary.selected { background-color: var(--selection-highlight); } -summary > div, .constraint { +summary > div, .observable { padding-top: 4px; padding-bottom: 4px; } -.element, .constraint { +.element, .observable { display: flex; flex-grow: 1; padding-left: 8px; @@ -107,7 +107,7 @@ details[open]:has(li) .element-switch::after { flex-grow: 1; } -.constraint-label { +.observable-label { flex-grow: 1; } @@ -123,22 +123,22 @@ details[open]:has(li) .element-switch::after { width: 56px; } -.constraint { +.observable { font-style: italic; } -.constraint.invalid { +.observable.invalid { color: var(--text-invalid); } -.constraint > input[type=text] { +.observable > input[type=text] { color: inherit; background-color: inherit; border: 1px solid var(--border); border-radius: 2px; } -.constraint.invalid > input[type=text] { +.observable.invalid > input[type=text] { border-color: var(--border-invalid); } diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 360a12d..07b4c28 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -6,8 +6,8 @@ use crate::{ AppState, assembly::{ Assembly, - Constraint, - ConstraintRole, + Observable, + ObservableRole, Element }, engine::Q @@ -210,8 +210,8 @@ pub fn AddRemove() -> View { } ); let desired = create_signal(0.0); - let role = create_signal(ConstraintRole::Measure); - state.assembly.insert_constraint(Constraint { + let role = create_signal(ObservableRole::Measure); + state.assembly.insert_observable(Observable { subjects: subjects, measured: measured, desired: desired, @@ -221,16 +221,16 @@ pub fn AddRemove() -> View { state.selection.update(|sel| sel.clear()); /* DEBUG */ - // print updated constraint list - console::log_1(&JsValue::from("Constraints:")); - state.assembly.constraints.with(|csts| { - for (_, cst) in csts.into_iter() { + // print updated observable list + console::log_1(&JsValue::from("Observables:")); + state.assembly.observables.with(|obsls| { + for (_, obs) in obsls.into_iter() { console::log_5( &JsValue::from(" "), - &JsValue::from(cst.subjects.0), - &JsValue::from(cst.subjects.1), + &JsValue::from(obs.subjects.0), + &JsValue::from(obs.subjects.1), &JsValue::from(":"), - &JsValue::from(cst.desired.get_untracked()) + &JsValue::from(obs.desired.get_untracked()) ); } }); @@ -239,10 +239,10 @@ pub fn AddRemove() -> View { // constrained, or is edited while constrained create_effect(move || { console::log_1(&JsValue::from( - format!("Constraint ({}, {}) updated", subjects.0, subjects.1) + format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) )); desired.track(); - if role.with(|r| matches!(r, ConstraintRole::Constrain)) { + if role.with(|r| matches!(r, ObservableRole::Constrain)) { state.assembly.realize(); } }); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 37bd484..e438934 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -7,9 +7,9 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::engine::{realize_gram, local_unif_to_std, ConfigSubspace, PartialMatrix}; -// the types of the keys we use to access an assembly's elements and constraints +// the types of the keys we use to access an assembly's elements and observables pub type ElementKey = usize; -pub type ConstraintKey = usize; +pub type ObservableKey = usize; pub type ElementColor = [f32; 3]; @@ -26,7 +26,7 @@ pub struct Element { pub label: String, pub color: ElementColor, pub representation: Signal>, - pub constraints: Signal>, + pub observables: Signal>, // a serial number, assigned by `Element::new`, that uniquely identifies // each element @@ -61,7 +61,7 @@ impl Element { label: label, color: color, representation: create_signal(representation), - constraints: create_signal(BTreeSet::default()), + observables: create_signal(BTreeSet::default()), serial: serial, column_index: None } @@ -111,19 +111,19 @@ impl Element { } } -pub enum ConstraintRole { +pub enum ObservableRole { Measure, Constrain, Invalid } #[derive(Clone)] -pub struct Constraint { +pub struct Observable { pub subjects: (ElementKey, ElementKey), pub measured: ReadSignal, pub desired: Signal, pub desired_text: Signal, - pub role: Signal + pub role: Signal } // the velocity is expressed in uniform coordinates @@ -137,9 +137,9 @@ type AssemblyMotion<'a> = Vec>; // a complete, view-independent description of an assembly #[derive(Clone)] pub struct Assembly { - // elements and constraints + // elements and observables pub elements: Signal>, - pub constraints: Signal>, + pub observables: Signal>, // solution variety tangent space. the basis vectors are stored in // configuration matrix format, ordered according to the elements' column @@ -161,13 +161,13 @@ impl Assembly { pub fn new() -> Assembly { Assembly { elements: create_signal(Slab::new()), - constraints: create_signal(Slab::new()), + observables: create_signal(Slab::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(FxHashMap::default()) } } - // --- inserting elements and constraints --- + // --- inserting elements and observables --- // insert an element into the assembly without checking whether we already // have an element with the same identifier. any element that does have the @@ -210,14 +210,14 @@ impl Assembly { ); } - pub fn insert_constraint(&self, constraint: Constraint) { - let subjects = constraint.subjects; - let key = self.constraints.update(|csts| csts.insert(constraint)); - let subject_constraints = self.elements.with( - |elts| (elts[subjects.0].constraints, elts[subjects.1].constraints) + pub fn insert_observable(&self, observable: Observable) { + let subjects = observable.subjects; + let key = self.observables.update(|obsls| obsls.insert(observable)); + let subject_observables = self.elements.with( + |elts| (elts[subjects.0].observables, elts[subjects.1].observables) ); - subject_constraints.0.update(|csts| csts.insert(key)); - subject_constraints.1.update(|csts| csts.insert(key)); + subject_observables.0.update(|obsls| obsls.insert(key)); + subject_observables.1.update(|obsls| obsls.insert(key)); } // --- realization --- @@ -234,13 +234,13 @@ impl Assembly { let (gram, guess) = self.elements.with_untracked(|elts| { // set up the off-diagonal part of the Gram matrix let mut gram_to_be = PartialMatrix::new(); - self.constraints.with_untracked(|csts| { - for (_, cst) in csts { - if cst.role.with_untracked(|role| matches!(role, ConstraintRole::Constrain)) { - let subjects = cst.subjects; + self.observables.with_untracked(|obsls| { + for (_, obs) in obsls { + if obs.role.with_untracked(|role| matches!(role, ObservableRole::Constrain)) { + let subjects = obs.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, cst.desired.get_untracked()); + gram_to_be.push_sym(row, col, obs.desired.get_untracked()); } } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 8459262..5444974 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -12,33 +12,33 @@ use crate::{ AppState, assembly, assembly::{ - Constraint, - ConstraintKey, - ConstraintRole::*, + Observable, + ObservableKey, + ObservableRole::*, ElementKey } }; -// an editable view of the Lorentz product representing a constraint +// an editable view of the Lorentz product representing an observable #[component(inline_props)] -fn LorentzProductInput(constraint: Constraint) -> View { +fn ObservableInput(observable: Observable) -> View { view! { input( r#type="text", - placeholder=constraint.measured.with(|result| result.to_string()), - bind:value=constraint.desired_text, + placeholder=observable.measured.with(|result| result.to_string()), + bind:value=observable.desired_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); let value = target.value(); if value.is_empty() { - constraint.role.set(Measure); + observable.role.set(Measure); } else { match target.value().parse::() { Ok(desired) => batch(|| { - constraint.desired.set(desired); - constraint.role.set(Constrain); + observable.desired.set(desired); + observable.role.set(Constrain); }), - Err(_) => constraint.role.set(Invalid) + Err(_) => observable.role.set(Invalid) }; } } @@ -46,29 +46,29 @@ fn LorentzProductInput(constraint: Constraint) -> View { } } -// a list item that shows a constraint in an outline view of an element +// a list item that shows an observable in an outline view of an element #[component(inline_props)] -fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View { +fn ObservableOutlineItem(observable_key: ObservableKey, 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 + let observable = assembly.observables.with(|obsls| obsls[observable_key].clone()); + let other_subject = if observable.subjects.0 == element_key { + observable.subjects.1 } else { - constraint.subjects.0 + observable.subjects.0 }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); - let class = constraint.role.map( + let class = observable.role.map( |role| match role { - Measure => "constraint", - Constrain => "constraint constrained", - Invalid => "constraint invalid" + Measure => "observable", + Constrain => "observable constrained", + Invalid => "observable invalid" } ); view! { li(class=class.get()) { - div(class="constraint-label") { (other_subject_label) } - LorentzProductInput(constraint=constraint) + div(class="observable-label") { (other_subject_label) } + ObservableInput(observable=observable) div(class="status") } } @@ -92,9 +92,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { ).collect::>() ) }; - let constrained = element.constraints.map(|csts| csts.len() > 0); - let constraint_list = element.constraints.map( - |csts| csts.clone().into_iter().collect() + let observed = element.observables.map(|obsls| obsls.len() > 0); + let observable_list = element.observables.map( + |obsls| obsls.clone().into_iter().collect() ); let details_node = create_node_ref(); view! { @@ -109,7 +109,7 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { state.select(key, event.shift_key()); event.prevent_default(); }, - "ArrowRight" if constrained.get() => { + "ArrowRight" if observed.get() => { let _ = details_node .get() .unchecked_into::() @@ -156,16 +156,16 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { div(class="status") } } - ul(class="constraints") { + ul(class="observables") { Keyed( - list=constraint_list, - view=move |cst_key| view! { - ConstraintOutlineItem( - constraint_key=cst_key, + list=observable_list, + view=move |obs_key| view! { + ObservableOutlineItem( + observable_key=obs_key, element_key=key ) }, - key=|cst_key| cst_key.clone() + key=|obs_key| obs_key.clone() ) } } @@ -174,8 +174,8 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { } // 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: +// observables associated with each element as a collapsible sub-list. its +// implementation is based on Kate Morley's HTML + CSS tree views: // // https://iamkate.com/code/tree-views/ // -- 2.43.0 From af2724f934e6cea8e4528428d2b4c8d3aff5b6de Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 26 Jan 2025 17:48:32 -0800 Subject: [PATCH 3/7] Rename `ObservableRole` variants Also rename corresponding CSS classes and add methods to check roles. --- app-proto/main.css | 8 ++++---- app-proto/src/add_remove.rs | 4 ++-- app-proto/src/assembly.rs | 22 ++++++++++++++++++---- app-proto/src/outline.rs | 12 ++++++------ 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index e41fcd9..f544e19 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -127,7 +127,7 @@ details[open]:has(li) .element-switch::after { font-style: italic; } -.observable.invalid { +.observable.invalid-constraint { color: var(--text-invalid); } @@ -138,7 +138,7 @@ details[open]:has(li) .element-switch::after { border-radius: 2px; } -.observable.invalid > input[type=text] { +.observable.invalid-constraint > input[type=text] { border-color: var(--border-invalid); } @@ -150,11 +150,11 @@ details[open]:has(li) .element-switch::after { font-style: normal; } -.constrained > .status::after, details:has(.constrained):not([open]) .status::after { +.valid-constraint > .status::after, details:has(.valid-constraint):not([open]) .status::after { content: '🔗'; } -.invalid > .status::after, details:has(.invalid):not([open]) .status::after { +.invalid-constraint > .status::after, details:has(.invalid-constraint):not([open]) .status::after { content: '⚠'; color: var(--text-invalid); } diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 07b4c28..640925a 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -210,7 +210,7 @@ pub fn AddRemove() -> View { } ); let desired = create_signal(0.0); - let role = create_signal(ObservableRole::Measure); + let role = create_signal(ObservableRole::Measurement); state.assembly.insert_observable(Observable { subjects: subjects, measured: measured, @@ -242,7 +242,7 @@ pub fn AddRemove() -> View { format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) )); desired.track(); - if role.with(|r| matches!(r, ObservableRole::Constrain)) { + if role.with(|rl| rl.is_valid_constraint()) { state.assembly.realize(); } }); diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index e438934..ba2f9c5 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -112,9 +112,17 @@ impl Element { } pub enum ObservableRole { - Measure, - Constrain, - Invalid + Measurement, + Constraint(bool) +} + +impl ObservableRole { + pub fn is_valid_constraint(&self) -> bool { + match self { + ObservableRole::Measurement => false, + ObservableRole::Constraint(valid) => *valid + } + } } #[derive(Clone)] @@ -126,6 +134,12 @@ pub struct Observable { pub role: Signal } +impl Observable { + fn role_is_valid_constraint_untracked(&self) -> bool { + self.role.with_untracked(|role| role.is_valid_constraint()) + } +} + // the velocity is expressed in uniform coordinates pub struct ElementMotion<'a> { pub key: ElementKey, @@ -236,7 +250,7 @@ impl Assembly { let mut gram_to_be = PartialMatrix::new(); self.observables.with_untracked(|obsls| { for (_, obs) in obsls { - if obs.role.with_untracked(|role| matches!(role, ObservableRole::Constrain)) { + if obs.role_is_valid_constraint_untracked() { let subjects = obs.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 5444974..d2f9058 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -31,14 +31,14 @@ fn ObservableInput(observable: Observable) -> View { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); let value = target.value(); if value.is_empty() { - observable.role.set(Measure); + observable.role.set(Measurement); } else { match target.value().parse::() { Ok(desired) => batch(|| { observable.desired.set(desired); - observable.role.set(Constrain); + observable.role.set(Constraint(true)); }), - Err(_) => observable.role.set(Invalid) + Err(_) => observable.role.set(Constraint(false)) }; } } @@ -60,9 +60,9 @@ fn ObservableOutlineItem(observable_key: ObservableKey, element_key: ElementKey) let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); let class = observable.role.map( |role| match role { - Measure => "observable", - Constrain => "observable constrained", - Invalid => "observable invalid" + Measurement => "observable", + Constraint(true) => "observable valid-constraint", + Constraint(false) => "observable invalid-constraint" } ); view! { -- 2.43.0 From dc8330df6a3ce414648028993a43a2ee58456468 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Sun, 9 Feb 2025 22:25:44 -0800 Subject: [PATCH 4/7] Revise observable styling Distinguish constraints from observables using dark background rather than marker. Customize focus highlighting. Drop the input type selector that used to make styling apply to the Lorentz product field but not the constraint activation check box. --- app-proto/main.css | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index f544e19..46bdf84 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -3,7 +3,8 @@ --text-bright: white; --text-invalid: #f58fc2; /* bright pink */ --border: #555; /* light gray */ - --border-focus: #aaa; /* bright gray */ + --border-focus-dark: #aaa; /* bright gray */ + --border-focus-light: white; --border-invalid: #70495c; /* dusky pink */ --selection-highlight: #444; /* medium gray */ --page-background: #222; /* dark gray */ @@ -131,14 +132,24 @@ details[open]:has(li) .element-switch::after { color: var(--text-invalid); } -.observable > input[type=text] { +.observable > input { color: inherit; background-color: inherit; border: 1px solid var(--border); border-radius: 2px; } -.observable.invalid-constraint > input[type=text] { +.observable > input::placeholder { + color: inherit; + opacity: 54%; + font-style: italic; +} + +.observable.valid-constraint > input { + background-color: var(--display-background); +} + +.observable.invalid-constraint > input { border-color: var(--border-invalid); } @@ -150,10 +161,6 @@ details[open]:has(li) .element-switch::after { font-style: normal; } -.valid-constraint > .status::after, details:has(.valid-constraint):not([open]) .status::after { - content: '🔗'; -} - .invalid-constraint > .status::after, details:has(.invalid-constraint):not([open]) .status::after { content: '⚠'; color: var(--text-invalid); @@ -171,5 +178,11 @@ canvas { } canvas:focus { - border-color: var(--border-focus); + border-color: var(--border-focus-dark); + outline: none; +} + +input:focus { + border-color: var(--border-focus-light); + outline: none; } \ No newline at end of file -- 2.43.0 From de7122d871b9bb9d4e7caf2a9a47f655bd6920fb Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 12 Feb 2025 10:37:48 -0800 Subject: [PATCH 5/7] Label observable type Right now, there's only one type of observable, so the label can be hard-coded. --- app-proto/main.css | 7 ++++++- app-proto/src/outline.rs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app-proto/main.css b/app-proto/main.css index 46bdf84..a1ff9da 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -24,7 +24,7 @@ body { display: flex; flex-direction: column; float: left; - width: 450px; + width: 500px; height: 100vh; margin: 0px; padding: 0px; @@ -128,6 +128,11 @@ details[open]:has(li) .element-switch::after { font-style: italic; } +.observable-type { + padding: 2px 8px 0px 8px; + font-size: 10pt; +} + .observable.invalid-constraint { color: var(--text-invalid); } diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index d2f9058..f341357 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -68,6 +68,7 @@ fn ObservableOutlineItem(observable_key: ObservableKey, element_key: ElementKey) view! { li(class=class.get()) { div(class="observable-label") { (other_subject_label) } + div(class="observable-type") { "Inversive distance" } ObservableInput(observable=observable) div(class="status") } -- 2.43.0 From 24139ad5e9a1822cc0e240a6021d6a91e5ddd22a Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 12 Feb 2025 11:35:07 -0800 Subject: [PATCH 6/7] Rename observables to regulators --- app-proto/main.css | 20 +++++----- app-proto/src/add_remove.rs | 26 ++++++------- app-proto/src/assembly.rs | 54 +++++++++++++-------------- app-proto/src/outline.rs | 74 ++++++++++++++++++------------------- 4 files changed, 87 insertions(+), 87 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index a1ff9da..d71ce4a 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -78,12 +78,12 @@ summary.selected { background-color: var(--selection-highlight); } -summary > div, .observable { +summary > div, .regulator { padding-top: 4px; padding-bottom: 4px; } -.element, .observable { +.element, .regulator { display: flex; flex-grow: 1; padding-left: 8px; @@ -108,7 +108,7 @@ details[open]:has(li) .element-switch::after { flex-grow: 1; } -.observable-label { +.regulator-label { flex-grow: 1; } @@ -124,37 +124,37 @@ details[open]:has(li) .element-switch::after { width: 56px; } -.observable { +.regulator { font-style: italic; } -.observable-type { +.regulator-type { padding: 2px 8px 0px 8px; font-size: 10pt; } -.observable.invalid-constraint { +.regulator.invalid-constraint { color: var(--text-invalid); } -.observable > input { +.regulator > input { color: inherit; background-color: inherit; border: 1px solid var(--border); border-radius: 2px; } -.observable > input::placeholder { +.regulator > input::placeholder { color: inherit; opacity: 54%; font-style: italic; } -.observable.valid-constraint > input { +.regulator.valid-constraint > input { background-color: var(--display-background); } -.observable.invalid-constraint > input { +.regulator.invalid-constraint > input { border-color: var(--border-invalid); } diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 640925a..d8d6163 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -6,8 +6,8 @@ use crate::{ AppState, assembly::{ Assembly, - Observable, - ObservableRole, + Regulator, + RegulatorRole, Element }, engine::Q @@ -210,8 +210,8 @@ pub fn AddRemove() -> View { } ); let desired = create_signal(0.0); - let role = create_signal(ObservableRole::Measurement); - state.assembly.insert_observable(Observable { + let role = create_signal(RegulatorRole::Measurement); + state.assembly.insert_regulator(Regulator { subjects: subjects, measured: measured, desired: desired, @@ -221,22 +221,22 @@ pub fn AddRemove() -> View { state.selection.update(|sel| sel.clear()); /* DEBUG */ - // print updated observable list - console::log_1(&JsValue::from("Observables:")); - state.assembly.observables.with(|obsls| { - for (_, obs) in obsls.into_iter() { + // print updated regulator list + console::log_1(&JsValue::from("Regulators:")); + state.assembly.regulators.with(|regs| { + for (_, reg) in regs.into_iter() { console::log_5( &JsValue::from(" "), - &JsValue::from(obs.subjects.0), - &JsValue::from(obs.subjects.1), + &JsValue::from(reg.subjects.0), + &JsValue::from(reg.subjects.1), &JsValue::from(":"), - &JsValue::from(obs.desired.get_untracked()) + &JsValue::from(reg.desired.get_untracked()) ); } }); - // update the realization when the observable becomes - // constrained, or is edited while constrained + // update the realization when the regulator becomes + // a constraint, or is edited while acting as a constraint create_effect(move || { console::log_1(&JsValue::from( format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index ba2f9c5..7c9418e 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -7,9 +7,9 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::engine::{realize_gram, local_unif_to_std, ConfigSubspace, PartialMatrix}; -// the types of the keys we use to access an assembly's elements and observables +// the types of the keys we use to access an assembly's elements and regulators pub type ElementKey = usize; -pub type ObservableKey = usize; +pub type RegulatorKey = usize; pub type ElementColor = [f32; 3]; @@ -26,7 +26,7 @@ pub struct Element { pub label: String, pub color: ElementColor, pub representation: Signal>, - pub observables: Signal>, + pub regulators: Signal>, // a serial number, assigned by `Element::new`, that uniquely identifies // each element @@ -61,7 +61,7 @@ impl Element { label: label, color: color, representation: create_signal(representation), - observables: create_signal(BTreeSet::default()), + regulators: create_signal(BTreeSet::default()), serial: serial, column_index: None } @@ -111,30 +111,30 @@ impl Element { } } -pub enum ObservableRole { +pub enum RegulatorRole { Measurement, Constraint(bool) } -impl ObservableRole { +impl RegulatorRole { pub fn is_valid_constraint(&self) -> bool { match self { - ObservableRole::Measurement => false, - ObservableRole::Constraint(valid) => *valid + RegulatorRole::Measurement => false, + RegulatorRole::Constraint(valid) => *valid } } } #[derive(Clone)] -pub struct Observable { +pub struct Regulator { pub subjects: (ElementKey, ElementKey), pub measured: ReadSignal, pub desired: Signal, pub desired_text: Signal, - pub role: Signal + pub role: Signal } -impl Observable { +impl Regulator { fn role_is_valid_constraint_untracked(&self) -> bool { self.role.with_untracked(|role| role.is_valid_constraint()) } @@ -151,9 +151,9 @@ type AssemblyMotion<'a> = Vec>; // a complete, view-independent description of an assembly #[derive(Clone)] pub struct Assembly { - // elements and observables + // elements and regulators pub elements: Signal>, - pub observables: Signal>, + pub regulators: Signal>, // solution variety tangent space. the basis vectors are stored in // configuration matrix format, ordered according to the elements' column @@ -175,13 +175,13 @@ impl Assembly { pub fn new() -> Assembly { Assembly { elements: create_signal(Slab::new()), - observables: create_signal(Slab::new()), + regulators: create_signal(Slab::new()), tangent: create_signal(ConfigSubspace::zero(0)), elements_by_id: create_signal(FxHashMap::default()) } } - // --- inserting elements and observables --- + // --- inserting elements and regulators --- // insert an element into the assembly without checking whether we already // have an element with the same identifier. any element that does have the @@ -224,14 +224,14 @@ impl Assembly { ); } - pub fn insert_observable(&self, observable: Observable) { - let subjects = observable.subjects; - let key = self.observables.update(|obsls| obsls.insert(observable)); - let subject_observables = self.elements.with( - |elts| (elts[subjects.0].observables, elts[subjects.1].observables) + pub fn insert_regulator(&self, regulator: Regulator) { + let subjects = regulator.subjects; + let key = self.regulators.update(|regs| regs.insert(regulator)); + let subject_regulators = self.elements.with( + |elts| (elts[subjects.0].regulators, elts[subjects.1].regulators) ); - subject_observables.0.update(|obsls| obsls.insert(key)); - subject_observables.1.update(|obsls| obsls.insert(key)); + subject_regulators.0.update(|regs| regs.insert(key)); + subject_regulators.1.update(|regs| regs.insert(key)); } // --- realization --- @@ -248,13 +248,13 @@ impl Assembly { let (gram, guess) = self.elements.with_untracked(|elts| { // set up the off-diagonal part of the Gram matrix let mut gram_to_be = PartialMatrix::new(); - self.observables.with_untracked(|obsls| { - for (_, obs) in obsls { - if obs.role_is_valid_constraint_untracked() { - let subjects = obs.subjects; + self.regulators.with_untracked(|regs| { + for (_, reg) in regs { + if reg.role_is_valid_constraint_untracked() { + let subjects = reg.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, obs.desired.get_untracked()); + gram_to_be.push_sym(row, col, reg.desired.get_untracked()); } } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index f341357..6e3e0d3 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -12,33 +12,33 @@ use crate::{ AppState, assembly, assembly::{ - Observable, - ObservableKey, - ObservableRole::*, + Regulator, + RegulatorKey, + RegulatorRole::*, ElementKey } }; -// an editable view of the Lorentz product representing an observable +// an editable view of a regulator #[component(inline_props)] -fn ObservableInput(observable: Observable) -> View { +fn RegulatorInput(regulator: Regulator) -> View { view! { input( r#type="text", - placeholder=observable.measured.with(|result| result.to_string()), - bind:value=observable.desired_text, + placeholder=regulator.measured.with(|result| result.to_string()), + bind:value=regulator.desired_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); let value = target.value(); if value.is_empty() { - observable.role.set(Measurement); + regulator.role.set(Measurement); } else { match target.value().parse::() { Ok(desired) => batch(|| { - observable.desired.set(desired); - observable.role.set(Constraint(true)); + regulator.desired.set(desired); + regulator.role.set(Constraint(true)); }), - Err(_) => observable.role.set(Constraint(false)) + Err(_) => regulator.role.set(Constraint(false)) }; } } @@ -46,30 +46,30 @@ fn ObservableInput(observable: Observable) -> View { } } -// a list item that shows an observable in an outline view of an element +// a list item that shows a regulator in an outline view of an element #[component(inline_props)] -fn ObservableOutlineItem(observable_key: ObservableKey, element_key: ElementKey) -> View { +fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { let state = use_context::(); let assembly = &state.assembly; - let observable = assembly.observables.with(|obsls| obsls[observable_key].clone()); - let other_subject = if observable.subjects.0 == element_key { - observable.subjects.1 + let regulator = assembly.regulators.with(|regs| regs[regulator_key].clone()); + let other_subject = if regulator.subjects.0 == element_key { + regulator.subjects.1 } else { - observable.subjects.0 + regulator.subjects.0 }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); - let class = observable.role.map( + let class = regulator.role.map( |role| match role { - Measurement => "observable", - Constraint(true) => "observable valid-constraint", - Constraint(false) => "observable invalid-constraint" + Measurement => "regulator", + Constraint(true) => "regulator valid-constraint", + Constraint(false) => "regulator invalid-constraint" } ); view! { li(class=class.get()) { - div(class="observable-label") { (other_subject_label) } - div(class="observable-type") { "Inversive distance" } - ObservableInput(observable=observable) + div(class="regulator-label") { (other_subject_label) } + div(class="regulator-type") { "Inversive distance" } + RegulatorInput(regulator=regulator) div(class="status") } } @@ -93,9 +93,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { ).collect::>() ) }; - let observed = element.observables.map(|obsls| obsls.len() > 0); - let observable_list = element.observables.map( - |obsls| obsls.clone().into_iter().collect() + let regulated = element.regulators.map(|regs| regs.len() > 0); + let regulator_list = element.regulators.map( + |regs| regs.clone().into_iter().collect() ); let details_node = create_node_ref(); view! { @@ -110,7 +110,7 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { state.select(key, event.shift_key()); event.prevent_default(); }, - "ArrowRight" if observed.get() => { + "ArrowRight" if regulated.get() => { let _ = details_node .get() .unchecked_into::() @@ -157,16 +157,16 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { div(class="status") } } - ul(class="observables") { + ul(class="regulators") { Keyed( - list=observable_list, - view=move |obs_key| view! { - ObservableOutlineItem( - observable_key=obs_key, + list=regulator_list, + view=move |reg_key| view! { + RegulatorOutlineItem( + regulator_key=reg_key, element_key=key ) }, - key=|obs_key| obs_key.clone() + key=|reg_key| reg_key.clone() ) } } @@ -174,9 +174,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { } } -// a component that lists the elements of the current assembly, showing the -// observables associated with each element as a collapsible sub-list. its -// implementation is based on Kate Morley's HTML + CSS tree views: +// a component that lists the elements of the current assembly, showing each +// element's regulators in a collapsible sub-list. its implementation is based +// on Kate Morley's HTML + CSS tree views: // // https://iamkate.com/code/tree-views/ // -- 2.43.0 From b3e4e902f39d4abc195878755203799fe1302fa1 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 12 Feb 2025 11:55:45 -0800 Subject: [PATCH 7/7] Rename `Regulator` fields --- app-proto/src/add_remove.rs | 14 +++++++------- app-proto/src/assembly.rs | 8 ++++---- app-proto/src/outline.rs | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index d8d6163..3280dac 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -200,7 +200,7 @@ pub fn AddRemove() -> View { (subject_vec[0].clone(), subject_vec[1].clone()) } ); - let measured = state.assembly.elements.map( + let measurement = state.assembly.elements.map( move |elts| { let reps = ( elts[subjects.0].representation.get_clone(), @@ -209,13 +209,13 @@ pub fn AddRemove() -> View { reps.0.dot(&(&*Q * reps.1)) } ); - let desired = create_signal(0.0); + let set_point = create_signal(0.0); let role = create_signal(RegulatorRole::Measurement); state.assembly.insert_regulator(Regulator { subjects: subjects, - measured: measured, - desired: desired, - desired_text: create_signal(String::new()), + measurement: measurement, + set_point: set_point, + set_point_text: create_signal(String::new()), role: role, }); state.selection.update(|sel| sel.clear()); @@ -230,7 +230,7 @@ pub fn AddRemove() -> View { &JsValue::from(reg.subjects.0), &JsValue::from(reg.subjects.1), &JsValue::from(":"), - &JsValue::from(reg.desired.get_untracked()) + &JsValue::from(reg.set_point.get_untracked()) ); } }); @@ -241,7 +241,7 @@ pub fn AddRemove() -> View { console::log_1(&JsValue::from( format!("Updated constraint with subjects ({}, {})", subjects.0, subjects.1) )); - desired.track(); + set_point.track(); if role.with(|rl| rl.is_valid_constraint()) { state.assembly.realize(); } diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 7c9418e..c052a3b 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -128,9 +128,9 @@ impl RegulatorRole { #[derive(Clone)] pub struct Regulator { pub subjects: (ElementKey, ElementKey), - pub measured: ReadSignal, - pub desired: Signal, - pub desired_text: Signal, + pub measurement: ReadSignal, + pub set_point: Signal, + pub set_point_text: Signal, pub role: Signal } @@ -254,7 +254,7 @@ impl Assembly { let subjects = reg.subjects; let row = elts[subjects.0].column_index.unwrap(); let col = elts[subjects.1].column_index.unwrap(); - gram_to_be.push_sym(row, col, reg.desired.get_untracked()); + gram_to_be.push_sym(row, col, reg.set_point.get_untracked()); } } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 6e3e0d3..8dcda93 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -25,8 +25,8 @@ fn RegulatorInput(regulator: Regulator) -> View { view! { input( r#type="text", - placeholder=regulator.measured.with(|result| result.to_string()), - bind:value=regulator.desired_text, + placeholder=regulator.measurement.with(|result| result.to_string()), + bind:value=regulator.set_point_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); let value = target.value(); @@ -34,8 +34,8 @@ fn RegulatorInput(regulator: Regulator) -> View { regulator.role.set(Measurement); } else { match target.value().parse::() { - Ok(desired) => batch(|| { - regulator.desired.set(desired); + Ok(set_pt) => batch(|| { + regulator.set_point.set(set_pt); regulator.role.set(Constraint(true)); }), Err(_) => regulator.role.set(Constraint(false)) -- 2.43.0