forked from StudioInfinity/dyna3
Generalize constraints to observables (#48)
Unifies the interface elements for measuring and constraining real-valued observables, as proposed in issue #47. The resulting combination is called a "Regulator," at least in the code. They are presented as text inputs in the table view. When a Regulatore is in measurement mode (has no "set point"), the text field displays its value. Entering a desired value into the text field creates a set point, and then the Regulator acts to (attempt to) constrain the value. Setting the desired value to the empty string switches the observable back to measurement mode. If you enter a desired value that can't be parsed as a floating point number, the regulator input is flagged as invalid and it has no effect on the state of the regulator. The set point can in this case be restored to its previous value (or to no set point if that was its prior state) by pressing the "Esc" key. Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Co-authored-by: glen <glen@studioinfinity.org> Reviewed-on: glen/dyna3#48 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
This commit is contained in:
parent
46324fecc6
commit
da28bc99d2
7 changed files with 249 additions and 126 deletions
|
@ -1,56 +1,97 @@
|
|||
use itertools::Itertools;
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{
|
||||
Event,
|
||||
HtmlInputElement,
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
wasm_bindgen::JsCast
|
||||
};
|
||||
|
||||
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
|
||||
use crate::{
|
||||
AppState,
|
||||
assembly,
|
||||
assembly::{ElementKey, Regulator, RegulatorKey},
|
||||
specified::SpecifiedValue
|
||||
};
|
||||
|
||||
// an editable view of the Lorentz product representing a constraint
|
||||
// an editable view of a regulator
|
||||
#[component(inline_props)]
|
||||
fn LorentzProductInput(constraint: Constraint) -> View {
|
||||
fn RegulatorInput(regulator: Regulator) -> View {
|
||||
let valid = create_signal(true);
|
||||
let value = create_signal(
|
||||
regulator.set_point.with_untracked(|set_pt| set_pt.spec.clone())
|
||||
);
|
||||
|
||||
// this closure resets the input value to the regulator's set point
|
||||
// specification
|
||||
let reset_value = move || {
|
||||
batch(|| {
|
||||
valid.set(true);
|
||||
value.set(regulator.set_point.with(|set_pt| set_pt.spec.clone()));
|
||||
})
|
||||
};
|
||||
|
||||
// reset the input value whenever the regulator's set point specification
|
||||
// is updated
|
||||
create_effect(reset_value);
|
||||
|
||||
view! {
|
||||
input(
|
||||
r#type="text",
|
||||
bind:value=constraint.lorentz_prod_text,
|
||||
on:change=move |event: Event| {
|
||||
let target: HtmlInputElement = event.target().unwrap().unchecked_into();
|
||||
match target.value().parse::<f64>() {
|
||||
Ok(lorentz_prod) => batch(|| {
|
||||
constraint.lorentz_prod.set(lorentz_prod);
|
||||
constraint.lorentz_prod_valid.set(true);
|
||||
}),
|
||||
Err(_) => constraint.lorentz_prod_valid.set(false)
|
||||
};
|
||||
class=move || {
|
||||
if valid.get() {
|
||||
regulator.set_point.with(|set_pt| {
|
||||
if set_pt.is_present() {
|
||||
"regulator-input constraint"
|
||||
} else {
|
||||
"regulator-input"
|
||||
}
|
||||
})
|
||||
} else {
|
||||
"regulator-input invalid"
|
||||
}
|
||||
},
|
||||
placeholder=regulator.measurement.with(|result| result.to_string()),
|
||||
bind:value=value,
|
||||
on:change=move |_| {
|
||||
valid.set(
|
||||
match SpecifiedValue::try_from(value.get_clone_untracked()) {
|
||||
Ok(set_pt) => {
|
||||
regulator.set_point.set(set_pt);
|
||||
true
|
||||
}
|
||||
Err(_) => false
|
||||
}
|
||||
)
|
||||
},
|
||||
on:keydown={
|
||||
move |event: KeyboardEvent| {
|
||||
match event.key().as_str() {
|
||||
"Escape" => reset_value(),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// a list item that shows a constraint 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 ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View {
|
||||
fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View {
|
||||
let state = use_context::<AppState>();
|
||||
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 regulator = assembly.regulators.with(|regs| regs[regulator_key]);
|
||||
let other_subject = if regulator.subjects.0 == element_key {
|
||||
regulator.subjects.1
|
||||
} else {
|
||||
constraint.subjects.0
|
||||
regulator.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)
|
||||
li(class="regulator") {
|
||||
div(class="regulator-label") { (other_subject_label) }
|
||||
div(class="regulator-type") { "Inversive distance" }
|
||||
RegulatorInput(regulator=regulator)
|
||||
div(class="status")
|
||||
}
|
||||
}
|
||||
|
@ -74,9 +115,9 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||
).collect::<Vec<_>>()
|
||||
)
|
||||
};
|
||||
let constrained = element.constraints.map(|csts| csts.len() > 0);
|
||||
let constraint_list = element.constraints.map(
|
||||
|csts| csts.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! {
|
||||
|
@ -91,7 +132,7 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||
state.select(key, event.shift_key());
|
||||
event.prevent_default();
|
||||
},
|
||||
"ArrowRight" if constrained.get() => {
|
||||
"ArrowRight" if regulated.get() => {
|
||||
let _ = details_node
|
||||
.get()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
|
@ -138,16 +179,16 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||
div(class="status")
|
||||
}
|
||||
}
|
||||
ul(class="constraints") {
|
||||
ul(class="regulators") {
|
||||
Keyed(
|
||||
list=constraint_list,
|
||||
view=move |cst_key| view! {
|
||||
ConstraintOutlineItem(
|
||||
constraint_key=cst_key,
|
||||
list=regulator_list,
|
||||
view=move |reg_key| view! {
|
||||
RegulatorOutlineItem(
|
||||
regulator_key=reg_key,
|
||||
element_key=key
|
||||
)
|
||||
},
|
||||
key=|cst_key| cst_key.clone()
|
||||
key=|reg_key| reg_key.clone()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -155,9 +196,9 @@ 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:
|
||||
// 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/
|
||||
//
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue