Reorganize the menu bar and add additional commands to it

This commit is contained in:
Keavon Chambers
2025-03-01 13:44:28 -08:00
parent fd81d043a2
commit ddb0c8c249
10 changed files with 337 additions and 99 deletions
@@ -41,6 +41,7 @@ impl PreferencesDialogMessageHandler {
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
let zoom_with_scroll = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.zoom_with_scroll)
.tooltip(zoom_with_scroll_tooltip)
@@ -60,7 +61,11 @@ impl PreferencesDialogMessageHandler {
let editing_header = vec![TextLabel::new("Editing").italic(true).widget_holder()];
let selection_label = vec![Separator::new(SeparatorType::Unrelated).widget_holder(), TextLabel::new("Selection").widget_holder()];
let selection_label = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("Selection").widget_holder(),
];
let selection_mode = RadioInput::new(vec![
RadioEntryData::new(SelectionMode::Touched.to_string())
@@ -93,6 +98,11 @@ impl PreferencesDialogMessageHandler {
])
.selected_index(Some(preferences.selection_mode as u32))
.widget_holder();
let selection_mode = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
selection_mode,
];
// ============
// EXPERIMENTAL
@@ -102,6 +112,7 @@ impl PreferencesDialogMessageHandler {
let node_graph_section_tooltip = "Appearance of the wires running between node connections in the graph";
let node_graph_wires_label = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("Node Graph Wires").tooltip(node_graph_section_tooltip).widget_holder(),
];
@@ -117,9 +128,15 @@ impl PreferencesDialogMessageHandler {
])
.selected_index(Some(preferences.graph_wire_style as u32))
.widget_holder();
let graph_wire_style = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
graph_wire_style,
];
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
.tooltip(vello_tooltip)
@@ -135,6 +152,7 @@ impl PreferencesDialogMessageHandler {
let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills.";
let vector_meshes = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.vector_meshes)
.tooltip(vector_mesh_tooltip)
@@ -169,14 +187,10 @@ impl PreferencesDialogMessageHandler {
LayoutGroup::Row { widgets: zoom_with_scroll },
LayoutGroup::Row { widgets: editing_header },
LayoutGroup::Row { widgets: selection_label },
LayoutGroup::Row {
widgets: vec![Separator::new(SeparatorType::Unrelated).widget_holder(), selection_mode],
},
LayoutGroup::Row { widgets: selection_mode },
LayoutGroup::Row { widgets: experimental_header },
LayoutGroup::Row { widgets: node_graph_wires_label },
LayoutGroup::Row {
widgets: vec![Separator::new(SeparatorType::Unrelated).widget_holder(), graph_wire_style],
},
LayoutGroup::Row { widgets: graph_wire_style },
LayoutGroup::Row { widgets: use_vello },
LayoutGroup::Row { widgets: vector_meshes },
// LayoutGroup::Row { widgets: imaginate_server_hostname },
@@ -60,6 +60,9 @@ pub enum DocumentMessage {
FlipSelectedLayers {
flip_axis: FlipAxis,
},
RotateSelectedLayers {
degrees: f64,
},
GraphViewOverlay {
open: bool,
},
@@ -454,6 +454,30 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
}
}
DocumentMessage::RotateSelectedLayers { degrees } => {
// Get the bounding box of selected layers in viewport space
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
// Calculate the center of the bounding box to use as rotation pivot
let center = (max + min) / 2.;
// Transform that moves pivot point to origin
let bbox_trans = DAffine2::from_translation(-center);
let mut added_transaction = false;
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(&self.network_interface) {
if !added_transaction {
responses.add(DocumentMessage::AddTransaction);
added_transaction = true;
}
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_angle(degrees.to_radians()),
transform_in: TransformIn::Scope { scope: bbox_trans },
skip_rerender: false,
});
}
}
}
DocumentMessage::GraphViewOverlay { open } => {
self.graph_view_overlay_open = open;
@@ -1,8 +1,10 @@
use graphene_std::vector::misc::BooleanOperation;
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
use crate::messages::prelude::*;
pub struct MenuBarMessageData {
@@ -199,8 +201,26 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Duplicate".into(),
icon: Some("Copy".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::DuplicateSelectedLayers),
action: MenuBarEntry::create_action(|_| DocumentMessage::DuplicateSelectedLayers.into()),
disabled: no_active_document || !has_selected_nodes,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Delete".into(),
icon: Some("Trash".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
action: MenuBarEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
disabled: no_active_document || !has_selected_nodes,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Remove Artboards".into(),
label: "Convert to Infinite Canvas".into(),
icon: Some("Artboard".into()),
action: MenuBarEntry::create_action(|_| DocumentMessage::RemoveArtboards.into()),
disabled: no_active_document,
@@ -212,17 +232,17 @@ impl LayoutHolder for MenuBarMessageHandler {
"Layer".into(),
no_active_document,
MenuBarEntryChildren(vec![
vec![MenuBarEntry {
label: "New".into(),
icon: Some("NewLayer".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder),
action: MenuBarEntry::create_action(|_| DocumentMessage::CreateEmptyFolder.into()),
disabled: no_active_document,
..MenuBarEntry::default()
}],
vec![
MenuBarEntry {
label: "New Layer".into(),
icon: Some("NewLayer".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder),
action: MenuBarEntry::create_action(|_| DocumentMessage::CreateEmptyFolder.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Group Selected".into(),
label: "Group".into(),
icon: Some("Folder".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers),
action: MenuBarEntry::create_action(|_| {
@@ -235,14 +255,216 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Delete Selected".into(),
icon: Some("Trash".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
action: MenuBarEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
disabled: no_active_document || !has_selected_nodes,
label: "Ungroup".into(),
icon: Some("FolderOpen".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::UngroupSelectedLayers),
action: MenuBarEntry::create_action(|_| DocumentMessage::UngroupSelectedLayers.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Hide/Show".into(),
icon: Some("EyeHide".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility),
action: MenuBarEntry::create_action(|_| DocumentMessage::ToggleSelectedVisibility.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Lock/Unlock".into(),
icon: Some("PadlockLocked".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked),
action: MenuBarEntry::create_action(|_| DocumentMessage::ToggleSelectedLocked.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Grab".into(),
icon: Some("TransformationGrab".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Rotate".into(),
icon: Some("TransformationRotate".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Scale".into(),
icon: Some("TransformationScale".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Arrange".into(),
icon: Some("StackHollow".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "Raise To Front".into(),
icon: Some("Stack".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Raise".into(),
icon: Some("StackRaise".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Lower".into(),
icon: Some("StackLower".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Lower to Back".into(),
icon: Some("StackBottom".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Reverse".into(),
icon: Some("StackReverse".into()),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersReverse.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
}],
]),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Align".into(),
icon: Some("AlignVerticalCenter".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren({
let choices = [
[
(AlignAxis::X, AlignAggregate::Min, "AlignLeft", "Align Left"),
(AlignAxis::X, AlignAggregate::Center, "AlignHorizontalCenter", "Align Horizontal Center"),
(AlignAxis::X, AlignAggregate::Max, "AlignRight", "Align Right"),
],
[
(AlignAxis::Y, AlignAggregate::Min, "AlignTop", "Align Top"),
(AlignAxis::Y, AlignAggregate::Center, "AlignVerticalCenter", "Align Vertical Center"),
(AlignAxis::Y, AlignAggregate::Max, "AlignBottom", "Align Bottom"),
],
];
choices
.into_iter()
.map(|group| {
group
.into_iter()
.map(|(axis, aggregate, icon, name)| MenuBarEntry {
label: name.into(),
icon: Some(icon.into()),
action: MenuBarEntry::create_action(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
})
.collect()
})
.collect()
}),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Flip".into(),
icon: Some("FlipVertical".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{
[(FlipAxis::X, "FlipHorizontal", "Horizontal"), (FlipAxis::Y, "FlipVertical", "Vertical")]
.into_iter()
.map(|(flip_axis, icon, name)| MenuBarEntry {
label: name.into(),
icon: Some(icon.into()),
action: MenuBarEntry::create_action(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
})
.collect()
}]),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Turn".into(),
icon: Some("TurnPositive90".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{
[(-90., "TurnNegative90", "Turn -90°"), (90., "TurnPositive90", "Turn 90°")]
.into_iter()
.map(|(degrees, icon, name)| MenuBarEntry {
label: name.into(),
icon: Some(icon.into()),
action: MenuBarEntry::create_action(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
})
.collect()
}]),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Boolean".into(),
icon: Some("BooleanSubtractFront".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{
let operations = BooleanOperation::list();
let icons = BooleanOperation::icons();
operations
.into_iter()
.zip(icons)
.map(move |(operation, icon)| MenuBarEntry {
label: operation.to_string(),
icon: Some(icon.into()),
action: MenuBarEntry::create_action(move |_| {
let group_folder_type = GroupFolderType::BooleanOperation(operation);
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
}),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
})
.collect()
}]),
..MenuBarEntry::default()
},
],
]),
),
MenuBarEntry::new_root(
"Select".into(),
no_active_document,
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "Select All".into(),
@@ -262,11 +484,14 @@ impl LayoutHolder for MenuBarMessageHandler {
},
MenuBarEntry {
label: "Select Parent".into(),
icon: Some("SelectParent".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectParentLayer),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectParentLayer.into()),
disabled: no_active_document || !has_selected_nodes,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Previous Selection".into(),
icon: Some("HistoryUndo".into()),
@@ -284,82 +509,6 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Grab Selected".into(),
icon: Some("TransformationGrab".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Rotate Selected".into(),
icon: Some("TransformationRotate".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Scale Selected".into(),
icon: Some("TransformationScale".into()),
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Order".into(),
icon: Some("StackHollow".into()),
action: MenuBarEntry::no_action(),
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "Raise To Front".into(),
icon: Some("Stack".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Raise".into(),
icon: Some("StackRaise".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Lower".into(),
icon: Some("StackLower".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Lower to Back".into(),
icon: Some("StackBottom".into()),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Reverse".into(),
icon: Some("StackReverse".into()),
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersReverse.into()),
disabled: no_active_document || !has_selected_layers,
..MenuBarEntry::default()
}],
]),
..MenuBarEntry::default()
}],
]),
),
MenuBarEntry::new_root(
@@ -168,6 +168,18 @@ impl SelectTool {
})
}
fn turn_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> {
[(-90., "TurnNegative90", "Turn -90°"), (90., "TurnPositive90", "Turn 90°")]
.into_iter()
.map(move |(degrees, icon, name)| {
IconButton::new(icon, 24)
.tooltip(name)
.on_update(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into())
.disabled(disabled)
.widget_holder()
})
}
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> {
let operations = BooleanOperation::list();
let icons = BooleanOperation::icons();
@@ -218,6 +230,10 @@ impl LayoutHolder for SelectTool {
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.flip_widgets(disabled));
// Turn
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.turn_widgets(disabled));
// Boolean
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));