From bd0982f821a0f8d4cd5d1128f4e1c72e88876831 Mon Sep 17 00:00:00 2001
From: Aaron Fenyes <aaron.fenyes@fareycircles.ooo>
Date: Fri, 27 Sep 2024 14:33:49 -0700
Subject: [PATCH] AddRemove: make a button that adds elements

In the process, switch selection storage back to `FxHashSet`, reverting
commit b3afd6f.
---
 app-proto/full-interface/Cargo.toml        |  1 +
 app-proto/full-interface/src/add_remove.rs | 68 +++++++++++++---------
 app-proto/full-interface/src/assembly.rs   | 33 ++++++++++-
 app-proto/full-interface/src/main.rs       |  5 +-
 4 files changed, 74 insertions(+), 33 deletions(-)

diff --git a/app-proto/full-interface/Cargo.toml b/app-proto/full-interface/Cargo.toml
index 7640b07..920469a 100644
--- a/app-proto/full-interface/Cargo.toml
+++ b/app-proto/full-interface/Cargo.toml
@@ -11,6 +11,7 @@ default = ["console_error_panic_hook"]
 itertools = "0.13.0"
 js-sys = "0.3.70"
 nalgebra = "0.33.0"
+rustc-hash = "2.0.0"
 slab = "0.4.9"
 sycamore = "0.9.0-beta.3"
 
diff --git a/app-proto/full-interface/src/add_remove.rs b/app-proto/full-interface/src/add_remove.rs
index e0ecf0f..f93f31f 100644
--- a/app-proto/full-interface/src/add_remove.rs
+++ b/app-proto/full-interface/src/add_remove.rs
@@ -6,16 +6,51 @@ use crate::Constraint;
 
 #[component]
 pub fn AddRemove() -> View {
-    let state = use_context::<AppState>();
-    
     view! {
         div(id="add-remove") {
             button(
-                on:click=move |_| {
+                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(
+                disabled={
+                    let state = use_context::<AppState>();
+                    state.selection.with(|sel| sel.len() != 2)
+                },
+                on:click=|_| {
+                    let state = use_context::<AppState>();
+                    let args = state.selection.with(
+                        |sel| {
+                            let arg_vec: Vec<_> = sel.into_iter().collect();
+                            (arg_vec[0].clone(), arg_vec[1].clone())
+                        }
+                    );
+                    state.assembly.insert_constraint(Constraint {
+                        args: args,
+                        rep: 0.0
+                    });
+                    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_4(
+                            console::log_5(
+                                &JsValue::from(" "),
                                 &JsValue::from(cst.args.0),
                                 &JsValue::from(cst.args.1),
                                 &JsValue::from(":"),
@@ -24,31 +59,6 @@ pub fn AddRemove() -> View {
                         }
                     });
                 }
-            ) { "+" }
-            button(
-                disabled={
-                    state.selection.with(|sel| sel.len() != 2)
-                },
-                on:click=move |_| {
-                    let args = state.selection.with(
-                        |sel| {
-                            let arg_vec: Vec<_> = sel.into_iter().collect();
-                            (arg_vec[0].clone(), arg_vec[1].clone())
-                        }
-                    );
-                    console::log_5(
-                        &JsValue::from("add constraint"),
-                        &JsValue::from(args.0),
-                        &JsValue::from(args.1),
-                        &JsValue::from(":"),
-                        &JsValue::from(0.0)
-                    );
-                    state.assembly.insert_constraint(Constraint {
-                        args: args,
-                        rep: 0.0
-                    });
-                    state.selection.update(|sel| sel.clear());
-                }
             ) { "🔗" }
         }
     }
diff --git a/app-proto/full-interface/src/assembly.rs b/app-proto/full-interface/src/assembly.rs
index 1d0a6f8..aa4355e 100644
--- a/app-proto/full-interface/src/assembly.rs
+++ b/app-proto/full-interface/src/assembly.rs
@@ -1,4 +1,5 @@
 use nalgebra::DVector;
+use rustc_hash::FxHashMap;
 use slab::Slab;
 use std::collections::BTreeSet;
 use sycamore::prelude::*;
@@ -21,18 +22,46 @@ pub struct Constraint {
 // 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>>
+    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())
+            constraints: create_signal(Slab::new()),
+            elements_by_id: create_signal(FxHashMap::default())
         }
     }
     
+    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(
+            |elts_by_id| elts_by_id.contains_key(&id)
+        ) {
+            id_num += 1;
+            id = format!("sphere{}", id_num);
+        }
+        
+        // create and insert a new element
+        let elt = Element {
+            id: id.clone(),
+            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()
+        };
+        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 insert_constraint(&self, constraint: Constraint) {
         let args = constraint.args;
         let key = self.constraints.update(|csts| csts.insert(constraint));
diff --git a/app-proto/full-interface/src/main.rs b/app-proto/full-interface/src/main.rs
index 87e06db..e867ad3 100644
--- a/app-proto/full-interface/src/main.rs
+++ b/app-proto/full-interface/src/main.rs
@@ -4,6 +4,7 @@ mod display;
 mod outline;
 
 use nalgebra::DVector;
+use rustc_hash::FxHashSet;
 use std::collections::BTreeSet;
 use sycamore::prelude::*;
 
@@ -15,14 +16,14 @@ use outline::Outline;
 #[derive(Clone)]
 struct AppState {
     assembly: Assembly,
-    selection: Signal<BTreeSet<usize>>
+    selection: Signal<FxHashSet<usize>>
 }
 
 impl AppState {
     fn new() -> AppState {
         AppState {
             assembly: Assembly::new(),
-            selection: create_signal(BTreeSet::default())
+            selection: create_signal(FxHashSet::default())
         }
     }
 }