From e5f4d523f90161a4615c5d4c51d83cb6e2a6cb1c Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 29 Oct 2024 13:46:15 -0700 Subject: [PATCH 1/7] Update the realization when a constraint is activated Sycamore probably has a better way to do this, but this way works for now. --- app-proto/src/add_remove.rs | 10 +++++++++- app-proto/src/assembly.rs | 12 +++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 7066089..00b63f8 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -214,10 +214,11 @@ pub fn AddRemove() -> View { (arg_vec[0].clone(), arg_vec[1].clone()) } ); + let active = create_signal(true); state.assembly.insert_constraint(Constraint { args: args, rep: 0.0, - active: create_signal(true) + active: active }); state.assembly.realize(); state.selection.update(|sel| sel.clear()); @@ -236,6 +237,13 @@ pub fn AddRemove() -> View { ); } }); + + // make constraint activation trigger a realization update + create_effect(move || { + if active.get() { + state.assembly.realize(); + } + }); } ) { "🔗" } select(bind:value=assembly_name) { /* DEBUG */ diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 228357e..648d0ef 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -97,7 +97,7 @@ impl Assembly { self.elements.update(|elts| { elts[args.0].constraints.insert(key); elts[args.1].constraints.insert(key); - }) + }); } // --- realization --- @@ -116,10 +116,12 @@ impl Assembly { let mut gram_to_be = PartialMatrix::new(); self.constraints.with_untracked(|csts| { for (_, cst) in csts { - let args = cst.args; - let row = elts[args.0].index; - let col = elts[args.1].index; - gram_to_be.push_sym(row, col, cst.rep); + if cst.active.get_untracked() { + let args = cst.args; + let row = elts[args.0].index; + let col = elts[args.1].index; + gram_to_be.push_sym(row, col, cst.rep); + } } }); From e0880d2ad2d314c06fbc324bcf2bbe8aa7ba0b8d Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 29 Oct 2024 22:32:00 -0700 Subject: [PATCH 2/7] Make constraints editable --- app-proto/Cargo.toml | 1 + app-proto/main.css | 11 +++++++++-- app-proto/src/add_remove.rs | 17 +++++++++++++---- app-proto/src/assembly.rs | 8 +++++--- app-proto/src/outline.rs | 25 +++++++++++++++++++++---- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/app-proto/Cargo.toml b/app-proto/Cargo.toml index 04ea271..e5bc05e 100644 --- a/app-proto/Cargo.toml +++ b/app-proto/Cargo.toml @@ -26,6 +26,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true } version = "0.3.69" features = [ 'HtmlCanvasElement', + 'HtmlInputElement', 'Performance', 'WebGl2RenderingContext', 'WebGlBuffer', diff --git a/app-proto/main.css b/app-proto/main.css index bdbacfb..44dc7a1 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -93,7 +93,7 @@ details[open]:has(li) .elt-switch::after { display: flex; } -.elt-rep > div, .cst-rep { +.elt-rep > div { padding: 2px 0px 0px 0px; font-size: 10pt; text-align: center; @@ -104,10 +104,17 @@ details[open]:has(li) .elt-switch::after { font-style: italic; } -.cst > input { +.cst > input[type=checkbox] { margin: 0px 8px 0px 0px; } +.cst > input[type=number] { + color: #fcfcfc; + background-color: inherit; + border: 1px solid #555; + border-radius: 2px; +} + /* display */ canvas { diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 00b63f8..5435418 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -214,11 +214,14 @@ pub fn AddRemove() -> View { (arg_vec[0].clone(), arg_vec[1].clone()) } ); + let rep = create_signal(0.0); let active = create_signal(true); state.assembly.insert_constraint(Constraint { args: args, - rep: 0.0, - active: active + rep: rep, + rep_text: create_signal(String::new()), + rep_valid: create_signal(false), + active: active, }); state.assembly.realize(); state.selection.update(|sel| sel.clear()); @@ -233,13 +236,19 @@ pub fn AddRemove() -> View { &JsValue::from(cst.args.0), &JsValue::from(cst.args.1), &JsValue::from(":"), - &JsValue::from(cst.rep) + &JsValue::from(cst.rep.get_untracked()) ); } }); - // make constraint activation trigger a realization update + // update the realization when the constraint activated, or + // edited while active create_effect(move || { + rep.track(); + console::log_2( + &JsValue::from("Constraint rep updated to"), + &JsValue::from(rep.get_untracked()) + ); if active.get() { state.assembly.realize(); } diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 648d0ef..62f5405 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -22,7 +22,9 @@ pub struct Element { #[derive(Clone)] pub struct Constraint { pub args: (usize, usize), - pub rep: f64, + pub rep: Signal, + pub rep_text: Signal, + pub rep_valid: Signal, pub active: Signal } @@ -116,11 +118,11 @@ impl Assembly { let mut gram_to_be = PartialMatrix::new(); self.constraints.with_untracked(|csts| { for (_, cst) in csts { - if cst.active.get_untracked() { + if cst.active.get_untracked() && cst.rep_valid.get_untracked() { let args = cst.args; let row = elts[args.0].index; let col = elts[args.1].index; - gram_to_be.push_sym(row, col, cst.rep); + gram_to_be.push_sym(row, col, cst.rep.get_untracked()); } } }); diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 4e4de9c..d6b0390 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -1,6 +1,7 @@ use itertools::Itertools; use sycamore::{prelude::*, web::tags::div}; -use web_sys::{Element, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; +use web_sys::{Element, Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; +use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::AppState; @@ -51,8 +52,6 @@ pub fn Outline() -> View { let constrained = elt.constraints.len() > 0; let details_node = create_node_ref(); view! { - /* [TO DO] switch to integer-valued parameters whenever - that becomes possible again */ li { details(ref=details_node) { summary( @@ -138,7 +137,25 @@ pub fn Outline() -> View { li(class="cst") { input(r#type="checkbox", bind:checked=cst.active) div(class="cst-label") { (other_arg_label) } - div(class="cst-rep") { (cst.rep) } + input( + r#type="number", + step="0.01", + bind:value=cst.rep_text, + on:change=move |event: Event| { + let target: HtmlInputElement = event.target().unwrap().unchecked_into(); + let rep_valid = target.check_validity() && !target.value().is_empty(); + batch(|| { + cst.rep_valid.set(rep_valid); + if rep_valid { + console::log_2( + &JsValue::from("Constraint rep parsed to"), + &JsValue::from(target.value_as_number()) + ); + cst.rep.set(target.value_as_number()); + } + }); + } + ) } } }, From a46ef2c8d63eaca964fceea6c1bf547a89944261 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 29 Oct 2024 22:53:48 -0700 Subject: [PATCH 3/7] Work around data binding bug in number input Setting `bind:value` or `bind:valueAsNumber` for a number input seems to restrict what you can type in it. We work around this by switching to text inputs for now. We should probably switch back to number inputs if we can, though, because they let us take advantage of the browser's parsing and validation. --- app-proto/main.css | 2 +- app-proto/src/outline.rs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app-proto/main.css b/app-proto/main.css index 44dc7a1..32ae5bf 100644 --- a/app-proto/main.css +++ b/app-proto/main.css @@ -108,7 +108,7 @@ details[open]:has(li) .elt-switch::after { margin: 0px 8px 0px 0px; } -.cst > input[type=number] { +.cst > input[type=text] { color: #fcfcfc; background-color: inherit; border: 1px solid #555; diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index d6b0390..62bc529 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -138,22 +138,21 @@ pub fn Outline() -> View { input(r#type="checkbox", bind:checked=cst.active) div(class="cst-label") { (other_arg_label) } input( - r#type="number", - step="0.01", + r#type="text", bind:value=cst.rep_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); - let rep_valid = target.check_validity() && !target.value().is_empty(); - batch(|| { - cst.rep_valid.set(rep_valid); - if rep_valid { + match target.value().parse::() { + Ok(rep) => batch(|| { + cst.rep.set(rep); + cst.rep_valid.set(true); console::log_2( &JsValue::from("Constraint rep parsed to"), - &JsValue::from(target.value_as_number()) + &JsValue::from(rep) ); - cst.rep.set(target.value_as_number()); - } - }); + }), + Err(_) => cst.rep_valid.set(false) + }; } ) } From 76ad4245d5346c4e6bafac5eeee2544608452723 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 29 Oct 2024 23:43:41 -0700 Subject: [PATCH 4/7] Factor out Lorentz product input --- app-proto/src/outline.rs | 52 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 62bc529..fcf983f 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -3,11 +3,36 @@ use sycamore::{prelude::*, web::tags::div}; use web_sys::{Element, Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ -use crate::AppState; +use crate::{AppState, assembly::Constraint}; -// this component lists the elements of the assembly, showing the constraints -// on each element as a collapsible sub-list. its implementation is based on -// Kate Morley's HTML + CSS tree views: +// an editable view of the Lorentz product representing a constraint +#[component(inline_props)] +fn LorentzProductInput(constraint: Constraint) -> View { + view! { + input( + r#type="text", + bind:value=constraint.rep_text, + on:change=move |event: Event| { + let target: HtmlInputElement = event.target().unwrap().unchecked_into(); + match target.value().parse::() { + Ok(rep) => batch(|| { + constraint.rep.set(rep); + constraint.rep_valid.set(true); + console::log_2( + &JsValue::from("Constraint rep parsed to"), + &JsValue::from(rep) + ); + }), + Err(_) => constraint.rep_valid.set(false) + }; + } + ) + } +} + +// 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: // // https://iamkate.com/code/tree-views/ // @@ -137,24 +162,7 @@ pub fn Outline() -> View { li(class="cst") { input(r#type="checkbox", bind:checked=cst.active) div(class="cst-label") { (other_arg_label) } - input( - r#type="text", - bind:value=cst.rep_text, - on:change=move |event: Event| { - let target: HtmlInputElement = event.target().unwrap().unchecked_into(); - match target.value().parse::() { - Ok(rep) => batch(|| { - cst.rep.set(rep); - cst.rep_valid.set(true); - console::log_2( - &JsValue::from("Constraint rep parsed to"), - &JsValue::from(rep) - ); - }), - Err(_) => cst.rep_valid.set(false) - }; - } - ) + LorentzProductInput(constraint=cst) } } }, From c2e3c64d4a3750c03fceb789bc8111cd71a67d5c Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 30 Oct 2024 00:16:34 -0700 Subject: [PATCH 5/7] Remove debug log from Lorentz product input --- app-proto/src/outline.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index fcf983f..6497fdd 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 web_sys::{Element, Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; -use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{AppState, assembly::Constraint}; @@ -18,10 +17,6 @@ fn LorentzProductInput(constraint: Constraint) -> View { Ok(rep) => batch(|| { constraint.rep.set(rep); constraint.rep_valid.set(true); - console::log_2( - &JsValue::from("Constraint rep parsed to"), - &JsValue::from(rep) - ); }), Err(_) => constraint.rep_valid.set(false) }; From 9e31037e17d1b3f21ac95b8fa7407487a257e18f Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 30 Oct 2024 00:19:44 -0700 Subject: [PATCH 6/7] Spread web-sys imports over multiple lines --- app-proto/src/outline.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 6497fdd..8edbe07 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -1,6 +1,13 @@ use itertools::Itertools; use sycamore::{prelude::*, web::tags::div}; -use web_sys::{Element, Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast}; +use web_sys::{ + Element, + Event, + HtmlInputElement, + KeyboardEvent, + MouseEvent, + wasm_bindgen::JsCast +}; use crate::{AppState, assembly::Constraint}; From 9c191ae586369e1217b9c809b52ad7f7253c8cf4 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Wed, 30 Oct 2024 00:27:16 -0700 Subject: [PATCH 7/7] Polish log messages --- app-proto/src/add_remove.rs | 4 ++-- app-proto/src/assembly.rs | 2 +- app-proto/src/engine.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index 5435418..92ae4be 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -228,7 +228,7 @@ pub fn AddRemove() -> View { /* DEBUG */ // print updated constraint list - console::log_1(&JsValue::from("constraints:")); + console::log_1(&JsValue::from("Constraints:")); state.assembly.constraints.with(|csts| { for (_, cst) in csts.into_iter() { console::log_5( @@ -246,7 +246,7 @@ pub fn AddRemove() -> View { create_effect(move || { rep.track(); console::log_2( - &JsValue::from("Constraint rep updated to"), + &JsValue::from("Lorentz product updated to"), &JsValue::from(rep.get_untracked()) ); if active.get() { diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 62f5405..0970932 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -146,7 +146,7 @@ impl Assembly { /* DEBUG */ // log the initial configuration matrix - console::log_1(&JsValue::from("old configuration:")); + console::log_1(&JsValue::from("Old configuration:")); for j in 0..guess.nrows() { let mut row_str = String::new(); for k in 0..guess.ncols() { diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index 2971750..2978a9a 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -60,7 +60,7 @@ impl PartialMatrix { pub fn log_to_console(&self) { let PartialMatrix(entries) = self; for ent in entries { - let ent_str = format!("{} {} {}", ent.index.0, ent.index.1, ent.value); + let ent_str = format!(" {} {} {}", ent.index.0, ent.index.1, ent.value); console::log_1(&JsValue::from(ent_str.as_str())); } }