Require regulators to have valid specifications

When an invalid specification is entered into a regulator input, keep it
confined to that input. Reset a regulator input by pressing *escape*.
This commit is contained in:
Aaron Fenyes 2025-02-18 01:27:11 -08:00
parent fef4127f69
commit 302d93638d
3 changed files with 49 additions and 26 deletions

View file

@ -133,28 +133,25 @@ details[open]:has(li) .element-switch::after {
font-size: 10pt; font-size: 10pt;
} }
.regulator.invalid-constraint { .regulator-input {
color: var(--text-invalid);
}
.regulator > input {
color: inherit; color: inherit;
background-color: inherit; background-color: inherit;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 2px; border-radius: 2px;
} }
.regulator > input::placeholder { .regulator-input::placeholder {
color: inherit; color: inherit;
opacity: 54%; opacity: 54%;
font-style: italic; font-style: italic;
} }
.regulator.valid-constraint > input { .regulator-input.constraint {
background-color: var(--display-background); background-color: var(--display-background);
} }
.regulator.invalid-constraint > input { .regulator-input.invalid {
color: var(--text-invalid);
border-color: var(--border-invalid); border-color: var(--border-invalid);
} }
@ -166,7 +163,7 @@ details[open]:has(li) .element-switch::after {
font-style: normal; font-style: normal;
} }
.invalid-constraint > .status::after, details:has(.invalid-constraint):not([open]) .status::after { .regulator:has(.invalid) > .status::after, details:has(.invalid):not([open]) .status::after {
content: '⚠'; content: '⚠';
color: var(--text-invalid); color: var(--text-invalid);
} }

View file

@ -111,6 +111,7 @@ impl Element {
} }
} }
// `set_point_spec` must always be a valid specification of `set_point`
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Regulator { pub struct Regulator {
pub subjects: (ElementKey, ElementKey), pub subjects: (ElementKey, ElementKey),
@ -119,12 +120,6 @@ pub struct Regulator {
pub set_point_spec: Signal<String> pub set_point_spec: Signal<String>
} }
impl Regulator {
pub fn has_no_set_point_spec(&self) -> bool {
self.set_point_spec.with(|spec| spec.is_empty())
}
}
// the velocity is expressed in uniform coordinates // the velocity is expressed in uniform coordinates
pub struct ElementMotion<'a> { pub struct ElementMotion<'a> {
pub key: ElementKey, pub key: ElementKey,

View file

@ -19,17 +19,55 @@ use crate::{
// an editable view of a regulator // an editable view of a regulator
#[component(inline_props)] #[component(inline_props)]
fn RegulatorInput(regulator: Regulator) -> View { fn RegulatorInput(regulator: Regulator) -> View {
let valid = create_signal(true);
let value = create_signal(regulator.set_point_spec.get_clone_untracked()); let value = create_signal(regulator.set_point_spec.get_clone_untracked());
create_effect(move || value.set(regulator.set_point_spec.get_clone()));
// this closure resets the input value to the regulator's set point
// specification, which is always a valid specification
let reset_value = move || {
batch(|| {
valid.set(true);
value.set(regulator.set_point_spec.get_clone());
})
};
// reset the input value whenever the regulator's set point specification
// is updated
create_effect(reset_value);
view! { view! {
input( input(
r#type="text", r#type="text",
class=move || {
if valid.get() {
match regulator.set_point.get() {
Some(_) => "regulator-input constraint",
None => "regulator-input"
}
} else {
"regulator-input invalid"
}
},
placeholder=regulator.measurement.with(|result| result.to_string()), placeholder=regulator.measurement.with(|result| result.to_string()),
bind:value=value, bind:value=value,
on:change=move |_| { on:change=move |_| {
let value_val = value.get_clone_untracked(); let value_val = value.get_clone_untracked();
regulator.set_point.set(value_val.parse::<f64>().ok()); match value_val.parse::<f64>() {
regulator.set_point_spec.set(value_val); Err(_) if !value_val.is_empty() => valid.set(false),
set_pt => batch(|| {
regulator.set_point.set(set_pt.ok());
regulator.set_point_spec.set(value_val);
valid.set(true);
})
};
},
on:keydown={
move |event: KeyboardEvent| {
match event.key().as_str() {
"Escape" => reset_value(),
_ => ()
}
}
} }
) )
} }
@ -47,15 +85,8 @@ fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) ->
regulator.subjects.0 regulator.subjects.0
}; };
let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
let class = create_memo(move || {
match regulator.set_point.get() {
None if regulator.has_no_set_point_spec() => "regulator",
None => "regulator invalid-constraint",
Some(_) => "regulator valid-constraint"
}
});
view! { view! {
li(class=class.get()) { li(class="regulator") {
div(class="regulator-label") { (other_subject_label) } div(class="regulator-label") { (other_subject_label) }
div(class="regulator-type") { "Inversive distance" } div(class="regulator-type") { "Inversive distance" }
RegulatorInput(regulator=regulator) RegulatorInput(regulator=regulator)