Groups: objects of type "group" draw nothing; members carry the group's id in a new `group` field (groups nest). A hidden group hides everything in it (Doc::hidden_in_groups, skipped by the driver), a locked one locks it in the Studio. Removing a group moves its members up into its parent group. isokit check warns about members of missing groups. New `order` op: the whole object order at once (each id once), one undo step. Studio object list as a tree in the document order, front first: each group sits just after its members, so it draws as one block, and ground pieces higher in the list lie on top. Groups fold open and shut; every row has an eye and a lock (shown on hover or when set); a click on a group selects all of it, so a drag on the map moves it all. Group / Ungroup (Ctrl+G, Ctrl+Shift+G), Order up/down (Ctrl+] Ctrl+[, with Shift to the front / back), drag rows above, below or into a group. Group panel: name, zoom, ungroup, to front / back, lock, hide, delete with contents. MCP: group field in the object schema, order op, guidance. Tests: Rust 81 (ops: groups, order, removal; driver: nested hidden groups), Studio 131, web 100.
This commit is contained in:
1 parent
5ec164a633
commit
540b37020c
15 files changed
+573
-30
No files matched your search
@@ -62,6 +62,34 @@ pub struct Object {
|
||||
pub hidden: bool,
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub locked: bool,
|
||||
/// Id of the group (an object of type "group") this object belongs to.
|
||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||
pub group: String,
|
||||
}
|
||||
|
||||
/// The type of the objects that group others: they draw nothing; hiding or
|
||||
/// locking one hides or locks everything in it.
|
||||
pub const GROUP: &str = "group";
|
||||
|
||||
impl Doc {
|
||||
/// Whether an object is hidden, by itself or by a group it is in (groups can nest).
|
||||
pub fn hidden_in_groups(&self, o: &Object) -> bool {
|
||||
let mut cur = o;
|
||||
for _ in 0..64 {
|
||||
if cur.hidden {
|
||||
return true;
|
||||
}
|
||||
match self
|
||||
.objects
|
||||
.iter()
|
||||
.find(|g| g.kind == GROUP && g.id == cur.group)
|
||||
{
|
||||
Some(g) if !cur.group.is_empty() => cur = g,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// One editable property of a prefab.
|
||||
|
||||
@@ -488,6 +488,10 @@ impl Runtime {
|
||||
.try_cast::<Map>()
|
||||
.unwrap_or_default();
|
||||
for o in &doc.objects {
|
||||
// groups draw nothing; a hidden group hides what is in it
|
||||
if o.kind == doc::GROUP {
|
||||
continue;
|
||||
}
|
||||
let mut j = serde_json::to_value(o).unwrap_or_default();
|
||||
let meta = b.catalog.get(&o.kind);
|
||||
let props = meta
|
||||
@@ -510,7 +514,9 @@ impl Runtime {
|
||||
}
|
||||
let d = dyn_json(&j);
|
||||
b.all_objects.push(d.clone());
|
||||
if !o.hidden && (o.levels.is_empty() || o.levels.iter().any(|l| l == level)) {
|
||||
if !doc.hidden_in_groups(o)
|
||||
&& (o.levels.is_empty() || o.levels.iter().any(|l| l == level))
|
||||
{
|
||||
b.objects.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,6 +858,33 @@ fn piece(id: &str, kind: &str, a: [f64; 2], b: [f64; 2], props: Value) -> Object
|
||||
o
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hidden_groups_hide_what_is_in_them() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let mut g = obj("g", "group", 0.0, 0.0, json!({}));
|
||||
g.hidden = true;
|
||||
let mut inner = obj("inner", "group", 0.0, 0.0, json!({}));
|
||||
inner.group = "g".into();
|
||||
let mut t1 = obj("t1", "tree", 0.0, 0.0, json!({}));
|
||||
t1.group = "inner".into();
|
||||
let mut t2 = obj("t2", "tree", 5.0, 0.0, json!({}));
|
||||
t2.group = "g".into();
|
||||
let doc = Doc {
|
||||
objects: vec![t1, t2, inner, g, obj("t3", "tree", 10.0, 0.0, json!({}))],
|
||||
..Default::default()
|
||||
};
|
||||
let b = r.build("site", 0, &cfg(), &doc).unwrap();
|
||||
// groups draw nothing and warn about nothing
|
||||
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
|
||||
assert_eq!(sprites_of(&b, "g") + sprites_of(&b, "inner"), 0);
|
||||
assert_eq!(sprites_of(&b, "t1") + sprites_of(&b, "t2"), 0);
|
||||
assert_eq!(sprites_of(&b, "t3"), 1);
|
||||
let mut shown = doc.clone();
|
||||
shown.objects[3].hidden = false;
|
||||
let b = r.build("site", 0, &cfg(), &shown).unwrap();
|
||||
assert_eq!(sprites_of(&b, "t1") + sprites_of(&b, "t2"), 2);
|
||||
}
|
||||
|
||||
/// The windows of every route mover piece of object `id`: (piece sprite, windows).
|
||||
fn travel_windows(b: &crate::Built, id: &str) -> Vec<Vec<[f64; 2]>> {
|
||||
fn walk(p: &isokit_core::Prim, out: &mut Vec<Vec<[f64; 2]>>) {
|
||||
|
||||
Reference in new issue
Block a user