From 4b4fb73fa3fe3770c2c6a6ff0f0bc6a37e8663c7 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 15 Jun 2026 21:02:47 -0700 Subject: [PATCH 1/2] Add layer and artboard nudge resizing --- .../messages/input_mapper/input_mappings.rs | 144 +++++++++--------- .../portfolio/document/document_message.rs | 3 + .../document/document_message_handler.rs | 45 +++++- .../common_functionality/utility_functions.rs | 54 +++++++ .../tool/tool_messages/artboard_tool.rs | 33 +++- .../tool/tool_messages/select_tool.rs | 7 +- .../messages/tool/tool_messages/shape_tool.rs | 19 ++- 7 files changed, 218 insertions(+), 87 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 6d4a4e4de3..2e93961f1e 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -123,30 +123,30 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping { entry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp), entry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected), entry!(KeyDown(Backspace); action_dispatch=ArtboardToolMessage::DeleteSelected), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), entry!(KeyDown(MouseRight); action_dispatch=ArtboardToolMessage::Abort), entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort), // @@ -192,30 +192,30 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping { entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides), entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides), entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::IncreaseSides), entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::DecreaseSides), // @@ -374,30 +374,30 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping { entry!(KeyDown(BracketRight); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), entry!(KeyDown(BracketLeft); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersLower), entry!(KeyDown(BracketRight); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersRaise), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite: Control }), + entry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite: Control }), // // TransformLayerMessage entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab), diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 09d1786d3c..203287fb58 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::Arc; use super::utility_types::misc::{GroupFolderType, SnappingState}; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::data_panel::DataPanelMessage; use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, OverlaysType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -108,6 +109,8 @@ pub enum DocumentMessage { NudgeSelectedLayers { delta_x: f64, delta_y: f64, + resize: Key, + resize_opposite: Key, }, PasteImage { name: Option, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 5abde2ced8..420887735c 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -24,6 +24,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::portfolio::utility_types::PanelType; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity}; +use crate::messages::tool::common_functionality::utility_functions::nudge_resize_bounds; use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::ToolType; @@ -735,8 +736,15 @@ impl MessageHandler> for DocumentMes responses.add(DocumentMessage::DocumentStructureChanged); responses.add(NodeGraphMessage::SendGraph); } - DocumentMessage::NudgeSelectedLayers { delta_x, delta_y } => { - self.nudge_selected_layers(delta_x, delta_y, responses); + DocumentMessage::NudgeSelectedLayers { + delta_x, + delta_y, + resize, + resize_opposite, + } => { + let resize = ipp.keyboard.key(resize); + let resize_opposite = ipp.keyboard.key(resize_opposite); + self.nudge_selected_layers(delta_x, delta_y, resize, resize_opposite, responses); } DocumentMessage::PasteImage { name, @@ -2672,14 +2680,41 @@ impl DocumentMessageHandler { responses.add(NodeGraphMessage::SendGraph); } - fn nudge_selected_layers(&mut self, delta_x: f64, delta_y: f64, responses: &mut VecDeque) { - responses.add(DocumentMessage::AddTransaction); - + fn nudge_selected_layers(&mut self, delta_x: f64, delta_y: f64, resize: bool, resize_opposite: bool, responses: &mut VecDeque) { let can_move = |layer| { let selected = self.network_interface.selected_nodes(); selected.layer_visible(layer, &self.network_interface) && !selected.layer_locked(layer, &self.network_interface) }; + if resize { + let layers: Vec<_> = self.network_interface.shallowest_unique_layers(&[]).filter(|&layer| can_move(layer)).collect(); + // Bail before opening a transaction when there's nothing resizable + let Some([min, max]) = layers.iter().filter_map(|&layer| self.metadata().bounding_box_document(layer)).reduce(Quad::combine_bounds) else { + return; + }; + + let resized = nudge_resize_bounds(min, max, DVec2::new(delta_x, delta_y), self.document_ptz.tilt(), resize_opposite); + + // Express the document-space scale in viewport space so it composes with each layer like the rest of the transform pipeline + let document_to_viewport = self.metadata().document_to_viewport; + let transform = document_to_viewport * resized.transform * document_to_viewport.inverse(); + + responses.add(DocumentMessage::AddTransaction); + + for layer in layers { + responses.add(GraphOperationMessage::TransformChange { + layer, + transform, + transform_in: TransformIn::Viewport, + skip_rerender: false, + }); + } + + return; + } + + responses.add(DocumentMessage::AddTransaction); + let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y))); responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation }); diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index fb19f4d2b9..eb267c28cf 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -586,3 +586,57 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac Some(first_layer) } + +/// Smallest extent, in document units, a nudge-resized box may have per axis (avoids a zero divisor and prevents collapse/inversion). +const NUDGE_RESIZE_MIN_EXTENT: f64 = 1.; + +/// The resized box corners and the document-space scale transform produced by [`nudge_resize_bounds`]. +pub struct NudgeResize { + pub min: DVec2, + pub max: DVec2, + pub transform: DAffine2, +} + +/// Resizes the axis-aligned document-space box `[min, max]` by an arrow-key nudge `delta` (screen space). +/// +/// `tilt` (document rotation) is snapped to a quarter turn so the arrow maps onto a box axis. The top-left corner is anchored by default, +/// or the bottom-right corner when `resize_opposite` (Control) is held; the other corner moves. The box stays at least one unit per axis. +pub fn nudge_resize_bounds(min: DVec2, max: DVec2, delta: DVec2, tilt: f64, resize_opposite: bool) -> NudgeResize { + // Snap rotation to a quarter turn so the screen arrow lands on a document-space box axis + let doc_delta = match ((tilt / std::f64::consts::FRAC_PI_2).round() as i32).rem_euclid(4) { + 1 => DVec2::new(delta.y, -delta.x), + 2 => -delta, + 3 => DVec2::new(-delta.y, delta.x), + _ => delta, + }; + + // Move one corner by the arrow, keeping the other (the anchor) at least the minimum extent away + let mut new_min = min; + let mut new_max = max; + if resize_opposite { + new_min += doc_delta; + new_min = new_min.min(new_max - DVec2::splat(NUDGE_RESIZE_MIN_EXTENT)); + } else { + new_max += doc_delta; + new_max = new_max.max(new_min + DVec2::splat(NUDGE_RESIZE_MIN_EXTENT)); + } + + // Ratio of new to old extent, treating a degenerate (sub-unit) original extent as unscaled + let old_extent = max - min; + let new_extent = new_max - new_min; + let scale = DVec2::new( + if old_extent.x.abs() < NUDGE_RESIZE_MIN_EXTENT { 1. } else { new_extent.x / old_extent.x }, + if old_extent.y.abs() < NUDGE_RESIZE_MIN_EXTENT { 1. } else { new_extent.y / old_extent.y }, + ); + + // Scale about the anchored corner, guarding non-finite components to zero + let anchor = if resize_opposite { max } else { min }; + let scale_center = DVec2::new(if anchor.x.is_finite() { anchor.x } else { 0. }, if anchor.y.is_finite() { anchor.y } else { 0. }); + let transform = DAffine2::from_scale_angle_translation(scale, 0., scale_center - scale * scale_center); + + NudgeResize { + min: new_min, + max: new_max, + transform, + } +} diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 1fe5d3d5c3..06cdd9e558 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -10,6 +10,7 @@ use crate::messages::tool::common_functionality::snapping; use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint; use crate::messages::tool::common_functionality::snapping::SnapData; use crate::messages::tool::common_functionality::transformation_cage::*; +use crate::messages::tool::common_functionality::utility_functions::nudge_resize_bounds; use graph_craft::document::NodeId; use graphene_std::renderer::{Quad, Rect}; @@ -30,7 +31,7 @@ pub enum ArtboardToolMessage { // Tool-specific messages UpdateSelectedArtboard, DeleteSelected, - NudgeSelected { delta_x: f64, delta_y: f64 }, + NudgeSelected { delta_x: f64, delta_y: f64, resize: Key, resize_opposite: Key }, PointerDown, PointerMove { constrain_axis_or_aspect: Key, center: Key }, PointerOutsideViewport { constrain_axis_or_aspect: Key, center: Key }, @@ -480,8 +481,16 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolFsmState::Ready { hovered } } - (_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => { - let Some(bounds) = &mut tool_data.bounding_box_manager else { + ( + _, + ArtboardToolMessage::NudgeSelected { + delta_x, + delta_y, + resize, + resize_opposite, + }, + ) => { + let Some(bounds) = &tool_data.bounding_box_manager else { return ArtboardToolFsmState::Ready { hovered }; }; let Some(selected_artboard) = tool_data.selected_artboard else { @@ -493,12 +502,24 @@ impl Fsm for ArtboardToolFsmState { } let [existing_top_left, existing_bottom_right] = bounds.bounds; - let delta = DVec2::from_angle(-document.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)); + let tilt = document.document_ptz.tilt(); + + // The resize key switches from nudging the artboard's position to resizing its box, anchored to the opposite edge by the other key + let resize = input.keyboard.key(resize); + let (location, dimensions) = if resize { + let resize_opposite = input.keyboard.key(resize_opposite); + let resized = nudge_resize_bounds(existing_top_left, existing_bottom_right, DVec2::new(delta_x, delta_y), tilt, resize_opposite); + (resized.min.round(), (resized.max - resized.min).round()) + } else { + let delta = DVec2::from_angle(-tilt).rotate(DVec2::new(delta_x, delta_y)); + ((existing_top_left + delta).round(), (existing_bottom_right - existing_top_left).round()) + }; + responses.add(DocumentMessage::AddTransaction); responses.add(GraphOperationMessage::ResizeArtboard { layer: selected_artboard, - location: DVec2::new(existing_top_left.x + delta.x, existing_top_left.y + delta.y).round(), - dimensions: (existing_bottom_right - existing_top_left).round(), + location, + dimensions, }); ArtboardToolFsmState::Ready { hovered } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 93435723d9..0bb01f9000 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1812,7 +1812,12 @@ impl Fsm for SelectToolFsmState { // TODO: Make all the following hints only appear if there is at least one selected layer HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]), HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]), - HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]), + HintGroup(vec![ + HintInfo::arrow_keys("Nudge Selected"), + HintInfo::keys([Key::Shift], "10x").prepend_plus(), + HintInfo::keys([Key::Alt], "Resize Corner").prepend_plus(), + HintInfo::keys([Key::Control], "Other Corner").prepend_plus(), + ]), HintGroup(vec![ HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, "Move Duplicate"), HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]), diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index c0c1146f36..710b0def8d 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -133,7 +133,7 @@ pub enum ShapeToolMessage { IncreaseSides, DecreaseSides, - NudgeSelectedLayers { delta_x: f64, delta_y: f64 }, + NudgeSelectedLayers { delta_x: f64, delta_y: f64, resize: Key, resize_opposite: Key }, } fn create_sides_widget(vertices: u32) -> WidgetInstance { @@ -994,8 +994,21 @@ impl Fsm for ShapeToolFsmState { } self } - (ShapeToolFsmState::Ready(_), ShapeToolMessage::NudgeSelectedLayers { delta_x, delta_y }) => { - responses.add(DocumentMessage::NudgeSelectedLayers { delta_x, delta_y }); + ( + ShapeToolFsmState::Ready(_), + ShapeToolMessage::NudgeSelectedLayers { + delta_x, + delta_y, + resize, + resize_opposite, + }, + ) => { + responses.add(DocumentMessage::NudgeSelectedLayers { + delta_x, + delta_y, + resize, + resize_opposite, + }); self } From 5619814a41d818249670451d272adb7b089ce029 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 15 Jun 2026 21:19:46 -0700 Subject: [PATCH 2/2] Improve bounding box validation for layer resizing to ensure only finite bounds are considered --- .../portfolio/document/document_message_handler.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 420887735c..4d94c93b18 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -2688,8 +2688,13 @@ impl DocumentMessageHandler { if resize { let layers: Vec<_> = self.network_interface.shallowest_unique_layers(&[]).filter(|&layer| can_move(layer)).collect(); - // Bail before opening a transaction when there's nothing resizable - let Some([min, max]) = layers.iter().filter_map(|&layer| self.metadata().bounding_box_document(layer)).reduce(Quad::combine_bounds) else { + // Combine only finite bounds (a non-finite box would poison the scale), bailing before opening a transaction if none remain + let Some([min, max]) = layers + .iter() + .filter_map(|&layer| self.metadata().bounding_box_document(layer)) + .filter(|[min, max]| min.is_finite() && max.is_finite()) + .reduce(Quad::combine_bounds) + else { return; };