Clean up the outline view #19
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "sketch-outline"
|
||||
name = "dyna3"
|
||||
version = "0.1.0"
|
||||
authors = ["Aaron"]
|
||||
authors = ["Aaron Fenyes", "Glen Whitney"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
|
@ -2,8 +2,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Sketch outline</title>
|
||||
<title>dyna3</title>
|
||||
<link data-trunk rel="css" href="main.css"/>
|
||||
<link href="https://fonts.bunny.net/css?family=fira-sans:ital,wght@0,400;1,400&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.bunny.net/css?family=noto-emoji:wght@400&text=%f0%9f%94%97%e2%9a%a0&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
@ -1,7 +1,20 @@
|
||||
:root {
|
||||
--text: #fcfcfc; /* almost white */
|
||||
--text-bright: white;
|
||||
--text-invalid: #f58fc2; /* bright pink */
|
||||
--border: #555; /* light gray */
|
||||
--border-focus: #aaa; /* bright gray */
|
||||
--border-invalid: #70495c; /* dusky pink */
|
||||
--selection-highlight: #444; /* medium gray */
|
||||
--page-background: #222; /* dark gray */
|
||||
--display-background: #020202; /* almost black */
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
color: #fcfcfc;
|
||||
background-color: #222;
|
||||
color: var(--text);
|
||||
background-color: var(--page-background);
|
||||
font-family: 'Fira Sans', sans-serif;
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
@ -16,7 +29,7 @@ body {
|
||||
padding: 0px;
|
||||
border-width: 0px 1px 0px 0px;
|
||||
border-style: solid;
|
||||
border-color: #555;
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
/* add-remove */
|
||||
@ -33,6 +46,15 @@ body {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
/* KLUDGE */
|
||||
/*
|
||||
for convenience, we're using emoji as temporary icons for some buttons. these
|
||||
buttons need to be displayed in an emoji font
|
||||
*/
|
||||
#add-remove > button.emoji {
|
||||
font-family: 'Noto Emoji', sans-serif;
|
||||
}
|
||||
|
||||
/* outline */
|
||||
|
||||
#outline {
|
||||
@ -51,81 +73,103 @@ summary {
|
||||
}
|
||||
|
||||
summary.selected {
|
||||
color: #fff;
|
||||
background-color: #444;
|
||||
color: var(--text-bright);
|
||||
background-color: var(--selection-highlight);
|
||||
}
|
||||
|
||||
summary > div, .cst {
|
||||
summary > div, .constraint {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.elt, .cst {
|
||||
.element, .constraint {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.elt-switch {
|
||||
.element-switch {
|
||||
width: 18px;
|
||||
padding-left: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
details:has(li) .elt-switch::after {
|
||||
details:has(li) .element-switch::after {
|
||||
content: '▸';
|
||||
}
|
||||
|
||||
details[open]:has(li) .elt-switch::after {
|
||||
details[open]:has(li) .element-switch::after {
|
||||
content: '▾';
|
||||
}
|
||||
|
||||
.elt-label {
|
||||
.element-label {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cst-label {
|
||||
.constraint-label {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.elt-rep {
|
||||
.element-representation {
|
||||
glen marked this conversation as resolved
Outdated
|
||||
display: flex;
|
||||
glen marked this conversation as resolved
Outdated
glen
commented
I don't really mind I don't really mind `#222` and `#f0f0f0` sort of thing for various greys, because they are pretty easy to understand, but my mental hex-to-hue converter is otherwise not particularly good. Colors also often carry semantic content and/or play specific roles. So please don't use non-grey bare hex colors in css. Give them symbolic names that convey role (presuming there is one) and/or hue as well if feasible (or if not, comment the hue where the symbol is defined) and then use them symbolically, presumably with `var(...)` CSS references. For example, in Numberscope several colors are defined in App.vue and then used throughout the components with `var(...)` references. Thanks.
Vectornaut
commented
Done (in commit Done (in commit 0c8022d). I've named the color variables according to their roles, with cues about the intended color limited to brightness descriptions. There are comments describing the colors on the lines where the color variables are defined. I'm hoping that this will make it easier to keep the color descriptions up to date with the actual colors.
glen
commented
Those are excellent symbolic names and comments. If we are ever pulling in an external package that has css variables of its own, we will need to add a prefix (e.g., --text-bright becomes --dyna-text-bright), but they're good for now. Those are excellent symbolic names and comments. If we are ever pulling in an external package that has css variables of its own, we will need to add a prefix (e.g., --text-bright becomes --dyna-text-bright), but they're good for now.
|
||||
}
|
||||
|
||||
.elt-rep > div {
|
||||
.element-representation > div {
|
||||
padding: 2px 0px 0px 0px;
|
||||
font-size: 10pt;
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: right;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.cst {
|
||||
.constraint {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cst > input[type=checkbox] {
|
||||
.constraint.invalid {
|
||||
color: var(--text-invalid);
|
||||
}
|
||||
|
||||
.constraint > input[type=checkbox] {
|
||||
margin: 0px 8px 0px 0px;
|
||||
}
|
||||
|
||||
.cst > input[type=text] {
|
||||
color: #fcfcfc;
|
||||
.constraint > input[type=text] {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border: 1px solid #555;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.constraint.invalid > input[type=text] {
|
||||
border-color: var(--border-invalid);
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 20px;
|
||||
padding-left: 4px;
|
||||
text-align: center;
|
||||
font-family: 'Noto Emoji';
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
|
||||
content: '⚠';
|
||||
color: var(--text-invalid);
|
||||
}
|
||||
|
||||
/* display */
|
||||
|
||||
canvas {
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
background-color: #020202;
|
||||
border: 1px solid #555;
|
||||
background-color: var(--display-background);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
canvas:focus {
|
||||
border-color: #aaa;
|
||||
border-color: var(--border-focus);
|
||||
}
|
@ -1,155 +1,130 @@
|
||||
use std::collections::BTreeSet; /* DEBUG */
|
||||
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
|
||||
glen marked this conversation as resolved
Outdated
glen
commented
Shouldn't these debug functions be in some kind of a test and/or conditionally compiled only if one is testing? They look a bit, um, specific for functions that would actually be compiled into a webpage... Shouldn't these debug functions be in some kind of a test and/or conditionally compiled only if one is testing? They look a bit, um, specific for functions that would actually be compiled into a webpage...
Vectornaut
commented
These functions build the test assemblies we've been playing with: the "General" assembly that appears when you load the page, and the "Low-curvature" assembly that you can pick from the drop-down menu. They're always compiled because I've never wanted to build the app with the test configurations removed. It would useful to build a more formal test assembly system at some point. That task seems pretty self-contained, so I think it should be its own PR. I also think it'll be easier once we have a system for saving and loading assemblies. These functions build the test assemblies we've been playing with: the "General" assembly that appears when you load the page, and the "Low-curvature" assembly that you can pick from the drop-down menu. They're always compiled because I've never wanted to build the app with the test configurations removed.
It would useful to build a more formal test assembly system at some point. That task seems pretty self-contained, so I think it should be its own PR. I also think it'll be easier once we have a system for saving and loading assemblies.
glen
commented
Ah, so they are not debug functions; in fact they are the current "Assembly gallery" or "Assembly examples". So just rename/re-comment things to clarify their current role, and add a way to easily/quickly get to the empty assembly as well (or put an issue to do that in a future PR), and all should be well. Ah, so they are not debug functions; in fact they are the current "Assembly gallery" or "Assembly examples". So just rename/re-comment things to clarify their current role, and add a way to easily/quickly get to the empty assembly as well (or put an issue to do that in a future PR), and all should be well.
Vectornaut
commented
Done (in commit Done (in commit a48fef3).
glen
commented
It's also quite plausible to me that some examples will move into the dyna3 equivalent of a "featured gallery", but that idea may not deserve to be in a comment at this point. It's also quite plausible to me that some examples will move into the dyna3 equivalent of a "featured gallery", but that idea may not deserve to be in a comment at this point.
|
||||
// built a more formal test assembly system
|
||||
fn load_gen_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Element {
|
||||
id: String::from("gemini_a"),
|
||||
label: String::from("Castor"),
|
||||
color: [1.00_f32, 0.25_f32, 0.00_f32],
|
||||
representation: engine::sphere(0.5, 0.5, 0.0, 1.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("gemini_b"),
|
||||
label: String::from("Pollux"),
|
||||
color: [0.00_f32, 0.25_f32, 1.00_f32],
|
||||
representation: engine::sphere(-0.5, -0.5, 0.0, 1.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("ursa_major"),
|
||||
label: String::from("Ursa major"),
|
||||
color: [0.25_f32, 0.00_f32, 1.00_f32],
|
||||
representation: engine::sphere(-0.5, 0.5, 0.0, 0.75),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("ursa_minor"),
|
||||
label: String::from("Ursa minor"),
|
||||
color: [0.25_f32, 1.00_f32, 0.00_f32],
|
||||
representation: engine::sphere(0.5, -0.5, 0.0, 0.5),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("moon_deimos"),
|
||||
label: String::from("Deimos"),
|
||||
color: [0.75_f32, 0.75_f32, 0.00_f32],
|
||||
representation: engine::sphere(0.0, 0.15, 1.0, 0.25),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("moon_phobos"),
|
||||
label: String::from("Phobos"),
|
||||
color: [0.00_f32, 0.75_f32, 0.50_f32],
|
||||
representation: engine::sphere(0.0, -0.15, -1.0, 0.25),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "central".to_string(),
|
||||
label: "Central".to_string(),
|
||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||
representation: engine::sphere(0.0, 0.0, 0.0, 1.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "assemb_plane".to_string(),
|
||||
label: "Assembly plane".to_string(),
|
||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||
representation: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "side1".to_string(),
|
||||
label: "Side 1".to_string(),
|
||||
color: [1.00_f32, 0.00_f32, 0.25_f32],
|
||||
representation: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "side2".to_string(),
|
||||
label: "Side 2".to_string(),
|
||||
color: [0.25_f32, 1.00_f32, 0.00_f32],
|
||||
representation: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "side3".to_string(),
|
||||
label: "Side 3".to_string(),
|
||||
color: [0.00_f32, 0.25_f32, 1.00_f32],
|
||||
representation: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "corner1".to_string(),
|
||||
label: "Corner 1".to_string(),
|
||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||
representation: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: "corner2".to_string(),
|
||||
label: "Corner 2".to_string(),
|
||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||
representation: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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 {
|
||||
id: String::from("corner3"),
|
||||
label: String::from("Corner 3"),
|
||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||
representation: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
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)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -202,6 +177,7 @@ pub fn AddRemove() -> View {
|
||||
}
|
||||
glen marked this conversation as resolved
glen
commented
Please elaborate all instances of KLUDGE Please elaborate all instances of KLUDGE
Vectornaut
commented
Done (in commit Done (in commit 2b083be).
|
||||
) { "+" }
|
||||
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)
|
||||
@ -215,15 +191,15 @@ pub fn AddRemove() -> View {
|
||||
}
|
||||
);
|
||||
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: create_signal(false),
|
||||
lorentz_prod_valid: lorentz_prod_valid,
|
||||
active: active,
|
||||
});
|
||||
state.assembly.realize();
|
||||
state.selection.update(|sel| sel.clear());
|
||||
|
||||
/* DEBUG */
|
||||
@ -241,23 +217,23 @@ pub fn AddRemove() -> View {
|
||||
}
|
||||
});
|
||||
|
||||
// update the realization when the constraint activated, or
|
||||
// edited while active
|
||||
// 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();
|
||||
console::log_2(
|
||||
&JsValue::from("Lorentz product updated to"),
|
||||
&JsValue::from(lorentz_prod.get_untracked())
|
||||
);
|
||||
if active.get() {
|
||||
if active.get() && lorentz_prod_valid.get() {
|
||||
state.assembly.realize();
|
||||
}
|
||||
});
|
||||
}
|
||||
) { "🔗" }
|
||||
select(bind:value=assembly_name) { /* DEBUG */
|
||||
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
|
||||
option(value="general") { "General" }
|
||||
option(value="low-curv") { "Low-curvature" }
|
||||
option(value="empty") { "Empty" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,33 @@ pub struct Element {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub color: ElementColor,
|
||||
pub representation: DVector<f64>,
|
||||
pub constraints: BTreeSet<ConstraintKey>,
|
||||
pub representation: Signal<DVector<f64>>,
|
||||
pub constraints: Signal<BTreeSet<ConstraintKey>>,
|
||||
|
||||
// the configuration matrix column index that was assigned to this element
|
||||
// last time the assembly was realized
|
||||
/* TO DO */
|
||||
// this is public, as a kludge, because `Element` doesn't have a constructor
|
||||
// yet. it should be made private as soon as the constructor is written
|
||||
pub index: usize
|
||||
column_index: usize
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn new(
|
||||
id: String,
|
||||
label: String,
|
||||
color: ElementColor,
|
||||
representation: DVector<f64>
|
||||
) -> Element {
|
||||
Element {
|
||||
id: id,
|
||||
label: label,
|
||||
color: color,
|
||||
representation: create_signal(representation),
|
||||
constraints: create_signal(BTreeSet::default()),
|
||||
column_index: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Constraint {
|
||||
pub subjects: (ElementKey, ElementKey),
|
||||
@ -92,24 +108,23 @@ impl Assembly {
|
||||
|
||||
// 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],
|
||||
representation: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
|
||||
constraints: BTreeSet::default(),
|
||||
index: 0
|
||||
}
|
||||
Element::new(
|
||||
id,
|
||||
format!("Sphere {}", id_num),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn insert_constraint(&self, constraint: Constraint) {
|
||||
let subjects = constraint.subjects;
|
||||
let key = self.constraints.update(|csts| csts.insert(constraint));
|
||||
self.elements.update(|elts| {
|
||||
elts[subjects.0].constraints.insert(key);
|
||||
elts[subjects.1].constraints.insert(key);
|
||||
});
|
||||
let subject_constraints = self.elements.with(
|
||||
|elts| (elts[subjects.0].constraints, elts[subjects.1].constraints)
|
||||
);
|
||||
subject_constraints.0.update(|csts| csts.insert(key));
|
||||
subject_constraints.1.update(|csts| csts.insert(key));
|
||||
}
|
||||
|
||||
// --- realization ---
|
||||
@ -118,7 +133,7 @@ impl Assembly {
|
||||
// index the elements
|
||||
self.elements.update_silent(|elts| {
|
||||
for (index, (_, elt)) in elts.into_iter().enumerate() {
|
||||
elt.index = index;
|
||||
elt.column_index = index;
|
||||
}
|
||||
});
|
||||
|
||||
@ -130,8 +145,8 @@ impl Assembly {
|
||||
for (_, cst) in csts {
|
||||
if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() {
|
||||
let subjects = cst.subjects;
|
||||
let row = elts[subjects.0].index;
|
||||
let col = elts[subjects.1].index;
|
||||
let row = elts[subjects.0].column_index;
|
||||
let col = elts[subjects.1].column_index;
|
||||
gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked());
|
||||
}
|
||||
}
|
||||
@ -141,9 +156,9 @@ impl Assembly {
|
||||
// Gram matrix
|
||||
let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len());
|
||||
for (_, elt) in elts {
|
||||
let index = elt.index;
|
||||
let index = elt.column_index;
|
||||
gram_to_be.push_sym(index, index, 1.0);
|
||||
guess_to_be.set_column(index, &elt.representation);
|
||||
guess_to_be.set_column(index, &elt.representation.get_clone_untracked());
|
||||
}
|
||||
|
||||
(gram_to_be, guess_to_be)
|
||||
@ -185,11 +200,11 @@ impl Assembly {
|
||||
|
||||
if success {
|
||||
// read out the solution
|
||||
self.elements.update(|elts| {
|
||||
for (_, elt) in elts.iter_mut() {
|
||||
elt.representation.set_column(0, &config.column(elt.index));
|
||||
}
|
||||
});
|
||||
for (_, elt) in self.elements.get_clone_untracked() {
|
||||
elt.representation.update(
|
||||
|rep| rep.set_column(0, &config.column(elt.column_index))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -103,7 +103,11 @@ pub fn Display() -> View {
|
||||
// change listener
|
||||
let scene_changed = create_signal(true);
|
||||
create_effect(move || {
|
||||
state.assembly.elements.track();
|
||||
state.assembly.elements.with(|elts| {
|
||||
for (_, elt) in elts {
|
||||
elt.representation.track();
|
||||
}
|
||||
});
|
||||
state.selection.track();
|
||||
scene_changed.set(true);
|
||||
});
|
||||
@ -295,25 +299,40 @@ pub fn Display() -> View {
|
||||
let assembly_to_world = &location * &orientation;
|
||||
|
||||
// get the assembly
|
||||
let elements = state.assembly.elements.get_clone();
|
||||
let element_iter = (&elements).into_iter();
|
||||
let reps_world: Vec<_> = element_iter.clone().map(
|
||||
|(_, elt)| &assembly_to_world * &elt.representation
|
||||
).collect();
|
||||
let colors: Vec<_> = element_iter.clone().map(|(key, elt)|
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
elt.color.map(|ch| 0.2 + 0.8*ch)
|
||||
} else {
|
||||
elt.color
|
||||
}
|
||||
).collect();
|
||||
let highlights: Vec<_> = element_iter.map(|(key, _)|
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
1.0_f32
|
||||
} else {
|
||||
HIGHLIGHT
|
||||
}
|
||||
).collect();
|
||||
let (
|
||||
elt_cnt,
|
||||
reps_world,
|
||||
colors,
|
||||
highlights
|
||||
) = state.assembly.elements.with(|elts| {
|
||||
(
|
||||
// number of elements
|
||||
elts.len() as i32,
|
||||
|
||||
// representation vectors in world coordinates
|
||||
elts.iter().map(
|
||||
|(_, elt)| elt.representation.with(|rep| &assembly_to_world * rep)
|
||||
).collect::<Vec<_>>(),
|
||||
|
||||
// colors
|
||||
elts.iter().map(|(key, elt)| {
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
elt.color.map(|ch| 0.2 + 0.8*ch)
|
||||
} else {
|
||||
elt.color
|
||||
}
|
||||
}).collect::<Vec<_>>(),
|
||||
|
||||
// highlight levels
|
||||
elts.iter().map(|(key, _)| {
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
1.0_f32
|
||||
} else {
|
||||
HIGHLIGHT
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
)
|
||||
});
|
||||
|
||||
// set the resolution
|
||||
let width = canvas.width() as f32;
|
||||
@ -322,7 +341,7 @@ pub fn Display() -> View {
|
||||
ctx.uniform1f(shortdim_loc.as_ref(), width.min(height));
|
||||
|
||||
// pass the assembly
|
||||
ctx.uniform1i(sphere_cnt_loc.as_ref(), elements.len() as i32);
|
||||
ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt);
|
||||
for n in 0..reps_world.len() {
|
||||
let v = &reps_world[n];
|
||||
ctx.uniform3f(
|
||||
|
@ -1,7 +1,6 @@
|
||||
use itertools::Itertools;
|
||||
use sycamore::{prelude::*, web::tags::div};
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{
|
||||
Element,
|
||||
Event,
|
||||
HtmlInputElement,
|
||||
KeyboardEvent,
|
||||
@ -9,7 +8,7 @@ use web_sys::{
|
||||
wasm_bindgen::JsCast
|
||||
};
|
||||
|
||||
use crate::{AppState, assembly::Constraint};
|
||||
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
|
||||
|
||||
// an editable view of the Lorentz product representing a constraint
|
||||
#[component(inline_props)]
|
||||
@ -32,6 +31,143 @@ fn LorentzProductInput(constraint: Constraint) -> View {
|
||||
}
|
||||
}
|
||||
|
||||
// a list item that shows a constraint in an outline view of an element
|
||||
#[component(inline_props)]
|
||||
fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View {
|
||||
let state = use_context::<AppState>();
|
||||
let assembly = &state.assembly;
|
||||
let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone());
|
||||
let other_subject = if constraint.subjects.0 == element_key {
|
||||
constraint.subjects.1
|
||||
} else {
|
||||
constraint.subjects.0
|
||||
};
|
||||
let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
|
||||
let class = constraint.lorentz_prod_valid.map(
|
||||
|&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" }
|
||||
);
|
||||
view! {
|
||||
li(class=class.get()) {
|
||||
input(r#type="checkbox", bind:checked=constraint.active)
|
||||
div(class="constraint-label") { (other_subject_label) }
|
||||
LorentzProductInput(constraint=constraint)
|
||||
div(class="status")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a list item that shows an element in an outline view of an assembly
|
||||
#[component(inline_props)]
|
||||
fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
||||
let state = use_context::<AppState>();
|
||||
let class = state.selection.map(
|
||||
move |sel| if sel.contains(&key) { "selected" } else { "" }
|
||||
);
|
||||
let label = element.label.clone();
|
||||
let rep_components = element.representation.map(
|
||||
|rep| rep.iter().map(
|
||||
|u| format!("{:.3}", u).replace("-", "\u{2212}")
|
||||
).collect()
|
||||
);
|
||||
let constrained = element.constraints.map(|csts| csts.len() > 0);
|
||||
let constraint_list = element.constraints.map(
|
||||
|csts| csts.clone().into_iter().collect()
|
||||
);
|
||||
let details_node = create_node_ref();
|
||||
view! {
|
||||
li {
|
||||
details(ref=details_node) {
|
||||
summary(
|
||||
class=class.get(),
|
||||
on:keydown={
|
||||
move |event: KeyboardEvent| {
|
||||
match event.key().as_str() {
|
||||
glen marked this conversation as resolved
glen
commented
What's the "native" form of event.key()? Seems a pity to convert it to a str just to match it against three options. Can't constants of the actual values of the three options (before str conversion), perhaps defined above if need be, be used in place? What's the "native" form of event.key()? Seems a pity to convert it to a str just to match it against three options. Can't constants of the actual values of the three options (before str conversion), perhaps defined above if need be, be used in place?
Vectornaut
commented
The The [`key`](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html#method.key) method returns a [`String`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html) (a heap-allocated, variable-length string object). Each of the string literals we're matching with is a [`&str`](https://doc.rust-lang.org/nightly/core/primitive.str.html) (a reference to a "string slice," which seems to be a pointer to a fixed-length string of bytes). An explicit conversion is required, and [`as_str`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html#method.as_str) seems to be the [standard way](https://stackoverflow.com/questions/25383488/how-to-match-a-string-against-string-literals?rq=3) to do it.
glen
commented
Oh, gotcha. A Rustism I was not yet used to. Hope you can see why it looked odd. Oh, gotcha. A Rustism I was not yet used to. Hope you can see why it looked odd.
Vectornaut
commented
Yup. Stuff like Yup. Stuff like `"hello".to_string()` looked even weirder to me at first, but the Rust book authors seem to consider it the most idiomatic way of going in the other direction...
glen
commented
Well yes in fact that looks so weird that I believe that in husht we will implement a labeled-literal syntax like Well yes in fact that looks so weird that I believe that in husht we will implement a labeled-literal syntax like `s"hello"` for exactly this. Note that room has been left in the Rust syntax for such a thing, it's just not part of the language (yet, anyway).
|
||||
"Enter" => {
|
||||
if event.shift_key() {
|
||||
state.selection.update(|sel| {
|
||||
if !sel.remove(&key) {
|
||||
sel.insert(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.selection.update(|sel| {
|
||||
sel.clear();
|
||||
sel.insert(key);
|
||||
});
|
||||
}
|
||||
event.prevent_default();
|
||||
},
|
||||
"ArrowRight" if constrained.get() => {
|
||||
let _ = details_node
|
||||
.get()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.set_attribute("open", "");
|
||||
},
|
||||
"ArrowLeft" => {
|
||||
let _ = details_node
|
||||
.get()
|
||||
.unchecked_into::<web_sys::Element>()
|
||||
.remove_attribute("open");
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
div(
|
||||
class="element-switch",
|
||||
on:click=|event: MouseEvent| event.stop_propagation()
|
||||
)
|
||||
div(
|
||||
class="element",
|
||||
on:click={
|
||||
move |event: MouseEvent| {
|
||||
if event.shift_key() {
|
||||
state.selection.update(|sel| {
|
||||
if !sel.remove(&key) {
|
||||
sel.insert(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.selection.update(|sel| {
|
||||
sel.clear();
|
||||
sel.insert(key);
|
||||
});
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
) {
|
||||
div(class="element-label") { (label) }
|
||||
div(class="element-representation") {
|
||||
Indexed(
|
||||
list=rep_components,
|
||||
view=|coord_str| view! {
|
||||
div { (coord_str) }
|
||||
}
|
||||
)
|
||||
}
|
||||
div(class="status")
|
||||
}
|
||||
}
|
||||
ul(class="constraints") {
|
||||
Keyed(
|
||||
list=constraint_list,
|
||||
view=move |cst_key| view! {
|
||||
ConstraintOutlineItem(
|
||||
constraint_key=cst_key,
|
||||
element_key=key
|
||||
)
|
||||
},
|
||||
key=|cst_key| cst_key.clone()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
@ -40,15 +176,16 @@ fn LorentzProductInput(constraint: Constraint) -> View {
|
||||
//
|
||||
#[component]
|
||||
pub fn Outline() -> View {
|
||||
// sort the elements alphabetically by ID
|
||||
let elements_sorted = create_memo(|| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.elements
|
||||
.get_clone()
|
||||
let state = use_context::<AppState>();
|
||||
|
||||
// list the elements alphabetically by ID
|
||||
let element_list = state.assembly.elements.map(
|
||||
|elts| elts
|
||||
.clone()
|
||||
.into_iter()
|
||||
.sorted_by_key(|(_, elt)| elt.id.clone())
|
||||
.collect()
|
||||
});
|
||||
);
|
||||
|
||||
view! {
|
||||
ul(
|
||||
@ -59,128 +196,11 @@ pub fn Outline() -> View {
|
||||
}
|
||||
) {
|
||||
Keyed(
|
||||
list=elements_sorted,
|
||||
view=|(key, elt)| {
|
||||
let state = use_context::<AppState>();
|
||||
let class = create_memo({
|
||||
move || {
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
});
|
||||
let label = elt.label.clone();
|
||||
let rep_components = elt.representation.iter().map(|u| {
|
||||
let u_coord = u.to_string().replace("-", "\u{2212}");
|
||||
View::from(div().children(u_coord))
|
||||
}).collect::<Vec<_>>();
|
||||
let constrained = elt.constraints.len() > 0;
|
||||
let details_node = create_node_ref();
|
||||
view! {
|
||||
li {
|
||||
details(ref=details_node) {
|
||||
summary(
|
||||
class=class.get(),
|
||||
on:keydown={
|
||||
move |event: KeyboardEvent| {
|
||||
match event.key().as_str() {
|
||||
"Enter" => {
|
||||
if event.shift_key() {
|
||||
state.selection.update(|sel| {
|
||||
if !sel.remove(&key) {
|
||||
sel.insert(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.selection.update(|sel| {
|
||||
sel.clear();
|
||||
sel.insert(key);
|
||||
});
|
||||
}
|
||||
event.prevent_default();
|
||||
},
|
||||
"ArrowRight" if constrained => {
|
||||
let _ = details_node
|
||||
.get()
|
||||
.unchecked_into::<Element>()
|
||||
.set_attribute("open", "");
|
||||
},
|
||||
"ArrowLeft" => {
|
||||
let _ = details_node
|
||||
.get()
|
||||
.unchecked_into::<Element>()
|
||||
.remove_attribute("open");
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
div(
|
||||
class="elt-switch",
|
||||
on:click=|event: MouseEvent| event.stop_propagation()
|
||||
)
|
||||
div(
|
||||
class="elt",
|
||||
on:click={
|
||||
move |event: MouseEvent| {
|
||||
if event.shift_key() {
|
||||
state.selection.update(|sel| {
|
||||
if !sel.remove(&key) {
|
||||
sel.insert(key);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.selection.update(|sel| {
|
||||
sel.clear();
|
||||
sel.insert(key);
|
||||
});
|
||||
}
|
||||
event.stop_propagation();
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
) {
|
||||
div(class="elt-label") { (label) }
|
||||
div(class="elt-rep") { (rep_components) }
|
||||
}
|
||||
}
|
||||
ul(class="constraints") {
|
||||
Keyed(
|
||||
list=elt.constraints.into_iter().collect::<Vec<_>>(),
|
||||
view=move |c_key| {
|
||||
let c_state = use_context::<AppState>();
|
||||
let assembly = &c_state.assembly;
|
||||
let cst = assembly.constraints.with(|csts| csts[c_key].clone());
|
||||
let other_arg = if cst.subjects.0 == key {
|
||||
cst.subjects.1
|
||||
} else {
|
||||
cst.subjects.0
|
||||
};
|
||||
let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
|
||||
view! {
|
||||
li(class="cst") {
|
||||
input(r#type="checkbox", bind:checked=cst.active)
|
||||
div(class="cst-label") { (other_arg_label) }
|
||||
LorentzProductInput(constraint=cst)
|
||||
}
|
||||
}
|
||||
},
|
||||
key=|c_key| c_key.clone()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list=element_list,
|
||||
view=|(key, elt)| view! {
|
||||
ElementOutlineItem(key=key, element=elt)
|
||||
},
|
||||
key=|(key, elt)| (
|
||||
key.clone(),
|
||||
elt.id.clone(),
|
||||
elt.label.clone(),
|
||||
elt.constraints.clone()
|
||||
)
|
||||
key=|(key, _)| key.clone()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
CSS class names, like variable names, should ideally be self-documenting. The class
.cst
doesn't say anything to me. Ideally, choose a different class name with clearer semantics. If that's not feasible for some reason, make sure to clearly comment the first use of the.cst
class.Done! I've expanded "cst" to "constraint," "elt" to "element," and "rep" to "representation" in the HTML element classes (commits
882286c
and3f3c173
).Yes, much more intelligible! Thanks, resolving.