diff --git a/desktop/wrapper/src/utils.rs b/desktop/wrapper/src/utils.rs index f3a27054c..0fcf7480c 100644 --- a/desktop/wrapper/src/utils.rs +++ b/desktop/wrapper/src/utils.rs @@ -3,8 +3,8 @@ pub(crate) mod menu { use base64::engine::Engine; use base64::engine::general_purpose::STANDARD as BASE64; - use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LayoutKey, LayoutKeysGroup}; - use graphite_editor::messages::input_mapper::utility_types::misc::ActionKeys; + use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKey, LabeledShortcut}; + use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut; use graphite_editor::messages::layout::LayoutMessage; use graphite_editor::messages::tool::tool_messages::tool_prelude::{LayoutGroup, LayoutTarget, MenuListEntry, SubLayout, Widget, WidgetId}; @@ -84,7 +84,7 @@ pub(crate) mod menu { } let shortcut = match shortcut_keys { - Some(ActionKeys::Keys(LayoutKeysGroup(keys))) => convert_layout_keys_to_shortcut(keys), + Some(ActionShortcut::Shortcut(LabeledShortcut(shortcut))) => convert_labeled_keys_to_shortcut(shortcut), _ => None, }; @@ -126,11 +126,11 @@ pub(crate) mod menu { items } - fn convert_layout_keys_to_shortcut(layout_keys: &Vec) -> Option { + fn convert_labeled_keys_to_shortcut(labeled_keys: &Vec) -> Option { let mut key: Option = None; let mut modifiers = Modifiers::default(); - for layout_key in layout_keys { - match layout_key.key() { + for labeled_key in labeled_keys { + match labeled_key.key() { Key::Shift => modifiers |= Modifiers::SHIFT, Key::Control => modifiers |= Modifiers::CONTROL, Key::Alt => modifiers |= Modifiers::ALT, diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 9582e0472..8abf6c576 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -7,7 +7,6 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; use crate::messages::prelude::*; -use crate::messages::tool::utility_types::HintData; use glam::IVec2; use graph_craft::document::NodeId; use graphene_std::raster::Image; @@ -257,10 +256,6 @@ pub enum FrontendMessage { UpdateGraphFadeArtwork { percentage: f64, }, - UpdateInputHints { - #[serde(rename = "hintData")] - hint_data: HintData, - }, UpdateLayersPanelControlBarLeftLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, @@ -335,6 +330,16 @@ pub enum FrontendMessage { #[serde(rename = "wirePath")] wire_path: Option, }, + UpdateWelcomeScreenButtonsLayout { + #[serde(rename = "layoutTarget")] + layout_target: LayoutTarget, + diff: Vec, + }, + UpdateStatusBarHintsLayout { + #[serde(rename = "layoutTarget")] + layout_target: LayoutTarget, + diff: Vec, + }, UpdateWorkingColorsLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, diff --git a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs index 36ac91721..e5b97c2bc 100644 --- a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -4,10 +4,18 @@ use bitflags::bitflags; use std::fmt::{self, Display, Formatter}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; +// =========== +// StorageType +// =========== + // TODO: Increase size of type /// Edit this to specify the storage type used. pub type StorageType = u128; +// ========= +// KeyStates +// ========= + // Base-2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type const STORAGE_SIZE: u32 = (std::mem::size_of::() * 8).trailing_zeros(); const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE; @@ -23,11 +31,19 @@ pub fn all_required_modifiers_pressed(keyboard_state: &KeyStates, modifiers: &Ke all_modifiers_without_pressed_modifiers.is_empty() } +// =========== +// KeyPosition +// =========== + pub enum KeyPosition { Pressed, Released, } +// ============ +// ModifierKeys +// ============ + bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] #[repr(transparent)] @@ -40,6 +56,10 @@ bitflags! { } } +// === +// Key +// === + // Currently this is mostly based on the JS `KeyboardEvent.code` list: // But in the future, especially once users can customize keyboard mappings, we should deviate more from this so we have actual symbols // like `+` (which doesn't exist because it's the shifted version of `=` on the US keyboard, after which these scan codes are named). @@ -198,14 +218,19 @@ pub enum Key { // Other keys that aren't part of the W3C spec // - /// "Cmd" on Mac (not present on other platforms) + /// "Cmd" on Mac (not present on other platforms). Command, - /// "Ctrl" on Windows/Linux, "Cmd" on Mac + /// "Ctrl" on Windows/Linux, "Cmd" on Mac. Accel, + /// Left mouse button click (LMB). MouseLeft, + /// Right mouse button click (RMB). MouseRight, + /// Middle mouse button click (MMB). MouseMiddle, + /// Mouse backward navigation button (typically on the side of the mouse). MouseBack, + /// Mouse forward navigation button (typically on the side of the mouse). MouseForward, // Fake keys for displaying special labels in the UI @@ -225,11 +250,11 @@ impl fmt::Display for Key { // Writing system keys const DIGIT_PREFIX: &str = "Digit"; - if key_name.len() == DIGIT_PREFIX.len() + 1 && &key_name[0..DIGIT_PREFIX.len()] == "Digit" { + if key_name.len() == DIGIT_PREFIX.len() + 1 && &key_name[0..DIGIT_PREFIX.len()] == DIGIT_PREFIX { return write!(f, "{}", key_name.chars().skip(DIGIT_PREFIX.len()).collect::()); } const KEY_PREFIX: &str = "Key"; - if key_name.len() == KEY_PREFIX.len() + 1 && &key_name[0..KEY_PREFIX.len()] == "Key" { + if key_name.len() == KEY_PREFIX.len() + 1 && &key_name[0..KEY_PREFIX.len()] == KEY_PREFIX { return write!(f, "{}", key_name.chars().skip(KEY_PREFIX.len()).collect::()); } @@ -313,26 +338,12 @@ impl fmt::Display for Key { } } -impl From for LayoutKey { - fn from(key: Key) -> Self { - Self { key, label: key.to_string() } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct LayoutKey { - key: Key, - label: String, -} - -impl LayoutKey { - pub fn key(&self) -> Key { - self.key - } -} - pub const NUMBER_OF_KEYS: usize = Key::_KeysVariantCount as usize - 1; +// ========= +// KeysGroup +// ========= + /// Only `Key`s that exist on a physical keyboard should be used. #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct KeysGroup(pub Vec); @@ -365,21 +376,25 @@ impl fmt::Display for KeysGroup { } } -impl From for String { - fn from(keys: KeysGroup) -> Self { - let layout_keys: LayoutKeysGroup = keys.into(); - serde_json::to_string(&layout_keys).expect("Failed to serialize KeysGroup") +// ========== +// LabeledKey +// ========== + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct LabeledKey { + key: Key, + label: String, +} + +impl LabeledKey { + pub fn key(&self) -> Key { + self.key } } -#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct LayoutKeysGroup(pub Vec); - -impl From for LayoutKeysGroup { - fn from(keys_group: KeysGroup) -> Self { - Self(keys_group.0.into_iter().map(|key| key.into()).collect()) - } -} +// =========== +// MouseMotion +// =========== #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum MouseMotion { @@ -397,6 +412,45 @@ pub enum MouseMotion { MmbDrag, } +// ======================= +// LabeledKeyOrMouseMotion +// ======================= + +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +#[serde(untagged)] +pub enum LabeledKeyOrMouseMotion { + Key(LabeledKey), + MouseMotion(MouseMotion), +} + +impl From for LabeledKeyOrMouseMotion { + fn from(key: Key) -> Self { + match key { + Key::MouseLeft => Self::MouseMotion(MouseMotion::Lmb), + Key::MouseRight => Self::MouseMotion(MouseMotion::Rmb), + Key::MouseMiddle => Self::MouseMotion(MouseMotion::Mmb), + _ => Self::Key(LabeledKey { key, label: key.to_string() }), + } + } +} + +// =============== +// LabeledShortcut +// =============== + +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct LabeledShortcut(pub Vec); + +impl From for LabeledShortcut { + fn from(keys_group: KeysGroup) -> Self { + Self(keys_group.0.into_iter().map(|key| key.into()).collect()) + } +} + +// ========= +// BitVector +// ========= + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BitVector([StorageType; LENGTH]); diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index 11ad50dac..bd0354d28 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -117,14 +117,23 @@ macro_rules! mapping { }}; } -/// Constructs an `ActionKeys` macro with a certain `Action` variant, conveniently wrapped in `Some()`. -macro_rules! action_keys { +/// Constructs an `ActionShortcut` macro with a certain `Action` variant, conveniently wrapped in `Some()`. +macro_rules! action_shortcut { ($action:expr_2021) => { - Some(crate::messages::input_mapper::utility_types::misc::ActionKeys::Action($action.into())) + Some(crate::messages::input_mapper::utility_types::misc::ActionShortcut::Action($action.into())) }; } -pub(crate) use action_keys; +macro_rules! action_shortcut_manual { + ($($keys:expr),*) => { + Some(crate::messages::input_mapper::utility_types::misc::ActionShortcut::Shortcut( + crate::messages::input_mapper::utility_types::input_keyboard::LabeledShortcut(vec![$($keys.into()),*]).into(), + )) + }; +} + +pub(crate) use action_shortcut; +pub(crate) use action_shortcut_manual; pub(crate) use entry; pub(crate) use mapping; pub(crate) use modifiers; diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index 7733d6a27..75343e0a7 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -1,4 +1,4 @@ -use super::input_keyboard::{KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed}; +use super::input_keyboard::{KeysGroup, LabeledShortcut, all_required_modifiers_pressed}; use crate::messages::input_mapper::key_mapping::MappingVariant; use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS}; use crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_BUTTONS; @@ -128,28 +128,24 @@ pub struct MappingEntry { } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum ActionKeys { +pub enum ActionShortcut { Action(MessageDiscriminant), - #[serde(rename = "keys")] - Keys(LayoutKeysGroup), + #[serde(rename = "shortcut")] + Shortcut(LabeledShortcut), } -impl ActionKeys { - pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) -> String { +impl ActionShortcut { + pub fn realize_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) { match self { Self::Action(action) => { if let Some(keys) = action_input_mapping(action) { - let description = keys.to_string(); - *self = Self::Keys(keys.into()); - description + *self = Self::Shortcut(keys.into()); } else { - *self = Self::Keys(KeysGroup::default().into()); - String::new() + *self = Self::Shortcut(KeysGroup::default().into()); } } - Self::Keys(keys) => { - warn!("Calling `.to_keys()` on a `ActionKeys::Keys` is a mistake/bug. Keys are: {keys:?}."); - String::new() + Self::Shortcut(shortcut) => { + warn!("Calling `.to_keys()` on a `ActionShortcut::Shortcut` is a mistake/bug. Shortcut is: {shortcut:?}."); } } } diff --git a/editor/src/messages/layout/layout_message.rs b/editor/src/messages/layout/layout_message.rs index b18c0055a..ff0ded389 100644 --- a/editor/src/messages/layout/layout_message.rs +++ b/editor/src/messages/layout/layout_message.rs @@ -12,6 +12,9 @@ pub enum LayoutMessage { layout: Layout, layout_target: LayoutTarget, }, + DestroyLayout { + layout_target: LayoutTarget, + }, WidgetValueCommit { layout_target: LayoutTarget, widget_id: WidgetId, diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 304c2e41c..dff1095ee 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -39,6 +39,11 @@ impl MessageHandler> for LayoutMessageHa LayoutMessage::SendLayout { layout, layout_target } => { self.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping); } + LayoutMessage::DestroyLayout { layout_target } => { + if let Some(layout) = self.layouts.get_mut(layout_target as usize) { + *layout = Default::default(); + } + } LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => { self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses); } @@ -78,7 +83,7 @@ impl LayoutMessageHandler { LayoutGroup::Section { layout, .. } => { stack.extend(layout.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val))); } - LayoutGroup::Table { rows } => { + LayoutGroup::Table { rows, .. } => { for (row_index, row) in rows.iter().enumerate() { for (cell_index, cell) in row.iter().enumerate() { // Return if this is the correct ID @@ -310,6 +315,7 @@ impl LayoutMessageHandler { responses.add(callback_message); } Widget::ImageLabel(_) => {} + Widget::ShortcutLabel(_) => {} Widget::IconLabel(_) => {} Widget::NodeCatalog(node_type_input) => match action { WidgetValueAction::Commit => { @@ -509,8 +515,10 @@ impl LayoutMessageHandler { LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { layout_target, diff }, LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff }, LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff }, + LayoutTarget::StatusBarHints => FrontendMessage::UpdateStatusBarHintsLayout { layout_target, diff }, LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff }, LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff }, + LayoutTarget::WelcomeScreenButtons => FrontendMessage::UpdateWelcomeScreenButtonsLayout { layout_target, diff }, LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff }, LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"), diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index f25261d5a..22ad94eaf 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -3,7 +3,6 @@ use super::widgets::input_widgets::*; use super::widgets::label_widgets::*; use crate::application::generate_uuid; use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; -use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::prelude::*; use std::sync::Arc; @@ -20,6 +19,8 @@ impl core::fmt::Display for WidgetId { #[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize, specta::Type)] #[repr(u8)] pub enum LayoutTarget { + /// The spreadsheet panel allows for the visualisation of data in the graph. + DataPanel, /// Contains the action buttons at the bottom of the dialog. Must be shown with the `FrontendMessage::DisplayDialog` message. DialogButtons, /// Contains the contents of the dialog's primary column. Must be shown with the `FrontendMessage::DisplayDialog` message. @@ -30,24 +31,26 @@ pub enum LayoutTarget { DocumentBar, /// Contains the dropdown for design / select / guide mode found on the top left of the canvas. DocumentMode, + /// Controls for adding, grouping, and deleting layers at the bottom of the Layers panel. + LayersPanelBottomBar, /// Blending options at the top of the Layers panel. LayersPanelControlLeftBar, /// Selected layer status (locked/hidden) at the top of the Layers panel. LayersPanelControlRightBar, - /// Controls for adding, grouping, and deleting layers at the bottom of the Layers panel. - LayersPanelBottomBar, /// The dropdown menu at the very top of the application: File, Edit, etc. MenuBar, /// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons. NodeGraphControlBar, /// The body of the Properties panel containing many collapsable sections. PropertiesPanel, - /// The spredsheet panel allows for the visualisation of data in the graph. - DataPanel, + /// The contextual input key/mouse combination shortcuts shown in the status bar at the bottom of the window. + StatusBarHints, /// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown. ToolOptions, /// The vertical buttons for all of the tools on the left of the canvas. ToolShelf, + /// The quick access buttons found on the welcome screen, shown when no documents are open. + WelcomeScreenButtons, /// The color swatch for the working colors and a flip and reset button found at the bottom of the tool shelf. WorkingColors, @@ -198,7 +201,7 @@ impl<'a> Iterator for WidgetIter<'a> { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Table { rows }) => { + Some(LayoutGroup::Table { rows, .. }) => { self.table.extend(rows.iter().flatten().rev()); self.next() } @@ -248,7 +251,7 @@ impl<'a> Iterator for WidgetIterMut<'a> { self.current_slice = Some(widgets); self.next() } - Some(LayoutGroup::Table { rows }) => { + Some(LayoutGroup::Table { rows, .. }) => { self.table.extend(rows.iter_mut().flatten().rev()); self.next() } @@ -281,6 +284,7 @@ pub enum LayoutGroup { Table { #[serde(rename = "tableWidgets")] rows: Vec>, + unstyled: bool, }, #[serde(rename = "section")] Section { @@ -332,7 +336,7 @@ impl LayoutGroup { Widget::TextInput(x) => &mut x.tooltip_label, Widget::TextLabel(x) => &mut x.tooltip_label, Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_label, - Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue, + Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::ShortcutLabel(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue, }; if val.is_empty() { val.clone_from(&label); @@ -368,7 +372,7 @@ impl LayoutGroup { Widget::TextInput(x) => &mut x.tooltip_description, Widget::TextLabel(x) => &mut x.tooltip_description, Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_description, - Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue, + Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::ShortcutLabel(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue, }; if val.is_empty() { val.clone_from(&description); @@ -506,7 +510,7 @@ impl WidgetHolder { && button1.icon == button2.icon && button1.tooltip_label == button2.tooltip_label && button1.tooltip_description == button2.tooltip_description - && button1.shortcut_keys == button2.shortcut_keys + && button1.tooltip_shortcut == button2.tooltip_shortcut && button1.popover_min_width == button2.popover_min_width { let mut new_widget_path = widget_path.to_vec(); @@ -564,6 +568,7 @@ pub enum Widget { IconLabel(IconLabel), ImageButton(ImageButton), ImageLabel(ImageLabel), + ShortcutLabel(ShortcutLabel), NodeCatalog(NodeCatalog), NumberInput(NumberInput), ParameterExposeButton(ParameterExposeButton), @@ -607,30 +612,22 @@ pub enum DiffUpdate { impl DiffUpdate { /// Append the keyboard shortcut to the tooltip where applicable pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) { - // Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip - let apply_shortcut_to_tooltip = |shortcut_keys: &mut ActionKeys, tooltip_shortcut: &mut String| { - let shortcut_text = shortcut_keys.to_keys(action_input_mapping); - - if matches!(shortcut_keys, ActionKeys::Keys(_)) && !shortcut_text.is_empty() { - tooltip_shortcut.push_str(&shortcut_text); - } - }; - - // Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip + // Go through each widget to convert `ActionShortcut::Action` to `ActionShortcut::Shortcut` and append the key combination to the widget tooltip let convert_tooltip = |widget_holder: &mut WidgetHolder| { // Handle all the widgets that have tooltips - let mut shortcut_keys = match &mut widget_holder.widget { - Widget::BreadcrumbTrailButtons(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::CheckboxInput(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::ColorInput(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::DropdownInput(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::FontInput(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::IconButton(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::NumberInput(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::ParameterExposeButton(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::PopoverButton(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::TextButton(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), - Widget::ImageButton(widget) => Some((&mut widget.tooltip_shortcut, &mut widget.shortcut_keys)), + let tooltip_shortcut = match &mut widget_holder.widget { + Widget::BreadcrumbTrailButtons(widget) => widget.tooltip_shortcut.as_mut(), + Widget::CheckboxInput(widget) => widget.tooltip_shortcut.as_mut(), + Widget::ColorInput(widget) => widget.tooltip_shortcut.as_mut(), + Widget::DropdownInput(widget) => widget.tooltip_shortcut.as_mut(), + Widget::FontInput(widget) => widget.tooltip_shortcut.as_mut(), + Widget::IconButton(widget) => widget.tooltip_shortcut.as_mut(), + Widget::NumberInput(widget) => widget.tooltip_shortcut.as_mut(), + Widget::ParameterExposeButton(widget) => widget.tooltip_shortcut.as_mut(), + Widget::PopoverButton(widget) => widget.tooltip_shortcut.as_mut(), + Widget::TextButton(widget) => widget.tooltip_shortcut.as_mut(), + Widget::ImageButton(widget) => widget.tooltip_shortcut.as_mut(), + Widget::ShortcutLabel(widget) => widget.shortcut.as_mut(), Widget::IconLabel(_) | Widget::ImageLabel(_) | Widget::CurveInput(_) @@ -643,34 +640,32 @@ impl DiffUpdate { | Widget::TextLabel(_) | Widget::WorkingColorsInput(_) => None, }; - if let Some((tooltip_shortcut, Some(shortcut_keys))) = &mut shortcut_keys { - apply_shortcut_to_tooltip(shortcut_keys, tooltip_shortcut); + + // Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut` + if let Some(tooltip_shortcut) = tooltip_shortcut { + tooltip_shortcut.realize_shortcut(action_input_mapping); } // Handle RadioInput separately because its tooltips are children of the widget if let Widget::RadioInput(radio_input) = &mut widget_holder.widget { for radio_entry_data in &mut radio_input.entries { - if let RadioEntryData { - tooltip_shortcut, - shortcut_keys: Some(shortcut_keys), - .. - } = radio_entry_data - { - apply_shortcut_to_tooltip(shortcut_keys, tooltip_shortcut); + // Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut` + if let Some(tooltip_shortcut) = radio_entry_data.tooltip_shortcut.as_mut() { + tooltip_shortcut.realize_shortcut(action_input_mapping); } } } }; // Recursively fill menu list entries with their realized shortcut keys specific to the current bindings and platform - let apply_action_keys_to_menu_lists = |entry_sections: &mut MenuListEntrySections| { + let apply_action_shortcut_to_menu_lists = |entry_sections: &mut MenuListEntrySections| { struct RecursiveWrapper<'a>(&'a dyn Fn(&mut MenuListEntrySections, &RecursiveWrapper)); let recursive_wrapper = RecursiveWrapper(&|entry_sections: &mut MenuListEntrySections, recursive_wrapper| { for entries in entry_sections { for entry in entries { - // Convert the shortcut actions to keys for this menu entry - if let Some(shortcut_keys) = &mut entry.shortcut_keys { - shortcut_keys.to_keys(action_input_mapping); + // Convert `ActionShortcut::Action` to `ActionShortcut::Shortcut` + if let Some(tooltip_shortcut) = &mut entry.tooltip_shortcut { + tooltip_shortcut.realize_shortcut(action_input_mapping); } // Recursively call this inner closure on the menu's children @@ -683,8 +678,8 @@ impl DiffUpdate { // Apply shortcut conversions to all widgets that have menu lists let convert_menu_lists = |widget_holder: &mut WidgetHolder| match &mut widget_holder.widget { - Widget::DropdownInput(dropdown_input) => apply_action_keys_to_menu_lists(&mut dropdown_input.entries), - Widget::TextButton(text_button) => apply_action_keys_to_menu_lists(&mut text_button.menu_list_children), + Widget::DropdownInput(dropdown_input) => apply_action_shortcut_to_menu_lists(&mut dropdown_input.entries), + Widget::TextButton(text_button) => apply_action_shortcut_to_menu_lists(&mut text_button.menu_list_children), _ => {} }; diff --git a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs index 3d47b5e48..6915a0922 100644 --- a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -1,4 +1,4 @@ -use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; use crate::messages::tool::tool_messages::tool_prelude::WidgetCallback; @@ -29,10 +29,7 @@ pub struct IconButton { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -63,10 +60,7 @@ pub struct PopoverButton { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, #[serde(rename = "popoverLayout")] pub popover_layout: SubLayout, @@ -104,10 +98,7 @@ pub struct ParameterExposeButton { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -148,10 +139,7 @@ pub struct TextButton { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, #[serde(rename = "menuListChildren")] pub menu_list_children: MenuListEntrySections, @@ -183,10 +171,7 @@ pub struct ImageButton { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -228,10 +213,7 @@ pub struct ColorInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -258,10 +240,7 @@ pub struct BreadcrumbTrailButtons { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index 304c5f5cf..a7fa9fbfc 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -1,4 +1,4 @@ -use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use derivative::*; use graphene_std::Color; @@ -23,15 +23,12 @@ pub struct CheckboxInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, #[serde(rename = "forLabel")] #[derivative(Debug = "ignore", PartialEq = "ignore")] pub for_label: CheckboxId, - #[serde(skip)] - pub shortcut_keys: Option, - // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] @@ -51,7 +48,6 @@ impl Default for CheckboxInput { tooltip_label: Default::default(), tooltip_description: Default::default(), tooltip_shortcut: Default::default(), - shortcut_keys: Default::default(), for_label: CheckboxId::new(), on_update: Default::default(), on_commit: Default::default(), @@ -106,10 +102,7 @@ pub struct DropdownInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Styling #[serde(rename = "minWidth")] @@ -146,14 +139,7 @@ pub struct MenuListEntry { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - // TODO: Make this serde(skip) - #[serde(rename = "shortcutKeys")] - pub shortcut_keys: Option, - - #[serde(rename = "shortcutRequiresLock")] - pub shortcut_requires_lock: bool, + pub tooltip_shortcut: Option, pub children: MenuListEntrySections, @@ -190,10 +176,7 @@ pub struct FontInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -218,10 +201,7 @@ pub struct NumberInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Disabled pub disabled: bool, @@ -394,10 +374,7 @@ pub struct RadioEntryData { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, - - #[serde(skip)] - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -436,7 +413,7 @@ pub struct TextAreaInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -467,7 +444,7 @@ pub struct TextInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, pub centered: bool, @@ -502,7 +479,7 @@ pub struct CurveInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] @@ -529,7 +506,7 @@ pub struct ReferencePointInput { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, // Callbacks #[serde(skip)] diff --git a/editor/src/messages/layout/utility_types/widgets/label_widgets.rs b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs index 4ab7845e3..3e21eda93 100644 --- a/editor/src/messages/layout/utility_types/widgets/label_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs @@ -1,4 +1,5 @@ use super::input_widgets::CheckboxId; +use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use derivative::*; use graphite_proc_macros::WidgetBuilder; @@ -16,7 +17,7 @@ pub struct IconLabel { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, } #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)] @@ -74,7 +75,7 @@ pub struct TextLabel { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, #[serde(rename = "forCheckbox")] #[derivative(PartialEq = "ignore")] @@ -102,7 +103,13 @@ pub struct ImageLabel { pub tooltip_description: String, #[serde(rename = "tooltipShortcut")] - pub tooltip_shortcut: String, + pub tooltip_shortcut: Option, } -// TODO: Add UserInputLabel +#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)] +#[derivative(Debug, PartialEq)] +pub struct ShortcutLabel { + // This is wrapped in an Option to satisfy the requirement that widgets implement Default + #[widget_builder(constructor)] + pub shortcut: Option, +} diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index 0fef51e0e..c5e59b30a 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -232,7 +232,7 @@ impl TableRowLayout for Vec { rows.insert(0, column_headings(&["", "element"])); - vec![LayoutGroup::Table { rows }] + vec![LayoutGroup::Table { rows, unstyled: false }] } } @@ -274,7 +274,7 @@ impl TableRowLayout for Table { rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"])); - vec![LayoutGroup::Table { rows }] + vec![LayoutGroup::Table { rows, unstyled: false }] } } @@ -490,7 +490,7 @@ impl TableRowLayout for Vector { } } - vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows }] + vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows, unstyled: false }] } } diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ea16e97ce..b9d6799ce 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -7,7 +7,7 @@ use super::utility_types::network_interface::{self, NodeNetworkInterface, Transa use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid}; use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_EXTENSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; -use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::input_mapper::utility_types::macros::action_shortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; @@ -2212,20 +2212,20 @@ impl DocumentMessageHandler { let mut widgets = vec![ IconButton::new("PlaybackToStart", 24) .tooltip_label("Restart Animation") - .shortcut_keys(action_keys!(AnimationMessageDiscriminant::RestartAnimation)) + .tooltip_shortcut(action_shortcut!(AnimationMessageDiscriminant::RestartAnimation)) .on_update(|_| AnimationMessage::RestartAnimation.into()) .disabled(time == Duration::ZERO) .widget_holder(), IconButton::new(if animation_is_playing { "PlaybackPause" } else { "PlaybackPlay" }, 24) .tooltip_label(if animation_is_playing { "Pause Animation" } else { "Play Animation" }) - .shortcut_keys(action_keys!(AnimationMessageDiscriminant::ToggleLivePreview)) + .tooltip_shortcut(action_shortcut!(AnimationMessageDiscriminant::ToggleLivePreview)) .on_update(|_| AnimationMessage::ToggleLivePreview.into()) .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), CheckboxInput::new(self.overlays_visibility_settings.all) .icon("Overlays") .tooltip_label("Overlays") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleOverlaysVisibility)) .on_update(|optional_input: &CheckboxInput| { DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked, @@ -2474,7 +2474,7 @@ impl DocumentMessageHandler { CheckboxInput::new(snapping_state.snapping_enabled) .icon("Snapping") .tooltip_label("Snapping") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSnapping)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSnapping)) .on_update(move |optional_input: &CheckboxInput| { DocumentMessage::SetSnapping { closure: Some(|snapping_state| &mut snapping_state.snapping_enabled), @@ -2544,7 +2544,7 @@ impl DocumentMessageHandler { CheckboxInput::new(self.snapping_state.grid_snapping) .icon("Grid") .tooltip_label("Grid") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleGridVisibility)) .on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into()) .widget_holder(), PopoverButton::new() @@ -2626,7 +2626,7 @@ impl DocumentMessageHandler { .icon(Some((if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" }).into())) .hover_icon(Some((if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" }).into())) .tooltip_label(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" }) - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) .on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into()) .widget_holder(), ]); @@ -2778,14 +2778,14 @@ impl DocumentMessageHandler { IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24) .hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into())) .tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" }) - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked)) .on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into()) .disabled(!has_selection) .widget_holder(), IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24) .hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into())) .tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" }) - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility)) .on_update(|_| DocumentMessage::ToggleSelectedVisibility.into()) .disabled(!has_selection) .widget_holder(), @@ -2850,7 +2850,7 @@ impl DocumentMessageHandler { Separator::new(SeparatorType::Unrelated).widget_holder(), IconButton::new("Folder", 24) .tooltip_label("Group Selected") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers)) .on_update(|_| { let group_folder_type = GroupFolderType::Layer; DocumentMessage::GroupSelectedLayers { group_folder_type }.into() @@ -2859,12 +2859,12 @@ impl DocumentMessageHandler { .widget_holder(), IconButton::new("NewLayer", 24) .tooltip_label("New Layer") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder)) .on_update(|_| DocumentMessage::CreateEmptyFolder.into()) .widget_holder(), IconButton::new("Trash", 24) .tooltip_label("Delete Selected") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers)) .on_update(|_| DocumentMessage::DeleteSelectedLayers.into()) .disabled(!has_selection) .widget_holder(), @@ -3148,17 +3148,17 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand let mut list = vec![ IconButton::new("ZoomIn", 24) .tooltip_label("Zoom In") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomIncrease)) .on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) .widget_holder(), IconButton::new("ZoomOut", 24) .tooltip_label("Zoom Out") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomDecrease)) .on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) .widget_holder(), IconButton::new("ZoomReset", 24) .tooltip_label("Reset Tilt and Zoom to 100%") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) .disabled(ptz.tilt().abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4) .widget_holder(), @@ -3168,7 +3168,7 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand IconButton::new("Reverse", 24) .tooltip_label("Unflip Canvas") .tooltip_description("Flip the canvas back to its standard orientation.") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasFlip)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasFlip)) .on_update(|_| NavigationMessage::CanvasFlip.into()) .widget_holder(), ); diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 3d02d9ce2..00e280b51 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -77,9 +77,7 @@ impl MessageHandler> for Navigat NavigationMessage::BeginCanvasPan => { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); - responses.add(FrontendMessage::UpdateInputHints { - hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), - }); + HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]).send_layout(responses); self.mouse_position = ipp.mouse.position; let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { @@ -96,12 +94,11 @@ impl MessageHandler> for Navigat responses.add(NavigationMessage::BeginCanvasPan); } else { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - responses.add(FrontendMessage::UpdateInputHints { - hint_data: HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]), - ]), - }); + HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]), + ]) + .send_layout(responses); self.navigation_operation = NavigationOperation::Tilt { tilt_original_for_abort: ptz.tilt(), @@ -119,12 +116,11 @@ impl MessageHandler> for Navigat }; responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn }); - responses.add(FrontendMessage::UpdateInputHints { - hint_data: HintData(vec![ - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo::keys([Key::Shift], "Increments")]), - ]), - }); + HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo::keys([Key::Shift], "Increments")]), + ]) + .send_layout(responses); self.navigation_operation = NavigationOperation::Zoom { zoom_raw_not_snapped: ptz.zoom(), diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 58a1dc807..572450686 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,8 +1,7 @@ use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode}; use super::{document_node_definitions, node_properties}; use crate::consts::GRID_SIZE; -use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; -use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::document_message_handler::navigation_controls; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; @@ -2108,7 +2107,7 @@ impl NodeGraphMessageHandler { .icon(Some("Node".to_string())) .tooltip_label("New Node") .tooltip_description("To add a node at the pointer location, perform the shortcut in an open area of the graph.") - .tooltip_shortcut(Key::MouseRight.to_string()) + .tooltip_shortcut(action_shortcut_manual!(Key::MouseRight)) .popover_layout({ // Showing only compatible types let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) { @@ -2154,7 +2153,7 @@ impl NodeGraphMessageHandler { // IconButton::new("Folder", 24) .tooltip_label("Group Selected") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers)) .on_update(|_| { let group_folder_type = GroupFolderType::Layer; DocumentMessage::GroupSelectedLayers { group_folder_type }.into() @@ -2163,12 +2162,12 @@ impl NodeGraphMessageHandler { .widget_holder(), IconButton::new("NewLayer", 24) .tooltip_label("New Layer") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder)) .on_update(|_| DocumentMessage::CreateEmptyFolder.into()) .widget_holder(), IconButton::new("Trash", 24) .tooltip_label("Delete Selected") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers)) .on_update(|_| DocumentMessage::DeleteSelectedLayers.into()) .disabled(!has_selection) .widget_holder(), @@ -2178,14 +2177,14 @@ impl NodeGraphMessageHandler { IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24) .hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into())) .tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" }) - .shortcut_keys(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked)) + .tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedLocked)) .on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into()) .disabled(!has_selection || !selection_includes_layers) .widget_holder(), IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24) .hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into())) .tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" }) - .shortcut_keys(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) + .tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) .on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into()) .disabled(!has_selection) .widget_holder(), @@ -2225,7 +2224,7 @@ impl NodeGraphMessageHandler { .icon(Some("FrameAll".to_string())) .tooltip_label("Preview") .tooltip_description("Temporarily set the graph output to the selected node or layer. Perform the shortcut on a node or layer for quick access.") - .tooltip_shortcut(KeysGroup(vec![Key::Alt, Key::MouseLeft]).to_string()) + .tooltip_shortcut(action_shortcut_manual!(Key::Alt, Key::MouseLeft)) .on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into()) .widget_holder(); widgets.extend([Separator::new(SeparatorType::Unrelated).widget_holder(), button]); @@ -2284,7 +2283,7 @@ impl NodeGraphMessageHandler { .icon(Some("GraphViewOpen".into())) .hover_icon(Some("GraphViewClosed".into())) .tooltip_label("Hide Node Graph") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle)) .on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into()) .widget_holder(), ]); @@ -2720,8 +2719,7 @@ impl NodeGraphMessageHandler { // Cancel the ongoing action if wiring || dragging_nodes || dragging_box_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 }); + HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]).send_layout(responses); return; } @@ -2749,7 +2747,7 @@ impl NodeGraphMessageHandler { HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, "Enter Node Subgraph")]), HintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, "Preview Node Output")]), ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } } diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index 47bc1eed2..63e1b8baa 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -452,8 +452,7 @@ impl TransformOperation { } hint_groups.push(HintGroup(typing_hints)); - let hint_data = HintData(hint_groups); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + HintData(hint_groups).send_layout(responses); } pub fn is_constraint_to_axis(&self) -> bool { diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index a8e434a42..0c7b744e4 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -1,5 +1,5 @@ use crate::messages::debug::utility_types::MessageLoggingVerbosity; -use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::input_mapper::utility_types::macros::action_shortcut; 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::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType}; @@ -68,7 +68,7 @@ impl LayoutHolder for MenuBarMessageHandler { let preferences = MenuListEntry::new("Preferences…") .label("Preferences…") .icon("Settings") - .shortcut_keys(action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog)) + .tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestPreferencesDialog)) .on_commit(|_| DialogMessage::RequestPreferencesDialog.into()); let menu_bar_buttons = vec![ @@ -89,21 +89,21 @@ impl LayoutHolder for MenuBarMessageHandler { vec![ MenuListEntry::new("Hide Graphite") .label("Hide Graphite") - .shortcut_keys(action_keys!(AppWindowMessageDiscriminant::Hide)) + .tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Hide)) .on_commit(|_| AppWindowMessage::Hide.into()), MenuListEntry::new("Hide Others") .label("Hide Others") - .shortcut_keys(action_keys!(AppWindowMessageDiscriminant::HideOthers)) + .tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::HideOthers)) .on_commit(|_| AppWindowMessage::HideOthers.into()), MenuListEntry::new("Show All") .label("Show All") - .shortcut_keys(action_keys!(AppWindowMessageDiscriminant::ShowAll)) + .tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::ShowAll)) .on_commit(|_| AppWindowMessage::ShowAll.into()), ], vec![ MenuListEntry::new("Quit Graphite") .label("Quit Graphite") - .shortcut_keys(action_keys!(AppWindowMessageDiscriminant::Close)) + .tooltip_shortcut(action_shortcut!(AppWindowMessageDiscriminant::Close)) .on_commit(|_| AppWindowMessage::Close.into()), ], ]) @@ -117,11 +117,11 @@ impl LayoutHolder for MenuBarMessageHandler { .label("New…") .icon("File") .on_commit(|_| DialogMessage::RequestNewDocumentDialog.into()) - .shortcut_keys(action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog)), + .tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog)), MenuListEntry::new("Open…") .label("Open…") .icon("Folder") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::OpenDocument)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::OpenDocument)) .on_commit(|_| PortfolioMessage::OpenDocument.into()), MenuListEntry::new("Open Demo Artwork…") .label("Open Demo Artwork…") @@ -132,13 +132,13 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Close") .label("Close") .icon("Close") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation)) .on_commit(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()) .disabled(no_active_document), MenuListEntry::new("Close All") .label("Close All") .icon("CloseAll") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::CloseAllDocumentsWithConfirmation)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::CloseAllDocumentsWithConfirmation)) .on_commit(|_| PortfolioMessage::CloseAllDocumentsWithConfirmation.into()) .disabled(no_active_document), ], @@ -146,14 +146,14 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Save") .label("Save") .icon("Save") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SaveDocument)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SaveDocument)) .on_commit(|_| DocumentMessage::SaveDocument.into()) .disabled(no_active_document), #[cfg(not(target_family = "wasm"))] MenuListEntry::new("Save As…") .label("Save As…") .icon("Save") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SaveDocumentAs)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SaveDocumentAs)) .on_commit(|_| DocumentMessage::SaveDocumentAs.into()) .disabled(no_active_document), ], @@ -161,12 +161,12 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Import…") .label("Import…") .icon("FileImport") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::Import)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Import)) .on_commit(|_| PortfolioMessage::Import.into()), MenuListEntry::new("Export…") .label("Export…") .icon("FileExport") - .shortcut_keys(action_keys!(DialogMessageDiscriminant::RequestExportDialog)) + .tooltip_shortcut(action_shortcut!(DialogMessageDiscriminant::RequestExportDialog)) .on_commit(|_| DialogMessage::RequestExportDialog.into()) .disabled(no_active_document), ], @@ -182,13 +182,13 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Undo") .label("Undo") .icon("HistoryUndo") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::Undo)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::Undo)) .on_commit(|_| DocumentMessage::Undo.into()) .disabled(no_active_document), MenuListEntry::new("Redo") .label("Redo") .icon("HistoryRedo") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::Redo)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::Redo)) .on_commit(|_| DocumentMessage::Redo.into()) .disabled(no_active_document), ], @@ -196,19 +196,19 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Cut") .label("Cut") .icon("Cut") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::Cut)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Cut)) .on_commit(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Copy") .label("Copy") .icon("Copy") - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::Copy)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Copy)) .on_commit(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Paste") .label("Paste") .icon("Paste") - .shortcut_keys(action_keys!(FrontendMessageDiscriminant::TriggerPaste)) + .tooltip_shortcut(action_shortcut!(FrontendMessageDiscriminant::TriggerPaste)) .on_commit(|_| FrontendMessage::TriggerPaste.into()) .disabled(no_active_document), ], @@ -216,13 +216,13 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Duplicate") .label("Duplicate") .icon("Copy") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::DuplicateSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DuplicateSelectedLayers)) .on_commit(|_| DocumentMessage::DuplicateSelectedLayers.into()) .disabled(no_active_document || !has_selected_nodes), MenuListEntry::new("Delete") .label("Delete") .icon("Trash") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers)) .on_commit(|_| DocumentMessage::DeleteSelectedLayers.into()) .disabled(no_active_document || !has_selected_nodes), ], @@ -238,13 +238,12 @@ impl LayoutHolder for MenuBarMessageHandler { TextButton::new("Layer") .label("Layer") .flush(true) - .disabled(no_active_document) .menu_list_children(vec![ vec![ MenuListEntry::new("New") .label("New") .icon("NewLayer") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder)) .on_commit(|_| DocumentMessage::CreateEmptyFolder.into()) .disabled(no_active_document), ], @@ -252,7 +251,7 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Group") .label("Group") .icon("Folder") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GroupSelectedLayers)) .on_commit(|_| { DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer, @@ -263,7 +262,7 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Ungroup") .label("Ungroup") .icon("FolderOpen") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::UngroupSelectedLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::UngroupSelectedLayers)) .on_commit(|_| DocumentMessage::UngroupSelectedLayers.into()) .disabled(no_active_document || !has_selected_layers), ], @@ -271,13 +270,13 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Hide/Show") .label("Hide/Show") .icon("EyeHide") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility)) .on_commit(|_| DocumentMessage::ToggleSelectedVisibility.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Lock/Unlock") .label("Lock/Unlock") .icon("PadlockLocked") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked)) .on_commit(|_| DocumentMessage::ToggleSelectedLocked.into()) .disabled(no_active_document || !has_selected_layers), ], @@ -285,19 +284,19 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Grab") .label("Grab") .icon("TransformationGrab") - .shortcut_keys(action_keys!(TransformLayerMessageDiscriminant::BeginGrab)) + .tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginGrab)) .on_commit(|_| TransformLayerMessage::BeginGrab.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Rotate") .label("Rotate") .icon("TransformationRotate") - .shortcut_keys(action_keys!(TransformLayerMessageDiscriminant::BeginRotate)) + .tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginRotate)) .on_commit(|_| TransformLayerMessage::BeginRotate.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Scale") .label("Scale") .icon("TransformationScale") - .shortcut_keys(action_keys!(TransformLayerMessageDiscriminant::BeginScale)) + .tooltip_shortcut(action_shortcut!(TransformLayerMessageDiscriminant::BeginScale)) .on_commit(|_| TransformLayerMessage::BeginScale.into()) .disabled(no_active_document || !has_selected_layers), ], @@ -311,25 +310,25 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Raise To Front") .label("Raise To Front") .icon("Stack") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront)) .on_commit(|_| DocumentMessage::SelectedLayersRaiseToFront.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Raise") .label("Raise") .icon("StackRaise") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise)) .on_commit(|_| DocumentMessage::SelectedLayersRaise.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Lower") .label("Lower") .icon("StackLower") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectedLayersLower)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower)) .on_commit(|_| DocumentMessage::SelectedLayersLower.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Lower to Back") .label("Lower to Back") .icon("StackBottom") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack)) .on_commit(|_| DocumentMessage::SelectedLayersLowerToBack.into()) .disabled(no_active_document || !has_selected_layers), ], @@ -508,25 +507,24 @@ impl LayoutHolder for MenuBarMessageHandler { TextButton::new("Select") .label("Select") .flush(true) - .disabled(no_active_document) .menu_list_children(vec![ vec![ MenuListEntry::new("Select All") .label("Select All") .icon("SelectAll") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectAllLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers)) .on_commit(|_| DocumentMessage::SelectAllLayers.into()) .disabled(no_active_document), MenuListEntry::new("Deselect All") .label("Deselect All") .icon("DeselectAll") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeselectAllLayers)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::DeselectAllLayers)) .on_commit(|_| DocumentMessage::DeselectAllLayers.into()) .disabled(no_active_document || !has_selected_nodes), MenuListEntry::new("Select Parent") .label("Select Parent") .icon("SelectParent") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectParentLayer)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectParentLayer)) .on_commit(|_| DocumentMessage::SelectParentLayer.into()) .disabled(no_active_document || !has_selected_nodes), ], @@ -534,13 +532,13 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Previous Selection") .label("Previous Selection") .icon("HistoryUndo") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectionStepBack)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectionStepBack)) .on_commit(|_| DocumentMessage::SelectionStepBack.into()) .disabled(!has_selection_history.0), MenuListEntry::new("Next Selection") .label("Next Selection") .icon("HistoryRedo") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::SelectionStepForward)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::SelectionStepForward)) .on_commit(|_| DocumentMessage::SelectionStepForward.into()) .disabled(!has_selection_history.1), ], @@ -549,19 +547,18 @@ impl LayoutHolder for MenuBarMessageHandler { TextButton::new("View") .label("View") .flush(true) - .disabled(no_active_document) .menu_list_children(vec![ vec![ MenuListEntry::new("Tilt") .label("Tilt") .icon("Tilt") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::BeginCanvasTilt)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::BeginCanvasTilt)) .on_commit(|_| NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: true }.into()) .disabled(no_active_document || node_graph_open), MenuListEntry::new("Reset Tilt") .label("Reset Tilt") .icon("TiltReset") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasTiltSet)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasTiltSet)) .on_commit(|_| NavigationMessage::CanvasTiltSet { angle_radians: 0.into() }.into()) .disabled(no_active_document || node_graph_open || !self.canvas_tilted), ], @@ -569,37 +566,37 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Zoom In") .label("Zoom In") .icon("ZoomIn") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomIncrease)) .on_commit(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) .disabled(no_active_document), MenuListEntry::new("Zoom Out") .label("Zoom Out") .icon("ZoomOut") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasZoomDecrease)) .on_commit(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) .disabled(no_active_document), MenuListEntry::new("Zoom to Selection") .label("Zoom to Selection") .icon("FrameSelected") - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::FitViewportToSelection)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::FitViewportToSelection)) .on_commit(|_| NavigationMessage::FitViewportToSelection.into()) .disabled(no_active_document || !has_selected_layers), MenuListEntry::new("Zoom to Fit") .label("Zoom to Fit") .icon("FrameAll") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasToFitAll)) .on_commit(|_| DocumentMessage::ZoomCanvasToFitAll.into()) .disabled(no_active_document), MenuListEntry::new("Zoom to 100%") .label("Zoom to 100%") .icon("Zoom1x") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent)) .on_commit(|_| DocumentMessage::ZoomCanvasTo100Percent.into()) .disabled(no_active_document), MenuListEntry::new("Zoom to 200%") .label("Zoom to 200%") .icon("Zoom2x") - .shortcut_keys(action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent)) + .tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent)) .on_commit(|_| DocumentMessage::ZoomCanvasTo200Percent.into()) .disabled(no_active_document), ], @@ -607,7 +604,7 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Flip") .label("Flip") .icon(if self.canvas_flipped { "CheckboxChecked" } else { "CheckboxUnchecked" }) - .shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasFlip)) + .tooltip_shortcut(action_shortcut!(NavigationMessageDiscriminant::CanvasFlip)) .on_commit(|_| NavigationMessage::CanvasFlip.into()) .disabled(no_active_document || node_graph_open), ], @@ -615,7 +612,7 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Rulers") .label("Rulers") .icon(if self.rulers_visible { "CheckboxChecked" } else { "CheckboxUnchecked" }) - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::ToggleRulers)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleRulers)) .on_commit(|_| PortfolioMessage::ToggleRulers.into()) .disabled(no_active_document), ], @@ -629,19 +626,19 @@ impl LayoutHolder for MenuBarMessageHandler { MenuListEntry::new("Properties") .label("Properties") .icon(if self.properties_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen)) .on_commit(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()), MenuListEntry::new("Layers") .label("Layers") .icon(if self.layers_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen)) .on_commit(|_| PortfolioMessage::ToggleLayersPanelOpen.into()), ], vec![ MenuListEntry::new("Data") .label("Data") .icon(if self.data_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }) - .shortcut_keys(action_keys!(PortfolioMessageDiscriminant::ToggleDataPanelOpen)) + .tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::ToggleDataPanelOpen)) .on_commit(|_| PortfolioMessage::ToggleDataPanelOpen.into()), ], ]) @@ -702,7 +699,7 @@ impl LayoutHolder for MenuBarMessageHandler { "CheckboxChecked".to_string() } }).unwrap_or_default()) - .shortcut_keys(action_keys!(DebugMessageDiscriminant::MessageOff)) + .tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageOff)) .on_commit(|_| DebugMessage::MessageOff.into()), MenuListEntry::new("Print Messages: Only Names") .label("Print Messages: Only Names") @@ -716,7 +713,7 @@ impl LayoutHolder for MenuBarMessageHandler { "CheckboxChecked".to_string() } }).unwrap_or_default()) - .shortcut_keys(action_keys!(DebugMessageDiscriminant::MessageNames)) + .tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageNames)) .on_commit(|_| DebugMessage::MessageNames.into()), MenuListEntry::new("Print Messages: Full Contents") .label("Print Messages: Full Contents") @@ -730,7 +727,7 @@ impl LayoutHolder for MenuBarMessageHandler { "CheckboxChecked".to_string() } }).unwrap_or_default()) - .shortcut_keys(action_keys!(DebugMessageDiscriminant::MessageContents)) + .tooltip_shortcut(action_shortcut!(DebugMessageDiscriminant::MessageContents)) .on_commit(|_| DebugMessage::MessageContents.into()), ], vec![MenuListEntry::new("Trigger a Crash").label("Trigger a Crash").icon("Warning").on_commit(|_| panic!())], diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 58209fe64..b08de4830 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -109,6 +109,7 @@ pub enum PortfolioMessage { parent_and_insert_index: Option<(LayerNodeIdentifier, usize)>, }, PrevDocument, + RequestWelcomeScreenButtonsLayout, SetActivePanel { panel: PanelType, }, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index e770c9d61..05fcf1080 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -7,6 +7,7 @@ use crate::messages::animation::TimingInformation; use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument}; +use crate::messages::input_mapper::utility_types::macros::action_shortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; @@ -20,7 +21,7 @@ use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed; -use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; +use crate::messages::tool::utility_types::{HintData, ToolType}; use crate::messages::viewport::ToPhysical; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; use derivative::*; @@ -218,8 +219,7 @@ impl MessageHandler> for Portfolio responses.add(PropertiesPanelMessage::Clear); responses.add(DocumentMessage::ClearLayersPanel); responses.add(DataPanelMessage::ClearLayout); - let hint_data = HintData(vec![HintGroup(vec![])]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + HintData::clear_layout(responses); } for document_id in &self.document_ids { @@ -243,8 +243,7 @@ impl MessageHandler> for Portfolio responses.add(PropertiesPanelMessage::Clear); responses.add(DocumentMessage::ClearLayersPanel); responses.add(DataPanelMessage::ClearLayout); - let hint_data = HintData(vec![HintGroup(vec![])]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + HintData::clear_layout(responses); } // Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed) @@ -882,6 +881,53 @@ impl MessageHandler> for Portfolio responses.add(PortfolioMessage::SelectDocument { document_id: prev_id }); } } + PortfolioMessage::RequestWelcomeScreenButtonsLayout => { + let donate = "https://graphite.rs/donate/"; + + let table = LayoutGroup::Table { + unstyled: true, + rows: vec![ + vec![ + TextButton::new("New Document") + .icon(Some("File".into())) + .flush(true) + .on_commit(|_| DialogMessage::RequestNewDocumentDialog.into()) + .widget_holder(), + ShortcutLabel::new(action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog)).widget_holder(), + ], + vec![ + TextButton::new("Open Document") + .icon(Some("Folder".into())) + .flush(true) + .on_commit(|_| PortfolioMessage::OpenDocument.into()) + .widget_holder(), + ShortcutLabel::new(action_shortcut!(PortfolioMessageDiscriminant::OpenDocument)).widget_holder(), + ], + vec![ + TextButton::new("Open Demo Artwork") + .icon(Some("Image".into())) + .flush(true) + .on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into()) + .widget_holder(), + ], + vec![ + TextButton::new("Support the Development Fund") + .icon(Some("Heart".into())) + .flush(true) + .on_commit(move |_| FrontendMessage::TriggerVisitLink { url: donate.to_string() }.into()) + .widget_holder(), + ], + ], + }; + + responses.add(LayoutMessage::DestroyLayout { + layout_target: LayoutTarget::WelcomeScreenButtons, + }); + responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(vec![table])), + layout_target: LayoutTarget::WelcomeScreenButtons, + }); + } PortfolioMessage::SetActivePanel { panel } => { self.active_panel = panel; responses.add(DocumentMessage::SetActivePanel { active_panel: self.active_panel }); diff --git a/editor/src/messages/portfolio/utility_types.rs b/editor/src/messages/portfolio/utility_types.rs index 3edc26d9e..4bcdd56de 100644 --- a/editor/src/messages/portfolio/utility_types.rs +++ b/editor/src/messages/portfolio/utility_types.rs @@ -41,6 +41,7 @@ pub enum KeyboardPlatformLayout { pub enum PanelType { #[default] Document, + Welcome, Layers, Properties, DataPanel, @@ -50,6 +51,7 @@ impl From for PanelType { fn from(value: String) -> Self { match value.as_str() { "Document" => PanelType::Document, + "Welcome" => PanelType::Welcome, "Layers" => PanelType::Layers, "Properties" => PanelType::Properties, "Data" => PanelType::DataPanel, diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 2b336eb57..f9516d933 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -7,7 +7,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayProvid use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext; -use crate::messages::tool::utility_types::ToolType; +use crate::messages::tool::utility_types::{HintData, ToolType}; use crate::node_graph_executor::NodeGraphExecutor; use graphene_std::raster::color::Color; @@ -190,7 +190,7 @@ impl MessageHandler> for ToolMessageHandler responses.add(OverlaysMessage::RemoveProvider { provider: ARTBOARD_OVERLAY_PROVIDER }); - responses.add(FrontendMessage::UpdateInputHints { hint_data: Default::default() }); + HintData::clear_layout(responses); responses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() }); self.tool_is_active = false; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 6dfebafda..7289d6814 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -601,7 +601,7 @@ impl Fsm for ArtboardToolFsmState { ]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index d08703274..c3200d7ce 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -464,7 +464,7 @@ impl Fsm for BrushToolFsmState { BrushToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index d1b9e4053..3ff5f3748 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -138,7 +138,7 @@ impl Fsm for EyedropperToolFsmState { } }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 86e64a53d..f32b07b94 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -158,7 +158,7 @@ impl Fsm for FillToolFsmState { FillToolFsmState::Filling => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 8db8a4dbf..102946812 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -346,7 +346,7 @@ impl Fsm for FreehandToolFsmState { FreehandToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index bea5eaebb..d5d2c9e70 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -530,7 +530,7 @@ impl Fsm for GradientToolFsmState { ]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index ed50348ea..0f997a84d 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -166,7 +166,7 @@ impl Fsm for NavigateToolFsmState { ]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index cc59f6db8..59d18ebcd 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -4,7 +4,7 @@ use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, }; -use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; +use crate::messages::input_mapper::utility_types::macros::action_shortcut_manual; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments}; @@ -270,7 +270,7 @@ impl LayoutHolder for PathTool { .icon("Dot") .tooltip_label("Point Editing Mode") .tooltip_description("To multi-select modes, perform the shortcut shown.") - .tooltip_shortcut(KeysGroup(vec![Key::Shift, Key::MouseLeft]).to_string()) + .tooltip_shortcut(action_shortcut_manual!(Key::Shift, Key::MouseLeft)) .on_update(|_| PathToolMessage::TogglePointEditing.into()) .widget_holder(); let segment_editing_mode = CheckboxInput::new(self.options.path_editing_mode.segment_editing_mode) @@ -278,7 +278,7 @@ impl LayoutHolder for PathTool { .icon("Remove") .tooltip_label("Segment Editing Mode") .tooltip_description("To multi-select modes, perform the shortcut shown.") - .tooltip_shortcut(KeysGroup(vec![Key::Shift, Key::MouseLeft]).to_string()) + .tooltip_shortcut(action_shortcut_manual!(Key::Shift, Key::MouseLeft)) .on_update(|_| PathToolMessage::ToggleSegmentEditing.into()) .widget_holder(); @@ -3589,5 +3589,5 @@ fn update_dynamic_hints( ]), PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index bcc80fdea..4089f1342 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -2288,7 +2288,7 @@ impl Fsm for PenToolFsmState { } }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index abe887d13..0787e36aa 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1728,7 +1728,7 @@ impl Fsm for SelectToolFsmState { HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]), ]), ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::Dragging { axis, using_compass, has_dragged, .. } if *has_dragged => { let mut hint_data = vec![ @@ -1743,7 +1743,7 @@ impl Fsm for SelectToolFsmState { hint_data.push(HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")])); }; let hint_data = HintData(hint_data); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::Drawing { has_drawn, .. } if *has_drawn => { let hint_data = HintData(vec![ @@ -1753,7 +1753,7 @@ impl Fsm for SelectToolFsmState { // TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048) // HintGroup(vec![HintInfo::keys([Key::Shift], "Extend")]) ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging { .. } => {} SelectToolFsmState::ResizingBounds => { @@ -1761,25 +1761,25 @@ impl Fsm for SelectToolFsmState { HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::RotatingBounds => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]), ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::SkewingBounds { .. } => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]), ]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } SelectToolFsmState::DraggingPivot => { let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]); - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } } } diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 11a73809d..bfe41e213 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1187,5 +1187,5 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 86252d408..18aca9588 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -503,7 +503,7 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::MergingEndpoints => HintData(vec![]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 8fd21543d..070db2c96 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -939,7 +939,7 @@ impl Fsm for TextToolFsmState { ]), }; - responses.add(FrontendMessage::UpdateInputHints { hint_data }); + hint_data.send_layout(responses); } fn update_cursor(&self, responses: &mut VecDeque) { diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index b0f26be3d..c07758404 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -4,9 +4,9 @@ use super::common_functionality::shape_editor::ShapeState; use super::tool_messages::*; use crate::messages::broadcast::BroadcastMessage; use crate::messages::broadcast::event::EventMessage; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion}; -use crate::messages::input_mapper::utility_types::macros::action_keys; -use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LabeledKeyOrMouseMotion, LabeledShortcut, MouseMotion}; +use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual}; +use crate::messages::input_mapper::utility_types::misc::ActionShortcut; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider; use crate::messages::preferences::PreferencesMessageHandler; @@ -125,12 +125,12 @@ impl DocumentToolData { widgets: vec![ IconButton::new("SwapVertical", 16) .tooltip_label("Swap") - .shortcut_keys(action_keys!(ToolMessageDiscriminant::SwapColors)) + .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors)) .on_update(|_| ToolMessage::SwapColors.into()) .widget_holder(), IconButton::new("WorkingColors", 16) .tooltip_label("Reset") - .shortcut_keys(action_keys!(ToolMessageDiscriminant::ResetColors)) + .tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors)) .on_update(|_| ToolMessage::ResetColors.into()) .widget_holder(), ], @@ -245,12 +245,12 @@ impl LayoutHolder for ToolData { ToolAvailability::Available(tool) => ToolEntry::new(tool.tool_type(), tool.icon_name()) .tooltip_label(tool.tooltip_label()) - .shortcut_keys(action_keys!(tool_type_to_activate_tool_message(tool.tool_type()))), + .tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(tool.tool_type()))), ToolAvailability::AvailableAsShape(shape) => ToolEntry::new(shape.tool_type(), shape.icon_name()) .tooltip_label(shape.tooltip_label()) .tooltip_description(shape.tooltip_description()) - .shortcut_keys(action_keys!(tool_type_to_activate_tool_message(shape.tool_type()))), + .tooltip_shortcut(action_shortcut!(tool_type_to_activate_tool_message(shape.tool_type()))), ToolAvailability::ComingSoon(tool) => tool.clone(), } }) @@ -258,7 +258,7 @@ impl LayoutHolder for ToolData { ) .flat_map(|group| { let separator = std::iter::once(Separator::new(SeparatorType::Section).direction(SeparatorDirection::Vertical).widget_holder()); - let buttons = group.into_iter().map(|ToolEntry { tooltip_label, tooltip_description, tooltip_shortcut, shortcut_keys, tool_type, icon_name }| { + let buttons = group.into_iter().map(|ToolEntry { tooltip_label, tooltip_description, tooltip_shortcut, tool_type, icon_name }| { let coming_soon = tooltip_description.contains("Coming soon."); IconButton::new(icon_name, 32) @@ -270,7 +270,6 @@ impl LayoutHolder for ToolData { .tooltip_label(tooltip_label.clone()) .tooltip_description(tooltip_description) .tooltip_shortcut(tooltip_shortcut) - .shortcut_keys(shortcut_keys) .on_update(move |_| { match tool_type { ToolType::Line => ToolMessage::ActivateToolShapeLine.into(), @@ -306,8 +305,7 @@ pub struct ToolEntry { pub icon_name: String, pub tooltip_label: String, pub tooltip_description: String, - pub tooltip_shortcut: String, - pub shortcut_keys: Option, + pub tooltip_shortcut: Option, } #[derive(Debug)] @@ -429,26 +427,26 @@ fn list_tools_in_groups() -> Vec> { ToolEntry::new(ToolType::Heal, "RasterHealTool") .tooltip_label("Heal Tool") .tooltip_description("Coming soon.") - .tooltip_shortcut(Key::KeyJ.to_string()), + .tooltip_shortcut(action_shortcut_manual!(Key::KeyJ)), ), ToolAvailability::ComingSoon( ToolEntry::new(ToolType::Clone, "RasterCloneTool") .tooltip_label("Clone Tool") .tooltip_description("Coming soon.") - .tooltip_shortcut(Key::KeyC.to_string()), + .tooltip_shortcut(action_shortcut_manual!(Key::KeyC)), ), ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip_label("Patch Tool").tooltip_description("Coming soon.")), ToolAvailability::ComingSoon( ToolEntry::new(ToolType::Detail, "RasterDetailTool") .tooltip_label("Detail Tool") .tooltip_description("Coming soon.") - .tooltip_shortcut(Key::KeyD.to_string()), + .tooltip_shortcut(action_shortcut_manual!(Key::KeyD)), ), ToolAvailability::ComingSoon( ToolEntry::new(ToolType::Relight, "RasterRelightTool") .tooltip_label("Relight Tool") .tooltip_description("Coming soon.") - .tooltip_shortcut(Key::KeyO.to_string()), + .tooltip_shortcut(action_shortcut_manual!(Key::KeyO)), ), ], ] @@ -518,6 +516,55 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis #[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct HintData(pub Vec); +impl HintData { + pub fn to_layout(&self) -> Layout { + let mut widgets = Vec::new(); + + for (index, hint_group) in self.0.iter().enumerate() { + if index > 0 { + widgets.push(Separator::new(SeparatorType::Section).widget_holder()); + } + for hint in &hint_group.0 { + if hint.plus { + widgets.push(TextLabel::new("+").bold(true).widget_holder()); + } + if hint.slash { + widgets.push(TextLabel::new("/").bold(true).widget_holder()); + } + + for shortcut in &hint.key_groups { + widgets.push(ShortcutLabel::new(Some(ActionShortcut::Shortcut(shortcut.clone()))).widget_holder()); + } + if let Some(mouse_movement) = &hint.mouse { + let mouse_movement = LabeledShortcut(vec![LabeledKeyOrMouseMotion::MouseMotion(mouse_movement.clone())]); + let shortcut = ActionShortcut::Shortcut(mouse_movement); + widgets.push(ShortcutLabel::new(Some(shortcut)).widget_holder()); + } + + if !hint.label.is_empty() { + widgets.push(TextLabel::new(hint.label.clone()).widget_holder()); + } + } + } + + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) + } + + pub fn send_layout(&self, responses: &mut VecDeque) { + responses.add(LayoutMessage::SendLayout { + layout: self.to_layout(), + layout_target: LayoutTarget::StatusBarHints, + }); + } + + pub fn clear_layout(responses: &mut VecDeque) { + responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), + layout_target: LayoutTarget::StatusBarHints, + }); + } +} + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct HintGroup(pub Vec); @@ -526,10 +573,10 @@ pub struct HintInfo { /// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy). /// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer). #[serde(rename = "keyGroups")] - pub key_groups: Vec, + pub key_groups: Vec, /// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint. #[serde(rename = "keyGroupsMac")] - pub key_groups_mac: Option>, + pub key_groups_mac: Option>, /// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs. /// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired. pub mouse: Option, diff --git a/frontend/src/components/floating-menus/MenuList.svelte b/frontend/src/components/floating-menus/MenuList.svelte index 1c6bada74..6c1f84717 100644 --- a/frontend/src/components/floating-menus/MenuList.svelte +++ b/frontend/src/components/floating-menus/MenuList.svelte @@ -12,8 +12,8 @@ import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte"; + import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; - import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte"; let self: FloatingMenu | undefined; let scroller: LayoutCol | undefined; @@ -447,8 +447,8 @@ - {#if entry.shortcutKeys?.keys.length} - + {#if entry.tooltipShortcut?.shortcut.length} + {/if} {#if entry.children?.length} @@ -499,7 +499,7 @@ margin: 4px 0; div { - background: var(--color-4-dimgray); + background: var(--color-3-darkgray); } } @@ -535,7 +535,7 @@ margin: 0 4px; } - .user-input-label { + .shortcut-label { margin-left: 12px; } diff --git a/frontend/src/components/floating-menus/Tooltip.svelte b/frontend/src/components/floating-menus/Tooltip.svelte index 520c25daf..5a9681c01 100644 --- a/frontend/src/components/floating-menus/Tooltip.svelte +++ b/frontend/src/components/floating-menus/Tooltip.svelte @@ -2,10 +2,12 @@ import { getContext } from "svelte"; import type { Editor } from "@graphite/editor"; + import type { LabeledShortcut } from "@graphite/messages"; import type { TooltipState } from "@graphite/state-providers/tooltip"; import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; + import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; const tooltip = getContext("tooltip"); @@ -15,7 +17,15 @@ $: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim()); $: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim()); - $: shortcut = filterTodo($tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim()); + $: shortcutJSON = $tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim(); + $: shortcut = ((shortcutJSON) => { + if (!shortcutJSON) return undefined; + try { + return JSON.parse(shortcutJSON) as LabeledShortcut; + } catch { + return undefined; + } + })(shortcutJSON); // TODO: Once all TODOs are replaced with real text, remove this function function filterTodo(text: string | undefined): string | undefined { @@ -24,8 +34,8 @@ } -
- {#if label || description} +{#if label || description} +
{#if label || shortcut} @@ -33,7 +43,7 @@ {label} {/if} {#if shortcut} - {shortcut} + {/if} {/if} @@ -41,8 +51,8 @@ {description} {/if} - {/if} -
+
+{/if} diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 351440012..58e09d796 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -588,10 +588,11 @@ data-visibility-button size={24} icon={node.visible ? "EyeVisible" : "EyeHidden"} + hoverIcon={node.visible ? "EyeHide" : "EyeShow"} action={() => { /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} - tooltipLabel={node.visible ? "Visible" : "Hidden"} + tooltipLabel={node.visible ? "Hide" : "Show"} /> diff --git a/frontend/src/components/widgets/WidgetLayout.svelte b/frontend/src/components/widgets/WidgetLayout.svelte index 63b75e274..afa849a11 100644 --- a/frontend/src/components/widgets/WidgetLayout.svelte +++ b/frontend/src/components/widgets/WidgetLayout.svelte @@ -18,7 +18,7 @@ {:else if isWidgetSection(layoutGroup)} {:else if isWidgetTable(layoutGroup)} - + {:else} Error: The widget layout that belongs here has an invalid layout group type {/if} diff --git a/frontend/src/components/widgets/WidgetSpan.svelte b/frontend/src/components/widgets/WidgetSpan.svelte index 2a206119f..bb024c9b3 100644 --- a/frontend/src/components/widgets/WidgetSpan.svelte +++ b/frontend/src/components/widgets/WidgetSpan.svelte @@ -27,6 +27,7 @@ import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte"; + import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; @@ -127,6 +128,11 @@ {#if iconLabel} {/if} + {@const shortcutLabel = narrowWidgetProps(component.props, "ShortcutLabel")} + {@const shortcutLabelShortcut = shortcutLabel?.shortcut ? { ...shortcutLabel, shortcut: shortcutLabel.shortcut } : undefined} + {#if shortcutLabel && shortcutLabelShortcut} + + {/if} {@const imageLabel = narrowWidgetProps(component.props, "ImageLabel")} {#if imageLabel} diff --git a/frontend/src/components/widgets/WidgetTable.svelte b/frontend/src/components/widgets/WidgetTable.svelte index 63b20e701..b9d5c84d0 100644 --- a/frontend/src/components/widgets/WidgetTable.svelte +++ b/frontend/src/components/widgets/WidgetTable.svelte @@ -5,15 +5,18 @@ export let widgetData: WidgetTableFromJsMessages; // eslint-disable-next-line @typescript-eslint/no-explicit-any - export let layoutTarget: any; // TODO: Give this a real type + export let layoutTarget: any; + export let unstyled = false; + + $: columns = widgetData.tableWidgets.length > 0 ? widgetData.tableWidgets[0].length : 0; - +
{#each widgetData.tableWidgets as row} {#each row as cell} - {/each} @@ -23,7 +26,7 @@
+
diff --git a/frontend/src/components/widgets/labels/TextLabel.svelte b/frontend/src/components/widgets/labels/TextLabel.svelte index b5e8ac6ed..4174ebf43 100644 --- a/frontend/src/components/widgets/labels/TextLabel.svelte +++ b/frontend/src/components/widgets/labels/TextLabel.svelte @@ -1,4 +1,6 @@ - -{#if displayKeyboardLockNotice} - -{:else} - - {#each keysWithLabelsGroups as keysWithLabels, groupIndex} - {#if groupIndex > 0} - - {/if} - {#each keyTextOrIconList(keysWithLabels) as keyInfo} -
- {#if keyInfo.icon} - - {:else if keyInfo.label !== undefined} - {keyInfo.label} - {/if} -
- {/each} - {/each} - {#if mouseMotion} -
- -
- {/if} - {#if $$slots.default} -
- -
- {/if} -
-{/if} - - diff --git a/frontend/src/components/window/status-bar/StatusBar.svelte b/frontend/src/components/window/status-bar/StatusBar.svelte index 82ef898a7..e92eb8f6e 100644 --- a/frontend/src/components/window/status-bar/StatusBar.svelte +++ b/frontend/src/components/window/status-bar/StatusBar.svelte @@ -2,45 +2,25 @@ import { getContext, onMount } from "svelte"; import type { Editor } from "@graphite/editor"; - import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@graphite/messages"; - import { operatingSystem } from "@graphite/utility-functions/platform"; + import { defaultWidgetLayout, patchWidgetLayout, UpdateStatusBarHintsLayout } from "@graphite/messages"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; - import Separator from "@graphite/components/widgets/labels/Separator.svelte"; - import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte"; + import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; const editor = getContext("editor"); - let hintData: HintData = []; - - function inputKeysForPlatform(hint: HintInfo): LayoutKeysGroup[] { - return operatingSystem() === "Mac" && hint.keyGroupsMac ? hint.keyGroupsMac : hint.keyGroups; - } + let statusBarHintsLayout = defaultWidgetLayout(); onMount(() => { - editor.subscriptions.subscribeJsMessage(UpdateInputHints, (data) => { - hintData = data.hintData; + editor.subscriptions.subscribeJsMessage(UpdateStatusBarHintsLayout, (updateStatusBarHintsLayout) => { + patchWidgetLayout(statusBarHintsLayout, updateStatusBarHintsLayout); + statusBarHintsLayout = statusBarHintsLayout; }); }); - - {#each hintData as hintGroup, index (hintGroup)} - {#if index !== 0} - - {/if} - {#each hintGroup as hint (hint)} - {#if hint.plus} - + - {/if} - {#if hint.slash} - / - {/if} - {hint.label} - {/each} - {/each} - +