dyna3/app-proto/src/add_remove.rs
Vectornaut 65cee1ecc2 Clean up the outline view (#19)
Clean up the source code and interface of the outline view. In addition, [fix a bug](commit/6e42681b719d7ec97c4225ca321225979bf87b56) that could cause `Assembly::realize` to react to itself under certain circumstances. Those circumstances arose, making the bug noticeable, while this branch was being written.

#### Source code

- Modularize the `Outline` component into smaller components.
- Switch from static iteration to dynamic Sycamore lists. This reduces the amount of re-rendering that happens when an element or constraint changes. It also allows constraint details to stay open or closed during constraint updates, rather than resetting to closed.
- Make `Element::index` private, as discussed [here](pulls/15#issuecomment-1816).

#### Interface

- Make constraints editable, updating the assembly realization on input. Flag constraints where the Lorentz product value doesn't parse.
- Round element vector coordinates to prevent the displayed strings from overlapping.

Note that issue #20 was created by this PR, but it will be addressed shortly.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: glen/dyna3#19
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-11-15 03:32:47 +00:00

240 lines
No EOL
8.6 KiB
Rust

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::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::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::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::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::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::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::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::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::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::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::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::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::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::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)
)
);
}
#[component]
pub fn AddRemove() -> View {
/* DEBUG */
let assembly_name = create_signal("general".to_string());
create_effect(move || {
// get name of chosen assembly
let name = assembly_name.get_clone();
console::log_1(
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
);
batch(|| {
let state = use_context::<AppState>();
let assembly = &state.assembly;
// clear state
assembly.elements.update(|elts| elts.clear());
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
state.selection.update(|sel| sel.clear());
// load assembly
match name.as_str() {
"general" => load_gen_assemb(assembly),
"low-curv" => load_low_curv_assemb(assembly),
_ => ()
};
});
});
view! {
div(id="add-remove") {
button(
on:click=|_| {
let state = use_context::<AppState>();
state.assembly.insert_new_element();
/* DEBUG */
// print updated list of elements by identifier
console::log_1(&JsValue::from("elements by identifier:"));
for (id, key) in state.assembly.elements_by_id.get_clone().iter() {
console::log_3(
&JsValue::from(" "),
&JsValue::from(id),
&JsValue::from(*key)
);
}
}
) { "+" }
button(
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
disabled={
let state = use_context::<AppState>();
state.selection.with(|sel| sel.len() != 2)
},
on:click=|_| {
let state = use_context::<AppState>();
let subjects = state.selection.with(
|sel| {
let subject_vec: Vec<_> = sel.into_iter().collect();
(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);
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,
});
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() {
console::log_5(
&JsValue::from(" "),
&JsValue::from(cst.subjects.0),
&JsValue::from(cst.subjects.1),
&JsValue::from(":"),
&JsValue::from(cst.lorentz_prod.get_untracked())
);
}
});
// 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();
if active.get() && lorentz_prod_valid.get() {
state.assembly.realize();
}
});
}
) { "🔗" }
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
option(value="general") { "General" }
option(value="low-curv") { "Low-curvature" }
option(value="empty") { "Empty" }
}
}
}
}