Comprehensively polish up the input hints across all tools and states (#1670)

* Add missing keyhints to most tools

* Standardize hints further

* Improve GRS numerical display values

* Additional hints improvements

* Improve Path tool hints; add Ctrl+Shift+A point deselection

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
milan-sedivy 2024-03-13 05:20:25 +02:00 committed by GitHub
parent 2263b0ab89
commit 9ac10cdcf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 218 additions and 136 deletions

View File

@ -197,7 +197,8 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PathToolMessage::PointerMove { alt: Alt, shift: Shift }),
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
entry!(KeyDown(KeyA); modifiers=[Control], action_dispatch=PathToolMessage::SelectAllPoints),
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop { shift_mirror_distance: Shift }),
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter {
@ -230,7 +231,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
//
// PenToolMessage
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}),
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control}),
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
@ -296,8 +297,8 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo),
entry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo),
entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers),
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),

View File

@ -192,6 +192,8 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![
// TODO: Fix bug where canceling doesn't work except with the Navigate tool active
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
@ -200,8 +202,6 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
plus: false,
slash: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Confirm")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Cancel")]),
]),
});
@ -268,7 +268,8 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Cancel")])]),
// TODO: Fix bug where canceling doesn't work except with the Navigate tool active
hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
});
self.mouse_position = ipp.mouse.position;
@ -302,6 +303,8 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![
// TODO: Fix bug where canceling doesn't work except with the Navigate tool active
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Control]).into()],
key_groups_mac: None,
@ -310,7 +313,6 @@ impl MessageHandler<NavigationMessage, (&DocumentMetadata, Option<[DVec2; 2]>, &
plus: false,
slash: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Cancel")]),
]),
});

View File

@ -274,37 +274,33 @@ impl TransformOperation {
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
let mut hints = Vec::new();
let axis_str = |vector: DVec2, separate: bool| match axis_constraint {
Axis::Both => {
if separate {
format!("X: {}, Y: {}", vector.x, vector.y)
} else {
vector.x.to_string()
}
}
Axis::X => format!("X: {}", vector.x),
Axis::Y => format!("Y: {}", vector.y),
};
let value_str = match self {
TransformOperation::None => String::new(),
TransformOperation::Grabbing(translation) => format!("Translate {}", axis_str(translation.to_dvec(), true)),
TransformOperation::Rotating(rotation) => format!("Rotate {}°", rotation.to_f64(snapping) * 360. / std::f64::consts::TAU),
TransformOperation::Scaling(scale) => format!("Scale {}", axis_str(scale.to_dvec(snapping), false)),
};
hints.push(HintInfo::label(value_str));
hints.push(HintInfo::keys([Key::Shift], "Precision Mode"));
let mut input_hints = Vec::new();
input_hints.push(HintInfo::keys([Key::Shift], "Slow Mode"));
if matches!(self, TransformOperation::Rotating(_) | TransformOperation::Scaling(_)) {
hints.push(HintInfo::keys([Key::Control], "Snap"));
input_hints.push(HintInfo::keys([Key::Control], "Snap"));
}
if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) {
hints.push(HintInfo::keys([Key::KeyX], "X Axis"));
hints.push(HintInfo::keys([Key::KeyY], "Y Axis"));
input_hints.push(HintInfo::keys([Key::KeyX], "Along X Axis"));
input_hints.push(HintInfo::keys([Key::KeyY], "Along Y Axis"));
}
let hint_data = HintData(vec![HintGroup(hints)]);
// TODO: Eventually, move this somewhere else (maybe an overlay in the corner of the viewport, design is TBD) since servicable but not ideal for UI design consistency to have it in the hints bar
let axis_text = |vector: DVec2, separate: bool| match (axis_constraint, separate) {
(Axis::Both, false) => format!("by {:.3}", vector.x),
(Axis::Both, true) => format!("by {:.3}, {:.3}", vector.x, vector.y),
(Axis::X, _) => format!("X by {:.3}", vector.x),
(Axis::Y, _) => format!("Y by {:.3}", vector.y),
};
let grs_value_text = match self {
TransformOperation::None => String::new(),
// TODO: Fix that the translation is showing numbers in viewport space, not document space
TransformOperation::Grabbing(translation) => format!("Translating {}", axis_text(translation.to_dvec(), true)),
TransformOperation::Rotating(rotation) => format!("Rotating by {:.3}°", rotation.to_f64(snapping) * 360. / std::f64::consts::TAU),
TransformOperation::Scaling(scale) => format!("Scaling {}", axis_text(scale.to_dvec(snapping), false)),
};
let grs_value = vec![HintInfo::label(grs_value_text)];
let hint_data = HintData(vec![HintGroup(input_hints), HintGroup(grs_value)]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
}

View File

@ -318,15 +318,6 @@ impl ShapeState {
document.metadata.document_to_viewport.transform_vector2(offset)
}
pub fn select_anchor_point_by_id(&mut self, layer: LayerNodeIdentifier, id: ManipulatorGroupId, add_to_selection: bool) {
if !add_to_selection {
self.deselect_all();
}
let point = ManipulatorPointId::new(id, SelectedType::Anchor);
let Some(selected_state) = self.selected_shape_state.get_mut(&layer) else { return };
selected_state.select_point(point);
}
/// Select/deselect the first point within the selection threshold.
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
pub fn change_point_selection(
@ -359,7 +350,7 @@ impl ShapeState {
if new_selected {
let retain_existing_selection = add_to_selection || already_selected;
if !retain_existing_selection {
self.deselect_all();
self.deselect_all_points();
}
// Add to the selected points
@ -383,20 +374,43 @@ impl ShapeState {
None
}
pub fn select_all_points(&mut self, document_network: &NodeNetwork) {
for (layer, state) in self.selected_shape_state.iter_mut() {
let Some(subpaths) = get_subpaths(*layer, document_network) else { return };
for manipulator in get_manipulator_groups(subpaths) {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor));
for selected_type in &[SelectedType::InHandle, SelectedType::OutHandle] {
state.deselect_point(ManipulatorPointId::new(manipulator.id, *selected_type));
}
}
pub fn select_anchor_point_by_id(&mut self, layer: LayerNodeIdentifier, id: ManipulatorGroupId, add_to_selection: bool) {
if !add_to_selection {
self.deselect_all_points();
}
let point = ManipulatorPointId::new(id, SelectedType::Anchor);
let Some(selected_state) = self.selected_shape_state.get_mut(&layer) else { return };
selected_state.select_point(point);
}
/// Selects all anchors, and deselects all handles, for the given layer.
pub fn select_all_anchors_in_layer(&mut self, document_network: &NodeNetwork, layer: LayerNodeIdentifier) {
let Some(state) = self.selected_shape_state.get_mut(&layer) else { return };
Self::select_all_anchors_in_layer_with_state(document_network, layer, state);
}
/// Selects all anchors, and deselects all handles, for the selected layers.
pub fn select_all_anchors_in_selected_layers(&mut self, document_network: &NodeNetwork) {
for (&layer, state) in self.selected_shape_state.iter_mut() {
Self::select_all_anchors_in_layer_with_state(document_network, layer, state);
}
}
pub fn deselect_all(&mut self) {
self.selected_shape_state.values_mut().for_each(|state| state.selected_points.clear());
/// Internal helper function that selects all anchors, and deselects all handles, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`].
fn select_all_anchors_in_layer_with_state(document_network: &NodeNetwork, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) {
let Some(subpaths) = get_subpaths(layer, document_network) else { return };
for manipulator in get_manipulator_groups(subpaths) {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor));
state.deselect_point(ManipulatorPointId::new(manipulator.id, SelectedType::InHandle));
state.deselect_point(ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle));
}
}
/// Deselects all points (anchors and handles) across every selected layer.
pub fn deselect_all_points(&mut self) {
for state in self.selected_shape_state.values_mut() {
state.selected_points.clear()
}
}
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
@ -407,6 +421,7 @@ impl ShapeState {
}
}
/// Returns an iterator over the currently selected layers to get their [`LayerNodeIdentifier`]s.
pub fn selected_layers(&self) -> impl Iterator<Item = &LayerNodeIdentifier> {
self.selected_shape_state.keys()
}
@ -427,14 +442,6 @@ impl ShapeState {
self.iter(document_network).flat_map(|subpaths| get_manipulator_groups(subpaths))
}
pub fn select_all_anchors(&mut self, document_network: &NodeNetwork, layer: LayerNodeIdentifier) {
let Some(subpaths) = get_subpaths(layer, document_network) else { return };
let Some(state) = self.selected_shape_state.get_mut(&layer) else { return };
for manipulator in get_manipulator_groups(subpaths) {
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor))
}
}
/// Provide the currently selected points by reference.
pub fn selected_points(&self) -> impl Iterator<Item = &'_ ManipulatorPointId> {
self.selected_shape_state.values().flat_map(|state| &state.selected_points)

View File

@ -429,10 +429,14 @@ impl Fsm for ArtboardToolFsmState {
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")]),
]),
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![
ArtboardToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
]),
ArtboardToolFsmState::ResizingBounds => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Preserve Aspect Ratio"), HintInfo::keys([Key::Alt], "From Center")]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -400,7 +400,7 @@ impl Fsm for BrushToolFsmState {
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw")]),
HintGroup(vec![HintInfo::keys([Key::BracketLeft, Key::BracketRight], "Shrink/Grow Brush")]),
]),
BrushToolFsmState::Drawing => HintData(vec![]),
BrushToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -260,7 +260,10 @@ impl Fsm for EllipseToolFsmState {
HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")])]),
EllipseToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -305,7 +305,7 @@ impl Fsm for FreehandToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]),
FreehandToolFsmState::Drawing => HintData(vec![]),
FreehandToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -459,7 +459,10 @@ impl Fsm for GradientToolFsmState {
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Gradient"),
HintInfo::keys([Key::Shift], "Snap 15°").prepend_plus(),
])]),
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°")])]),
GradientToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°")]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -234,11 +234,14 @@ impl Fsm for LineToolFsmState {
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(),
])]),
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
HintInfo::keys([Key::Shift], "Snap 15°"),
HintInfo::keys([Key::Alt], "From Center"),
HintInfo::keys([Key::Control], "Lock Angle"),
])]),
LineToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![
HintInfo::keys([Key::Shift], "Snap 15°"),
HintInfo::keys([Key::Alt], "From Center"),
HintInfo::keys([Key::Control], "Lock Angle"),
]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -154,16 +154,22 @@ impl Fsm for NavigateToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
NavigateToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Zoom In"), HintInfo::keys([Key::Shift], "Zoom Out").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Zoom"), HintInfo::keys([Key::Control], "Increments").prepend_plus()]),
HintGroup(vec![
HintInfo::keys_and_mouse([Key::Space], MouseMotion::LmbDrag, ""),
HintInfo::mouse(MouseMotion::MmbDrag, "Pan").prepend_slash(),
HintInfo::mouse(MouseMotion::MmbDrag, ""),
HintInfo::keys_and_mouse([Key::Space], MouseMotion::LmbDrag, "Pan").prepend_slash(),
]),
HintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, "Tilt")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Zoom"), HintInfo::keys([Key::Control], "Increments").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Zoom In"), HintInfo::keys([Key::Shift], "Zoom Out").prepend_plus()]),
]),
NavigateToolFsmState::Tilting => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")]),
]),
NavigateToolFsmState::Zooming => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Control], "Increments")]),
]),
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]),
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Increments")])]),
_ => HintData(Vec::new()),
};

View File

@ -29,6 +29,7 @@ pub enum PathToolMessage {
// Tool-specific messages
BreakPath,
DeselectAllPoints,
Delete,
DeleteAndBreakPath,
DragStop {
@ -58,7 +59,7 @@ pub enum PathToolMessage {
shift: Key,
},
RightClick,
SelectAllPoints,
SelectAllAnchors,
SelectedPointUpdated,
SelectedPointXChanged {
new_x: f64,
@ -163,7 +164,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
Delete,
NudgeSelectedPoints,
Enter,
SelectAllPoints,
SelectAllAnchors,
DeselectAllPoints,
BreakPath,
DeleteAndBreakPath,
),
@ -174,7 +176,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
DragStop,
PointerMove,
Delete,
SelectAllPoints,
BreakPath,
DeleteAndBreakPath,
),
@ -184,9 +185,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
PointerMove,
Delete,
Enter,
SelectAllPoints,
BreakPath,
DeleteAndBreakPath,
Escape,
RightClick,
),
InsertPoint => actions!(PathToolMessageDiscriminant;
Enter,
@ -324,11 +326,12 @@ impl PathToolData {
}
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = input.mouse.position;
shape_editor.select_all_anchors(&document.network, layer);
shape_editor.select_all_anchors_in_layer(&document.network, layer);
PathToolFsmState::Dragging
} else {
// Start drawing a box
}
// Start drawing a box
else {
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = input.mouse.position;
@ -495,7 +498,12 @@ impl Fsm for PathToolFsmState {
}
(PathToolFsmState::Dragging, PathToolMessage::Escape | PathToolMessage::RightClick) => {
responses.add(DocumentMessage::AbortTransaction);
shape_editor.deselect_all();
shape_editor.deselect_all_points();
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready
}
(PathToolFsmState::DrawingBox, PathToolMessage::Escape | PathToolMessage::RightClick) => {
responses.add(DocumentMessage::AbortTransaction);
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready
}
@ -525,7 +533,7 @@ impl Fsm for PathToolFsmState {
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all();
shape_editor.deselect_all_points();
shape_editor.change_point_selection(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, false);
responses.add(OverlaysMessage::Draw);
}
@ -570,8 +578,13 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectAllPoints) => {
shape_editor.select_all_points(&document.network);
(_, PathToolMessage::SelectAllAnchors) => {
shape_editor.select_all_anchors_in_selected_layers(&document.network);
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
(_, PathToolMessage::DeselectAllPoints) => {
shape_editor.deselect_all_points();
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
@ -609,26 +622,45 @@ impl Fsm for PathToolFsmState {
}
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let general_hint_data = HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]),
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
]);
let hint_data = match self {
PathToolFsmState::Ready => general_hint_data,
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
HintInfo::keys([Key::Alt], "Split/Align Handles (Toggle)"),
HintInfo::keys([Key::Shift], "Share Lengths of Aligned Handles"),
])]),
PathToolFsmState::DrawingBox => HintData(vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"),
HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus(),
])]),
PathToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
// TODO: Only show the following hints if at least one point is selected
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![HintInfo::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::keys([Key::Delete], "Delete Selected"),
// TODO: Only show the following hints if at least one anchor is selected
HintInfo::keys([Key::Accel], "No Dissolve").prepend_plus(),
HintInfo::keys([Key::Shift], "Break Anchor").prepend_plus(),
]),
]),
PathToolFsmState::Dragging => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![
// TODO: Make hint dynamically say "Make Handle Smooth" or "Make Handle Sharp" based on its current state
// TODO: Switch this to the "S" key
// TODO: Only show this if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown
HintInfo::keys([Key::Alt], "Toggle Smooth/Sharp Handles"),
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line)
// TODO: Show this only when a handle is being dragged
HintInfo::keys([Key::Shift], "Equidistant Handles (Smooth Only)"),
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged)
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged)
]),
]),
PathToolFsmState::DrawingBox => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"),
HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus(),
]),
]),
PathToolFsmState::InsertPoint => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel Insertion").prepend_slash()]),
]),
};

View File

@ -721,11 +721,23 @@ impl Fsm for PenToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Path")])]),
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Anchor"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Handle")]),
PenToolFsmState::PlacingAnchor => HintData(vec![
HintGroup(vec![
HintInfo::mouse(MouseMotion::Rmb, ""),
HintInfo::keys([Key::Escape], "").prepend_slash(),
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
HintGroup(vec![HintInfo::keys([Key::Alt], "Break Handle")]), // TODO: Show this only when dragging a handle
HintGroup(vec![HintInfo::keys([Key::Enter], "End Path")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]),
]),
PenToolFsmState::DraggingHandle => HintData(vec![
HintGroup(vec![
HintInfo::mouse(MouseMotion::Rmb, ""),
HintInfo::keys([Key::Escape], "").prepend_slash(),
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
HintGroup(vec![HintInfo::keys([Key::Alt], "Bend Handle")]),
]),
};

View File

@ -22,7 +22,7 @@ pub struct PolygonOptions {
fill: ToolColorOptions,
stroke: ToolColorOptions,
vertices: u32,
primitive_shape_type: PrimitiveShapeType,
polygon_type: PolygonType,
}
impl Default for PolygonOptions {
@ -32,7 +32,7 @@ impl Default for PolygonOptions {
line_weight: 5.,
fill: ToolColorOptions::new_secondary(),
stroke: ToolColorOptions::new_primary(),
primitive_shape_type: PrimitiveShapeType::Polygon,
polygon_type: PolygonType::Convex,
}
}
}
@ -53,8 +53,8 @@ pub enum PolygonToolMessage {
}
#[derive(PartialEq, Copy, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub enum PrimitiveShapeType {
Polygon = 0,
pub enum PolygonType {
Convex = 0,
Star = 1,
}
@ -63,7 +63,7 @@ pub enum PolygonOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
LineWeight(f64),
PrimitiveShapeType(PrimitiveShapeType),
PolygonType(PolygonType),
StrokeColor(Option<Color>),
StrokeColorType(ToolColorType),
Vertices(u32),
@ -93,16 +93,16 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder {
.widget_holder()
}
fn create_star_option_widget(primitive_shape_type: PrimitiveShapeType) -> WidgetHolder {
fn create_star_option_widget(polygon_type: PolygonType) -> WidgetHolder {
let entries = vec![
RadioEntryData::new("polygon")
.label("Polygon")
.on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PrimitiveShapeType(PrimitiveShapeType::Polygon)).into()),
RadioEntryData::new("convex")
.label("Convex")
.on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PolygonType(PolygonType::Convex)).into()),
RadioEntryData::new("star")
.label("Star")
.on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PrimitiveShapeType(PrimitiveShapeType::Star)).into()),
.on_update(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::PolygonType(PolygonType::Star)).into()),
];
RadioInput::new(entries).selected_index(Some(primitive_shape_type as u32)).widget_holder()
RadioInput::new(entries).selected_index(Some(polygon_type as u32)).widget_holder()
}
fn create_weight_widget(line_weight: f64) -> WidgetHolder {
@ -118,7 +118,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
impl LayoutHolder for PolygonTool {
fn layout(&self) -> Layout {
let mut widgets = vec![
create_star_option_widget(self.options.primitive_shape_type),
create_star_option_widget(self.options.polygon_type),
Separator::new(SeparatorType::Related).widget_holder(),
create_sides_widget(self.options.vertices),
];
@ -156,7 +156,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Polygon
};
match action {
PolygonOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
PolygonOptionsUpdate::PrimitiveShapeType(primitive_shape_type) => self.options.primitive_shape_type = primitive_shape_type,
PolygonOptionsUpdate::PolygonType(polygon_type) => self.options.polygon_type = polygon_type,
PolygonOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
@ -242,9 +242,9 @@ impl Fsm for PolygonToolFsmState {
polygon_data.start(document, input);
responses.add(DocumentMessage::StartTransaction);
let subpath = match tool_options.primitive_shape_type {
PrimitiveShapeType::Polygon => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
PrimitiveShapeType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
let subpath = match tool_options.polygon_type {
PolygonType::Convex => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
PolygonType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
};
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses);
polygon_data.layer = Some(layer);
@ -302,10 +302,13 @@ impl Fsm for PolygonToolFsmState {
let hint_data = match self {
PolygonToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polygon"),
HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect").prepend_plus(),
HintInfo::keys([Key::Shift], "Constrain Regular").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
PolygonToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect"), HintInfo::keys([Key::Alt], "From Center")])]),
PolygonToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Regular"), HintInfo::keys([Key::Alt], "From Center")]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -267,7 +267,10 @@ impl Fsm for RectangleToolFsmState {
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
])]),
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
RectangleToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });

View File

@ -1025,7 +1025,6 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection } => {
let hint_data = HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
HintGroup({
let mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, "Select Object"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()];
if *selection == NestedSelectionBehavior::Shallowest {
@ -1037,6 +1036,7 @@ impl Fsm for SelectToolFsmState {
HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"),
HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus(),
]),
HintGroup(vec![HintInfo::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(),
@ -1053,14 +1053,17 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Dragging => {
let hint_data = HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")]),
HintGroup(vec![
HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, "Move Duplicate"),
HintInfo::keys([Key::Alt], "Move Duplicate"),
HintInfo::keys([Key::Control, Key::KeyD], "Place Duplicate").add_mac_keys([Key::Command, Key::KeyD]),
]),
]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
SelectToolFsmState::DrawingBox { .. } => {
// TODO: Add hint and implement functionality for holding Shift to extend the selection, thus preventing the prior selection from being cleared
// TODO: Also fix the current functionality so canceling the box select doesn't clear the prior selection
let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}

View File

@ -297,6 +297,7 @@ impl Fsm for SplineToolFsmState {
let hint_data = match self {
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Spline")])]),
SplineToolFsmState::Drawing => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]),
HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]),
]),

View File

@ -455,11 +455,14 @@ impl Fsm for TextToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Text"), HintInfo::mouse(MouseMotion::Lmb, "Edit Text")])]),
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
HintInfo::keys([Key::Control, Key::Enter], "Commit Edit").add_mac_keys([Key::Command, Key::Enter]),
HintInfo::keys([Key::Escape], "Discard Edit"),
])]),
TextToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Place Text")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Edit Text")]),
]),
TextToolFsmState::Editing => HintData(vec![
HintGroup(vec![HintInfo::keys([Key::Escape], "Discard Changes")]),
HintGroup(vec![HintInfo::keys([Key::Control, Key::Enter], "Commit Changes").add_mac_keys([Key::Command, Key::Enter])]),
]),
};
responses.add(FrontendMessage::UpdateInputHints { hint_data });