Restyle and refactor shortcut labels to send hints bar and welcome screen layouts from Rust (#3447)

* Restyle UserInputLabel and refactor its usages to have all input its data sent from Rust

* Replace the welcome screen quick buttons with ones sent by backend

* Add the ShortcutLabel widget to the backend

* Replace hints bar with a backend-controlled layout; show mouse icons in place of mouse labels
This commit is contained in:
Keavon Chambers
2025-12-04 01:04:14 -08:00
committed by GitHub
parent 6ed42d06bb
commit 810ce40e9b
78 changed files with 981 additions and 997 deletions
@@ -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<ToolMessage, ToolMessageContext<'_>> 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;
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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<Message>) {
@@ -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);
}
@@ -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<Message>) {
@@ -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);
}
}
}
@@ -1187,5 +1187,5 @@ fn update_dynamic_hints(state: &ShapeToolFsmState, responses: &mut VecDeque<Mess
]),
ShapeToolFsmState::ModifyingGizmo => 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);
}
@@ -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<Message>) {
@@ -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<Message>) {
+64 -17
View File
@@ -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<ActionKeys>,
pub tooltip_shortcut: Option<ActionShortcut>,
}
#[derive(Debug)]
@@ -429,26 +427,26 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
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<HintGroup>);
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<Message>) {
responses.add(LayoutMessage::SendLayout {
layout: self.to_layout(),
layout_target: LayoutTarget::StatusBarHints,
});
}
pub fn clear_layout(responses: &mut VecDeque<Message>) {
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<HintInfo>);
@@ -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<LayoutKeysGroup>,
pub key_groups: Vec<LabeledShortcut>,
/// `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<Vec<LayoutKeysGroup>>,
pub key_groups_mac: Option<Vec<LabeledShortcut>>,
/// 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<MouseMotion>,