mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-27 20:06:53 -06:00
M1: drag layers onto a page in the Pages list (#46)
While dragging layer rows, another page in the Pages list highlights as a drop target; dropping moves the dragged layers (the selection when the dragged row is in it) to that page through object.moveToPage. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -152,7 +152,7 @@ Scores by behaviour, 0–100%. Hours are what remains (one agent).
|
||||
|---|---|---|---|---|
|
||||
| Canvas & navigation | 6 | 72% | frame guides, 2x pixel preview | 3–5 |
|
||||
| Selection & transform | 8 | 64% | scale tool polish, measure hover polish | 8–12 |
|
||||
| Layers panel | 5 | 76% | keyboard row navigation, drag between pages | 2–4 |
|
||||
| Layers panel | 5 | 79% | keyboard row navigation | 1–2 |
|
||||
| Shapes & vector | 8 | 63% | variable width, multi-point transform box, point snapping, live boolean editing, arc handles on canvas, shape builder on strokes and open paths | 10–15 |
|
||||
| Paint | 7 | 65% | colour profiles (Display P3), paint style picker polish, image adjustment presets | 6–10 |
|
||||
| Strokes | 3 | 70% | stroke styles, variable width, arrowhead previews in the menus | 3–5 |
|
||||
|
||||
@@ -185,14 +185,29 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
let Ok(ds) = app.session.ds() else { return };
|
||||
let pages: Vec<(NodeId, String)> = ds.doc.pages.iter().filter_map(|p| ds.doc.node(*p).map(|n| (*p, n.name.clone()))).collect();
|
||||
let cur = ds.page;
|
||||
let sel = ds.selection.clone();
|
||||
let mut select = None;
|
||||
let mut rename_done: Option<(NodeId, String)> = None;
|
||||
// Layers dragged from the list onto another page move there.
|
||||
let dragging: Option<NodeId> = ui.data(|d| d.get_temp(layer_drag_id()));
|
||||
let pointer = ui.input(|i| i.pointer.hover_pos());
|
||||
let mut move_to: Option<(NodeId, String)> = None;
|
||||
for (id, name) in pages {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(w, 32.0), Sense::click());
|
||||
let r = r.shrink2(vec2(8.0, 0.0));
|
||||
if resp.hovered() || id == cur {
|
||||
ui.painter().rect_filled(r, CornerRadius::same(5), t.hover);
|
||||
}
|
||||
if dragging.is_some()
|
||||
&& id != cur
|
||||
// The gap between rows counts too.
|
||||
&& pointer.is_some_and(|p| resp.interact_rect.expand2(vec2(0.0, 3.0)).contains(p))
|
||||
{
|
||||
ui.painter().rect_stroke(r, CornerRadius::same(5), Stroke::new(2.0, ACCENT), egui::StrokeKind::Inside);
|
||||
if ui.input(|i| i.pointer.any_released()) {
|
||||
move_to = Some((id, name.clone()));
|
||||
}
|
||||
}
|
||||
if app.ui.rename_page.as_ref().is_some_and(|(i, _)| *i == id) {
|
||||
let tr = Rect::from_min_max(pos2(r.min.x + 8.0, r.min.y + 4.0), pos2(r.max.x - 4.0, r.max.y - 4.0));
|
||||
if let Some(new) =
|
||||
@@ -235,6 +250,12 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
app.ui.rename_page = None;
|
||||
app.run("page.rename", json!({"id": id, "name": new}));
|
||||
}
|
||||
if let Some((page, name)) = move_to {
|
||||
let ids = dragged_layers(dragging, &sel);
|
||||
if app.run("object.moveToPage", json!({"ids": ids, "page": page})).is_some() {
|
||||
app.toast(format!("Moved to {name}"));
|
||||
}
|
||||
}
|
||||
if let Some(p) = select
|
||||
&& p != cur
|
||||
{
|
||||
@@ -243,6 +264,20 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
|
||||
crate::widgets::separator(ui);
|
||||
}
|
||||
|
||||
/// The layer row being dragged in the panel (temp data).
|
||||
fn layer_drag_id() -> Id {
|
||||
Id::new("layer_drag")
|
||||
}
|
||||
|
||||
/// What a drag of row `dragging` moves: the selection when the row is in it, else the row.
|
||||
fn dragged_layers(dragging: Option<NodeId>, sel: &[NodeId]) -> Vec<NodeId> {
|
||||
match dragging {
|
||||
Some(dg) if sel.contains(&dg) => sel.to_vec(),
|
||||
Some(dg) => vec![dg],
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Row {
|
||||
pub(crate) id: NodeId,
|
||||
@@ -407,18 +442,13 @@ fn layers(app: &mut FigApp, ui: &mut Ui) {
|
||||
let mut lock: Option<NodeId> = None;
|
||||
let mut rename_done: Option<(NodeId, String)> = None;
|
||||
let mut hovered_row: Option<NodeId> = None;
|
||||
let drag_id = Id::new("layer_drag");
|
||||
let drag_id = layer_drag_id();
|
||||
// Escape cancels a drag.
|
||||
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
ui.data_mut(|d| d.remove::<NodeId>(drag_id));
|
||||
}
|
||||
let dragging: Option<NodeId> = ui.data(|d| d.get_temp(drag_id));
|
||||
// What a drag moves: the selection when the dragged row is in it.
|
||||
let moving: Vec<NodeId> = match dragging {
|
||||
Some(dg) if sel.contains(&dg) => sel.clone(),
|
||||
Some(dg) => vec![dg],
|
||||
None => Vec::new(),
|
||||
};
|
||||
let moving = dragged_layers(dragging, &sel);
|
||||
// A layer can't go into itself or its own descendants.
|
||||
let blocked = |id: NodeId| moving.contains(&id) || doc.ancestors(id).iter().any(|a| moving.contains(a));
|
||||
let mut drop: Option<(NodeId, i8)> = None;
|
||||
|
||||
@@ -118,3 +118,32 @@ fn a_layer_cant_drop_into_its_own_child_and_escape_cancels() {
|
||||
u.button(f, false);
|
||||
assert_ne!(u.app.session.doc().unwrap().node(rect).unwrap().parent, Some(frame));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dropping_layers_on_another_page_moves_them_there() {
|
||||
let (mut u, _frame, _child, rect) = setup();
|
||||
let p1 = u.app.session.page().unwrap();
|
||||
let p2 = id(u.app.session.execute("page.add", &json!({})).unwrap());
|
||||
u.app.session.execute("page.select", &json!({"id": p1})).unwrap();
|
||||
u.frame(vec![]);
|
||||
// Find Page 2's row in the Pages list: the click that switches to it.
|
||||
let mut at = None;
|
||||
for y in (90..400).step_by(4) {
|
||||
let p = Pos2::new(150.0, y as f32);
|
||||
u.button(p, true);
|
||||
u.button(p, false);
|
||||
if u.app.session.page().unwrap() == p2 {
|
||||
// Rows are 32 high: aim at the middle, past the hit tolerance at its edge.
|
||||
at = Some(p + vec2(0.0, 12.0));
|
||||
u.app.session.execute("page.select", &json!({"id": p1})).unwrap();
|
||||
u.frame(vec![]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let page_row = at.expect("page 2 row");
|
||||
let r = u.row_at(rect);
|
||||
u.drag_to(r, page_row);
|
||||
u.button(page_row, false);
|
||||
let doc = u.app.session.doc().unwrap();
|
||||
assert_eq!(doc.ancestors(rect).last().copied(), Some(p2), "moved to page 2");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user