mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 12:16:53 -06:00
Layers panel: a new selection opens its layer and scrolls its row into view
Selecting an object on the canvas left the Layers panel as it was: its layer could be closed and its row out of view. A new selection now opens the layer it is on and scrolls its row into view once (closing or scrolling afterwards is left alone); selected rows are highlighted, and an object selected inside a group shows as its group. Unnamed image frames are listed by their file name, as InDesign lists placed images. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
11a51d5b66
commit
c7b3ee4f45
@@ -13,7 +13,8 @@ const ROW_H: f32 = 24.0;
|
||||
/// Width of the eye and lock columns.
|
||||
const COL_W: f32 = 22.0;
|
||||
|
||||
/// Display name of an item: its own name, the first words of a text frame's story, or the kind.
|
||||
/// Display name of an item: its own name, the first words of a text frame's story, the file a
|
||||
/// graphic frame shows (as InDesign lists placed images), or the kind.
|
||||
pub fn item_label(doc: &Document, it: &Item) -> String {
|
||||
if !it.name.is_empty() {
|
||||
return it.name.clone();
|
||||
@@ -41,6 +42,9 @@ pub fn item_label(doc: &Document, it: &Item) -> String {
|
||||
return format!("<{short}{ell}>");
|
||||
}
|
||||
}
|
||||
if let Some(name) = it.graphic().and_then(|g| doc.assets.get(&g.asset)).map(|a| a.name.trim()).filter(|n| !n.is_empty()) {
|
||||
return format!("<{name}>");
|
||||
}
|
||||
it.default_label().to_string()
|
||||
}
|
||||
|
||||
@@ -111,6 +115,15 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) {
|
||||
let active = st.active_layer;
|
||||
let selected: Vec<ItemId> = st.selection.items.clone();
|
||||
let sel_layers: Vec<_> = selected.iter().filter_map(|i| doc.item(*i).map(|it| it.layer)).collect();
|
||||
// The rows stand for top-level objects: an object selected inside a group shows as its group.
|
||||
let shown: Vec<ItemId> = selected.iter().map(|i| doc.top_level_of(*i).unwrap_or(*i)).collect();
|
||||
// A new selection (made on the canvas or anywhere else) is revealed: its layer opens and its row
|
||||
// scrolls into view, once, so the panel can still be scrolled and closed freely afterwards.
|
||||
let last_id = egui::Id::new("layers_revealed_selection");
|
||||
let now: Vec<u64> = shown.iter().map(|i| i.0).collect();
|
||||
let reveal = !now.is_empty() && ui.data(|d| d.get_temp::<Vec<u64>>(last_id)).as_ref() != Some(&now);
|
||||
ui.data_mut(|d| d.insert_temp(last_id, now));
|
||||
let mut scrolled = false;
|
||||
// Objects on the current spread, topmost first.
|
||||
let cur = crate::canvas::current_page(app).unwrap_or(0);
|
||||
let spread_items: Vec<std::sync::Arc<Item>> =
|
||||
@@ -118,7 +131,11 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
for l in doc.layers.iter().rev() {
|
||||
let open_id = egui::Id::new(("layer_open", l.id.0));
|
||||
let open: bool = ui.data(|d| d.get_temp(open_id)).unwrap_or(l.id == active);
|
||||
let mut open: bool = ui.data(|d| d.get_temp(open_id)).unwrap_or(l.id == active);
|
||||
if reveal && sel_layers.contains(&l.id) && !open {
|
||||
open = true;
|
||||
ui.data_mut(|d| d.insert_temp(open_id, true));
|
||||
}
|
||||
let lc = c32(l.color);
|
||||
let (row, resp) = ui.allocate_exact_size(vec2(ui.available_width(), ROW_H), Sense::click());
|
||||
if l.id == active {
|
||||
@@ -201,8 +218,14 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) {
|
||||
}
|
||||
for it in spread_items.iter().filter(|i| i.layer == l.id) {
|
||||
let (row, resp) = ui.allocate_exact_size(vec2(ui.available_width(), ROW_H), Sense::click());
|
||||
let is_sel = selected.contains(&it.id);
|
||||
if resp.hovered() {
|
||||
let is_sel = shown.contains(&it.id);
|
||||
if is_sel {
|
||||
ui.painter().rect_filled(row, 0.0, t.row_selected);
|
||||
if reveal && !scrolled {
|
||||
resp.scroll_to_me(Some(egui::Align::Center));
|
||||
scrolled = true;
|
||||
}
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(row, 0.0, t.hover);
|
||||
}
|
||||
let (eye, lock) = eye_lock(ui, &app.ui.language, row, !it.hidden, it.locked, !l.visible, ("item", it.id.0));
|
||||
@@ -267,4 +290,27 @@ mod tests {
|
||||
let rr = doc.item(ItemId(rect["id"].as_u64().unwrap())).unwrap();
|
||||
assert_eq!(item_label(doc, rr), "<rectangle>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selecting_an_object_opens_its_layer_in_the_panel() {
|
||||
use egui_kittest::Harness;
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let id = app.run("frame.create", json!({"rect": [72, 72, 200, 200]})).unwrap()["id"].as_u64().unwrap();
|
||||
let first = app.session.active().unwrap().doc.item(ItemId(id)).unwrap().layer;
|
||||
// A new layer becomes the active one: the first layer's row starts closed.
|
||||
app.run("layer.new", json!({})).unwrap();
|
||||
app.run("selection.set", json!({"ids": []})).unwrap();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(260.0, 400.0)).build_ui_state(|ui, app: &mut DesignApp| show(app, ui), app);
|
||||
h.run_steps(2);
|
||||
let open = |h: &Harness<'_, DesignApp>| h.ctx.data(|d| d.get_temp::<bool>(egui::Id::new(("layer_open", first.0)))).unwrap_or(false);
|
||||
assert!(!open(&h), "closed while nothing on it is selected");
|
||||
h.state_mut().run("selection.set", json!({"ids": [id]})).unwrap();
|
||||
h.run_steps(2);
|
||||
assert!(open(&h), "selecting the object opens its layer");
|
||||
// Closed again by hand, it stays closed while the selection stays the same.
|
||||
h.ctx.data_mut(|d| d.insert_temp(egui::Id::new(("layer_open", first.0)), false));
|
||||
h.run_steps(2);
|
||||
assert!(!open(&h));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user