dyna3/app-proto/src/assembly.rs
Vectornaut 86fa682b31 feat: Application prototype (#14)
Creates a prototype user interface for dyna3 in the `app-proto` folder. The interface is dynamically constructed using [Sycamore](https://sycamore.dev).

The prototype includes:

  * An application state model (the `AppState` type)
    * A constraint problem model (the `Assembly` type), used in the application state
  * Two views
    * A 3D rendering of the assembly (the `Display` component)
    * A list of elements and constraints (the `Outline` component)

The following features confirm that the views can reflect and send input to the model:

  * You can select elements by clicking and shift-clicking them in the outline. The selected elements are highlighted in the display.
  * You can add elements using a button above the outline. The new elements appear in the display.

Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Reviewed-on: glen/dyna3#14
Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net>
Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-10-21 23:38:27 +00:00

93 lines
No EOL
2.8 KiB
Rust

use nalgebra::DVector;
use rustc_hash::FxHashMap;
use slab::Slab;
use std::collections::BTreeSet;
use sycamore::prelude::*;
#[derive(Clone, PartialEq)]
pub struct Element {
pub id: String,
pub label: String,
pub color: [f32; 3],
pub rep: DVector<f64>,
pub constraints: BTreeSet<usize>
}
#[derive(Clone)]
pub struct Constraint {
pub args: (usize, usize),
pub rep: f64,
pub active: Signal<bool>
}
// a complete, view-independent description of an assembly
#[derive(Clone)]
pub struct Assembly {
// elements and constraints
pub elements: Signal<Slab<Element>>,
pub constraints: Signal<Slab<Constraint>>,
// indexing
pub elements_by_id: Signal<FxHashMap<String, usize>>
}
impl Assembly {
pub fn new() -> Assembly {
Assembly {
elements: create_signal(Slab::new()),
constraints: create_signal(Slab::new()),
elements_by_id: create_signal(FxHashMap::default())
}
}
// insert an element into the assembly without checking whether we already
// have an element with the same identifier. any element that does have the
// same identifier will get kicked out of the `elements_by_id` index
fn insert_element_unchecked(&self, elt: Element) {
let id = elt.id.clone();
let key = self.elements.update(|elts| elts.insert(elt));
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
}
pub fn try_insert_element(&self, elt: Element) -> bool {
let can_insert = self.elements_by_id.with_untracked(
|elts_by_id| !elts_by_id.contains_key(&elt.id)
);
if can_insert {
self.insert_element_unchecked(elt);
}
can_insert
}
pub fn insert_new_element(&self) {
// find the next unused identifier in the default sequence
let mut id_num = 1;
let mut id = format!("sphere{}", id_num);
while self.elements_by_id.with_untracked(
|elts_by_id| elts_by_id.contains_key(&id)
) {
id_num += 1;
id = format!("sphere{}", id_num);
}
// create and insert a new element
self.insert_element_unchecked(
Element {
id: id,
label: format!("Sphere {}", id_num),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
constraints: BTreeSet::default()
}
);
}
pub fn insert_constraint(&self, constraint: Constraint) {
let args = constraint.args;
let key = self.constraints.update(|csts| csts.insert(constraint));
self.elements.update(|elts| {
elts[args.0].constraints.insert(key);
elts[args.1].constraints.insert(key);
})
}
}