Browser fold arrows: each row is one widget with a stable id, and where the click lands decides what it does (fold arrow with padding, eye, radio, or the row), so an arrow click toggles without also selecting or picking the row or starting a drag; Collapse/Expand tips; ui.at {browser|chevron} and ui.browser for automation; scenario browser_chevrons clicks every folder's arrow (document settings, named views, origin, groups, sketches, construction, canvases, components, bodies, the design) and checks the children; the inch-width units test runs now that params reads unit-less lengths in design units
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
fd4d38d31e
commit
9d3cbb4acc
5 files changed
+606
-15
No files matched your search
@@ -442,7 +442,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "waits for unit-less parameters used as lengths to read in the design's units (solvecraft-params)"]
|
||||
fn a_bare_width_keeps_its_inches_when_the_design_goes_metric() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "document.units", json!({"units": "in"}));
|
||||
|
||||
@@ -17,6 +17,17 @@ use crate::context_menu::{Item, Target};
|
||||
use crate::theme::Tokens;
|
||||
use crate::{SolveApp, icons};
|
||||
|
||||
thread_local! {
|
||||
/// The rows drawn this frame: (label, row, fold arrow area, open). For automation
|
||||
/// (`ui.at {browser | chevron}`, `ui.browser`).
|
||||
static DRAWN: std::cell::RefCell<Vec<(String, Rect, Option<Rect>, Option<bool>)>> = const { std::cell::RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
/// The rows on screen: (label, row rect, fold arrow area, open).
|
||||
pub fn drawn_rows() -> Vec<(String, Rect, Option<Rect>, Option<bool>)> {
|
||||
DRAWN.with(|d| d.borrow().clone())
|
||||
}
|
||||
|
||||
/// Browser state kept between frames.
|
||||
#[derive(Default)]
|
||||
pub struct TreeState {
|
||||
@@ -246,6 +257,8 @@ struct RowResp {
|
||||
hovered: bool,
|
||||
/// The row's response (drag and drop).
|
||||
resp: Option<egui::Response>,
|
||||
/// The press began on the fold arrow, eye or radio (not a drag of the row).
|
||||
on_control: bool,
|
||||
}
|
||||
|
||||
fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
@@ -256,7 +269,10 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
ui.allocate_space(r.size());
|
||||
return RowResp { rect: Some(r), ..Default::default() };
|
||||
}
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), ROW_H), Sense::click_and_drag());
|
||||
// A stable id per row (not the layout position), so a press and its release stay on the
|
||||
// same row while rows above open or close.
|
||||
let (r, _) = ui.allocate_exact_size(vec2(ui.available_width(), ROW_H), Sense::hover());
|
||||
let resp = ui.interact(r, id, Sense::click_and_drag());
|
||||
// While something is dragged (an appearance swatch, drawn under the pointer) `hovered` is
|
||||
// off; the row under the pointer still counts, so a drop lands on it.
|
||||
let dragged_over = egui::DragAndDrop::has_any_payload(ui.ctx()) && ui.input(|i| i.pointer.hover_pos()).is_some_and(|p| r.contains(p));
|
||||
@@ -269,6 +285,27 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
let mut x = r.left() + 2.0 + row.depth as f32 * INDENT;
|
||||
let cy = r.center().y;
|
||||
let ink = if row.dim { t.text_dim } else { t.icon };
|
||||
// The row is one widget: where a click lands decides what it does (the fold arrow with
|
||||
// padding, the eye, the radio, or the row itself), so a click on the arrow never also
|
||||
// selects the row or starts a drag.
|
||||
let fold_r = Rect::from_min_max(pos2(x - 4.0, r.top()), pos2(x + 15.0, r.bottom()));
|
||||
let eye_r = Rect::from_center_size(pos2(x + 14.0 + 8.0, cy), vec2(19.0, r.height()));
|
||||
let radio_r = Rect::from_center_size(pos2(x + 32.0 + 6.0, cy), vec2(15.0, r.height()));
|
||||
let at = |p: Pos2| -> u8 {
|
||||
if row.fold.is_some() && fold_r.contains(p) {
|
||||
1
|
||||
} else if row.eye.is_some() && eye_r.contains(p) {
|
||||
2
|
||||
} else if row.radio.is_some() && radio_r.contains(p) {
|
||||
3
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
DRAWN.with(|d| d.borrow_mut().push((row.label.to_string(), r, row.fold.map(|_| fold_r), row.fold)));
|
||||
let press_on = ui.input(|i| i.pointer.press_origin()).map_or(0, at);
|
||||
let click_on = resp.interact_pointer_pos().map_or(0, at);
|
||||
let hover_on = resp.hover_pos().map_or(0, at);
|
||||
// Fold arrow.
|
||||
if let Some(open) = row.fold {
|
||||
let ar = Rect::from_center_size(pos2(x + 6.0, cy), vec2(12.0, 12.0));
|
||||
@@ -277,9 +314,10 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
} else {
|
||||
vec![pos2(ar.left() + 4.0, ar.top() + 2.0), pos2(ar.right() - 3.0, ar.center().y), pos2(ar.left() + 4.0, ar.bottom() - 2.0)]
|
||||
};
|
||||
ui.painter().add(egui::Shape::convex_polygon(pts, t.text_dim, Stroke::NONE));
|
||||
out.fold = ui.interact(ar.expand(3.0), id.with("fold"), Sense::click()).clicked();
|
||||
crate::scenario::publish_handle(&format!("fold:{}", row.label), ar.center());
|
||||
let ink = if hover_on == 1 { t.text } else { t.text_dim };
|
||||
ui.painter().add(egui::Shape::convex_polygon(pts, ink, Stroke::NONE));
|
||||
out.fold = resp.clicked() && click_on == 1;
|
||||
crate::scenario::publish_handle(&format!("fold:{}", row.label), fold_r.center());
|
||||
}
|
||||
x += 14.0;
|
||||
// Eye.
|
||||
@@ -289,9 +327,7 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
if !vis {
|
||||
ui.painter().line_segment([er.left_bottom() + vec2(2.0, -2.0), er.right_top() + vec2(-2.0, 2.0)], Stroke::new(1.4, t.text_dim));
|
||||
}
|
||||
let e = ui.interact(er.expand(2.0), id.with("eye"), Sense::click());
|
||||
out.eye = e.clicked();
|
||||
e.on_hover_text(if vis { "Hide" } else { "Show" });
|
||||
out.eye = resp.clicked() && click_on == 2;
|
||||
}
|
||||
x += 18.0;
|
||||
// Activation radio (components).
|
||||
@@ -301,9 +337,7 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
if active {
|
||||
ui.painter().circle_filled(rc, 2.8, t.accent);
|
||||
}
|
||||
let rr = ui.interact(Rect::from_center_size(rc, vec2(14.0, 14.0)), id.with("radio"), Sense::click());
|
||||
out.radio = rr.clicked();
|
||||
rr.on_hover_text(if active { "Active component" } else { "Activate" });
|
||||
out.radio = resp.clicked() && click_on == 3;
|
||||
x += 15.0;
|
||||
}
|
||||
if !row.icon.is_empty() {
|
||||
@@ -322,9 +356,20 @@ fn draw_row(ui: &mut egui::Ui, id: egui::Id, row: &Row) -> RowResp {
|
||||
bx += 15.0;
|
||||
}
|
||||
crate::scenario::publish_handle(&format!("row:{}", row.label), r.center());
|
||||
let tip = match hover_on {
|
||||
1 => Some(if row.fold == Some(true) { "Collapse" } else { "Expand" }),
|
||||
2 => Some(if row.eye == Some(true) { "Hide" } else { "Show" }),
|
||||
3 => Some(if row.radio == Some(true) { "Active component" } else { "Activate" }),
|
||||
_ => None,
|
||||
};
|
||||
let resp = match tip {
|
||||
Some(t) => resp.on_hover_text(t),
|
||||
None => resp,
|
||||
};
|
||||
out.on_control = press_on != 0;
|
||||
out.clicked = resp.clicked() && click_on == 0;
|
||||
out.double = resp.double_clicked() && click_on == 0;
|
||||
out.resp = Some(resp.clone());
|
||||
out.clicked = resp.clicked();
|
||||
out.double = resp.double_clicked();
|
||||
if resp.secondary_clicked() {
|
||||
out.secondary = resp.interact_pointer_pos().or(Some(r.left_bottom()));
|
||||
}
|
||||
@@ -424,6 +469,7 @@ struct Actions {
|
||||
pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get();
|
||||
app.tree.order.clear();
|
||||
DRAWN.with(|d| d.borrow_mut().clear());
|
||||
let mut acts = Actions::default();
|
||||
egui::Panel::left("sc_browser")
|
||||
.exact_size(250.0)
|
||||
@@ -1227,7 +1273,7 @@ fn entry_row(
|
||||
}
|
||||
let Some(resp) = &r.resp else { return };
|
||||
// Drag: the selected items of this folder when this one is selected, else just this one.
|
||||
if resp.drag_started() {
|
||||
if resp.drag_started() && !r.on_control {
|
||||
let keys: Vec<String> = if e.selected { selected_keys(app, comp, folder) } else { vec![e.key.clone()] };
|
||||
resp.dnd_set_drag_payload(Drag { comp, folder, keys });
|
||||
}
|
||||
|
||||
@@ -287,6 +287,20 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
None => err(format!("handle {h} is not shown")),
|
||||
};
|
||||
}
|
||||
// A browser row by its label: the row itself, or its fold arrow.
|
||||
for (k, chevron) in [("browser", false), ("chevron", true)] {
|
||||
if let Some(label) = p.get(k).and_then(Value::as_str) {
|
||||
let rows = crate::browser::drawn_rows();
|
||||
let found = rows
|
||||
.iter()
|
||||
.find(|r| r.0 == label)
|
||||
.and_then(|r| if chevron { r.2.map(|f| f.center()) } else { Some(r.1.left_center() + egui::vec2(120.0, 0.0)) });
|
||||
return match found {
|
||||
Some(q) => ok(json!([q.x, q.y])),
|
||||
None => err(format!("no browser row `{label}`{} on screen", if chevron { " with a fold arrow" } else { "" })),
|
||||
};
|
||||
}
|
||||
}
|
||||
if let Some(d) = p.get("dimension").and_then(Value::as_str) {
|
||||
return match crate::dim_view::text_at(d) {
|
||||
Some(q) => ok(json!([q.x, q.y])),
|
||||
@@ -426,6 +440,11 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
None => ok(json!({"command": id, "key": crate::keymap::effective(app, id)})),
|
||||
}
|
||||
}
|
||||
"ui.browser" => ok(json!({
|
||||
"rows": crate::browser::drawn_rows().iter().map(|(label, r, _, open)| json!({"label": label, "y": r.center().y, "open": open})).collect::<Vec<_>>(),
|
||||
"selection": app.session.selection,
|
||||
"picked_components": app.tree.picked_components,
|
||||
})),
|
||||
"ui.capture" => match s("action") {
|
||||
Some(a) => match crate::browser::answer_capture(app, a) {
|
||||
Ok(()) => ok(json!({"pending_moves": app.session.pending_moves.len()})),
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
//! - `{"camera": {"yaw": rad, "pitch": rad}}` puts the camera at an orbit; `{"view_anim": "top",
|
||||
//! "max_deg": 10}` animates to a view and fails if any frame turns the view more than that
|
||||
//! - AT may also be `{"plane": "XY"}` (the middle of an origin plane's square), `{"axis": "Z"}`,
|
||||
//! `{"dimension": "d1"}` or `{"handle": "arrow"}` (a manipulator handle: see [`publish_handle`])
|
||||
//! `{"dimension": "d1"}`, `{"handle": "arrow"}` (a manipulator handle: see [`publish_handle`]),
|
||||
//! `{"browser": "Bodies"}` (a browser row by its label) or `{"chevron": "Bodies"}` (its fold arrow)
|
||||
//! - `{"autosave": true}`: autosave into the scenario's recovery folder; `{"restart": "crash" |
|
||||
//! "close"}`: the app dies (or closes) and a new one starts on the same folders
|
||||
//! - `{"script": "examples/…/x.json"}`: run a command script through the engine; `"$DIR"` in a
|
||||
@@ -864,6 +865,27 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> {
|
||||
return Err(format!("design name: got {}, want {v}", doc["name"]));
|
||||
}
|
||||
}
|
||||
"browser_has" | "browser_lacks" => {
|
||||
// Browser rows on screen, by label.
|
||||
let r = h.call("ui.browser", json!({}));
|
||||
let labels: Vec<String> =
|
||||
r["result"]["rows"].as_array().into_iter().flatten().filter_map(|x| x["label"].as_str().map(str::to_string)).collect();
|
||||
for want in v.as_array().into_iter().flatten().filter_map(Value::as_str) {
|
||||
let there = labels.iter().any(|l| l == want);
|
||||
if there != (k.as_str() == "browser_has") {
|
||||
return Err(format!("browser row `{want}` {}: rows {labels:?}", if there { "still shown" } else { "missing" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
"browser_selected" => {
|
||||
// Nothing selected and no component picked (a fold click selects nothing).
|
||||
let r = h.call("ui.browser", json!({}));
|
||||
let none = r["result"]["selection"].as_array().is_none_or(|a| a.is_empty())
|
||||
&& r["result"]["picked_components"].as_array().is_none_or(|a| a.is_empty());
|
||||
if none == v.as_bool().unwrap_or(false) {
|
||||
return Err(format!("browser selection: {}", r["result"]));
|
||||
}
|
||||
}
|
||||
"query" => {
|
||||
// {command, params?, path: [keys or indices], value}: a command's result at path.
|
||||
let cmd = v["command"].as_str().ok_or("query needs `command`")?;
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
[
|
||||
{
|
||||
"note": "A design with every kind of browser folder: bodies in a group, a component, a sketch, a construction plane, a canvas and named views."
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "solid.box",
|
||||
"params": {
|
||||
"length": 10,
|
||||
"width": 10,
|
||||
"height": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "solid.box",
|
||||
"params": {
|
||||
"length": 10,
|
||||
"width": 10,
|
||||
"height": 10,
|
||||
"corner": [
|
||||
30,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "solid.box",
|
||||
"params": {
|
||||
"length": 10,
|
||||
"width": 10,
|
||||
"height": 10,
|
||||
"corner": [
|
||||
60,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "component.from_bodies",
|
||||
"params": {
|
||||
"bodies": [
|
||||
"Body3"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "browser.group",
|
||||
"params": {
|
||||
"folder": "bodies",
|
||||
"items": [
|
||||
"Body1"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "construct.plane.offset",
|
||||
"params": {
|
||||
"base": "XY",
|
||||
"offset": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "sketch.create",
|
||||
"params": {
|
||||
"plane": "XZ"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "sketch.rectangle.two_point",
|
||||
"params": {
|
||||
"p0": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"p1": [
|
||||
5,
|
||||
5
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"call": "engine.execute",
|
||||
"params": {
|
||||
"command": "sketch.finish",
|
||||
"params": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"make_image": "pic.png"
|
||||
},
|
||||
{
|
||||
"choose_file": "pic.png"
|
||||
},
|
||||
{
|
||||
"start": "canvas.insert"
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"plane": "XY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "Escape"
|
||||
},
|
||||
{
|
||||
"call": "ui.set",
|
||||
"params": {
|
||||
"dark": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "Folders that start closed open with their arrow, and close again; the click selects nothing."
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Units: mm"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Document Settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Units: mm"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Document Settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Units: mm"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Top"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Named Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Top"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Named Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Top"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"XY"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Origin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"XY"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Origin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"XY"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "Folders that start open close with their arrow, and open again."
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Group1 (1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Body1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Group1 (1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Sketch1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Sketches"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Sketch1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Sketches"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Sketch1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Plane1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Construction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Plane1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Construction"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Plane1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Canvas1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Canvases"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Canvas1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Canvases"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Canvas1"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body3"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Body3:1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Body3"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Body3:1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body3"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Bodies"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Body2"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Bodies"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Body2"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "The design's own row closes the whole tree."
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Bodies"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Untitled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Bodies"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"chevron": "Untitled"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Bodies"
|
||||
],
|
||||
"browser_selected": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"note": "Clicking the row of a folder (not its arrow) toggles it too; clicking a component's arrow doesn't pick it."
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"browser": "Sketches"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_lacks": [
|
||||
"Sketch1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"click": {
|
||||
"browser": "Sketches"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expect": {
|
||||
"browser_has": [
|
||||
"Sketch1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"shot": "browser_folds"
|
||||
}
|
||||
]
|
||||
Reference in new issue
Block a user