Replace text-only tooltips with custom richly styled tooltips (#3436)

* Replace the title attribute with custom FloatingMenu tooltips

* Separate tooltip labels and descriptions into two styled blocks

* Move keyboard shortcut tooltips to a separate section at the bottom

* Update shortcut key styling in tooltips and hints bar

* Fix .to_string()
This commit is contained in:
Keavon Chambers
2025-11-30 13:32:58 -08:00
committed by GitHub
parent 94e5c8fc05
commit e8ebcc2c21
94 changed files with 1323 additions and 580 deletions
@@ -42,17 +42,18 @@ impl PreferencesDialogMessageHandler {
let navigation_header = vec![TextLabel::new("Navigation").italic(true).widget_holder()];
let zoom_rate_tooltip = "Adjust how fast zooming occurs when using the scroll wheel or pinch gesture (relative to a default of 50)";
let zoom_rate_description = "Adjust how fast zooming occurs when using the scroll wheel or pinch gesture (relative to a default of 50).";
let zoom_rate_label = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("Zoom Rate").tooltip(zoom_rate_tooltip).widget_holder(),
TextLabel::new("Zoom Rate").tooltip_label("Zoom Rate").tooltip_description(zoom_rate_description).widget_holder(),
];
let zoom_rate = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(map_zoom_rate_to_display(preferences.viewport_zoom_wheel_rate)))
.tooltip(zoom_rate_tooltip)
.tooltip_label("Zoom Rate")
.tooltip_description(zoom_rate_description)
.mode_range()
.int()
.min(1.)
@@ -69,12 +70,13 @@ impl PreferencesDialogMessageHandler {
];
let checkbox_id = CheckboxId::new();
let zoom_with_scroll_tooltip = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads)";
let zoom_with_scroll_description = "Use the scroll wheel for zooming instead of vertically panning (not recommended for trackpads).";
let zoom_with_scroll = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.zoom_with_scroll)
.tooltip(zoom_with_scroll_tooltip)
.tooltip_label("Zoom with Scroll")
.tooltip_description(zoom_with_scroll_description)
.on_update(|checkbox_input: &CheckboxInput| {
PreferencesMessage::ModifyLayout {
zoom_with_scroll: checkbox_input.checked,
@@ -84,9 +86,10 @@ impl PreferencesDialogMessageHandler {
.for_label(checkbox_id)
.widget_holder(),
TextLabel::new("Zoom with Scroll")
.table_align(true)
.tooltip(zoom_with_scroll_tooltip)
.tooltip_label("Zoom with Scroll")
.tooltip_description(zoom_with_scroll_description)
.for_checkbox(checkbox_id)
.table_align(true)
.widget_holder(),
];
@@ -105,7 +108,8 @@ impl PreferencesDialogMessageHandler {
let selection_mode = RadioInput::new(vec![
RadioEntryData::new(SelectionMode::Touched.to_string())
.label(SelectionMode::Touched.to_string())
.tooltip(SelectionMode::Touched.tooltip_description())
.tooltip_label(SelectionMode::Touched.to_string())
.tooltip_description(SelectionMode::Touched.tooltip_description())
.on_update(move |_| {
PreferencesMessage::SelectionMode {
selection_mode: SelectionMode::Touched,
@@ -114,7 +118,8 @@ impl PreferencesDialogMessageHandler {
}),
RadioEntryData::new(SelectionMode::Enclosed.to_string())
.label(SelectionMode::Enclosed.to_string())
.tooltip(SelectionMode::Enclosed.tooltip_description())
.tooltip_label(SelectionMode::Enclosed.to_string())
.tooltip_description(SelectionMode::Enclosed.tooltip_description())
.on_update(move |_| {
PreferencesMessage::SelectionMode {
selection_mode: SelectionMode::Enclosed,
@@ -123,7 +128,8 @@ impl PreferencesDialogMessageHandler {
}),
RadioEntryData::new(SelectionMode::Directional.to_string())
.label(SelectionMode::Directional.to_string())
.tooltip(SelectionMode::Directional.tooltip_description())
.tooltip_label(SelectionMode::Directional.to_string())
.tooltip_description(SelectionMode::Directional.tooltip_description())
.on_update(move |_| {
PreferencesMessage::SelectionMode {
selection_mode: SelectionMode::Directional,
@@ -145,20 +151,25 @@ impl PreferencesDialogMessageHandler {
let experimental_header = vec![TextLabel::new("Experimental").italic(true).widget_holder()];
let node_graph_section_tooltip = "Appearance of the wires running between node connections in the graph";
let node_graph_section_description = "Appearance of the wires running between node connections in the graph.";
let node_graph_wires_label = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("Node Graph Wires").tooltip(node_graph_section_tooltip).widget_holder(),
TextLabel::new("Node Graph Wires")
.tooltip_label("Node Graph Wires")
.tooltip_description(node_graph_section_description)
.widget_holder(),
];
let graph_wire_style = RadioInput::new(vec![
RadioEntryData::new(GraphWireStyle::Direct.to_string())
.label(GraphWireStyle::Direct.to_string())
.tooltip(GraphWireStyle::Direct.tooltip_description())
.tooltip_label(GraphWireStyle::Direct.to_string())
.tooltip_description(GraphWireStyle::Direct.tooltip_description())
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::Direct }.into()),
RadioEntryData::new(GraphWireStyle::GridAligned.to_string())
.label(GraphWireStyle::GridAligned.to_string())
.tooltip(GraphWireStyle::GridAligned.tooltip_description())
.tooltip_label(GraphWireStyle::GridAligned.to_string())
.tooltip_description(GraphWireStyle::GridAligned.tooltip_description())
.on_update(move |_| PreferencesMessage::GraphWireStyle { style: GraphWireStyle::GridAligned }.into()),
])
.selected_index(Some(preferences.graph_wire_style as u32))
@@ -170,36 +181,47 @@ impl PreferencesDialogMessageHandler {
];
let checkbox_id = CheckboxId::new();
let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)";
let vello_description = "Use the experimental Vello renderer. (Your browser must support WebGPU).";
let use_vello = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu())
.tooltip(vello_tooltip)
.tooltip_label("Vello Renderer")
.tooltip_description(vello_description)
.disabled(!preferences.supports_wgpu())
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into())
.for_label(checkbox_id)
.widget_holder(),
TextLabel::new("Vello Renderer")
.table_align(true)
.tooltip(vello_tooltip)
.tooltip_label("Vello Renderer")
.tooltip_description(vello_description)
.disabled(!preferences.supports_wgpu())
.for_checkbox(checkbox_id)
.table_align(true)
.widget_holder(),
];
let checkbox_id = CheckboxId::new();
let vector_mesh_tooltip =
"Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle stroke joins and fills.";
let vector_mesh_description = "
Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\
Currently this does not properly handle stroke joins and fills.
"
.trim();
let vector_meshes = vec![
Separator::new(SeparatorType::Unrelated).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
CheckboxInput::new(preferences.vector_meshes)
.tooltip(vector_mesh_tooltip)
.tooltip_label("Vector Meshes")
.tooltip_description(vector_mesh_description)
.on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into())
.for_label(checkbox_id)
.widget_holder(),
TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).for_checkbox(checkbox_id).widget_holder(),
TextLabel::new("Vector Meshes")
.tooltip_label("Vector Meshes")
.tooltip_description(vector_mesh_description)
.for_checkbox(checkbox_id)
.table_align(true)
.widget_holder(),
];
Layout::WidgetLayout(WidgetLayout::new(vec![
@@ -49,16 +49,19 @@ impl DialogLayoutHolder for LicensesDialog {
impl LayoutHolder for LicensesDialog {
fn layout(&self) -> Layout {
let description = concat!(
"The Graphite logo and brand identity are copyright © [YEAR]\nGraphite Labs, LLC. See \"Graphite Logo\" for usage policy.",
"\n\n",
"The Graphite editor's icons and design assets are copyright\n© [YEAR] Graphite Labs, LLC. See \"Graphite Icons\" for details.",
"\n\n",
"Graphite code is copyright © [YEAR] Graphite contributors\nand is made available under the Apache 2.0 license. See\n\"Graphite License\" for details.",
"\n\n",
"Graphite is distributed with third-party open source code\ndependencies. See \"Other Licenses\" for details.",
)
.replace("[YEAR]", &self.localized_commit_year);
let year = &self.localized_commit_year;
let description = format!(
"
The Graphite logo and brand identity are copyright © {year}\nGraphite Labs, LLC. See \"Graphite Logo\" for usage policy.\n\
\n\
The Graphite editor's icons and design assets are copyright\n© {year} Graphite Labs, LLC. See \"Graphite Icons\" for details.\n\
\n\
Graphite code is copyright © {year} Graphite contributors\nand is made available under the Apache 2.0 license. See\n\"Graphite License\" for details.\n\
\n\
Graphite is distributed with third-party open source code\ndependencies. See \"Other Licenses\" for details.
"
);
let description = description.trim();
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {
@@ -410,7 +410,6 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(FakeKeyPlus); modifiers=[Accel], canonical, action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }),
entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }),
entry!(KeyDown(KeyF); modifiers=[Alt], action_dispatch=NavigationMessage::CanvasFlip),
entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel),
entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }),
entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }),
@@ -296,9 +296,9 @@ impl fmt::Display for Key {
KeyboardPlatformLayout::Standard => "Ctrl",
KeyboardPlatformLayout::Mac => "",
},
Self::MouseLeft => "LMB",
Self::MouseRight => "RMB",
Self::MouseMiddle => "MMB",
Self::MouseLeft => "Click",
Self::MouseRight => "R.Click",
Self::MouseMiddle => "M.Click",
Self::MouseBack => "Mouse Back",
Self::MouseForward => "Mouse Fwd",
@@ -321,7 +321,7 @@ impl From<Key> for LayoutKey {
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct LayoutKey {
pub key: Key,
key: Key,
label: String,
}
@@ -351,37 +351,73 @@ impl From<Vec<WidgetHolder>> for LayoutGroup {
}
impl LayoutGroup {
/// Applies a tooltip to all widgets in this row or column without a tooltip.
pub fn with_tooltip(self, tooltip: impl Into<String>) -> Self {
/// Applies a tooltip label to all widgets in this row or column without a tooltip.
pub fn with_tooltip_label(self, label: impl Into<String>) -> Self {
let (is_col, mut widgets) = match self {
LayoutGroup::Column { widgets } => (true, widgets),
LayoutGroup::Row { widgets } => (false, widgets),
_ => unimplemented!(),
};
let tooltip = tooltip.into();
let label = label.into();
for widget in &mut widgets {
let val = match &mut widget.widget {
Widget::CheckboxInput(x) => &mut x.tooltip,
Widget::ColorInput(x) => &mut x.tooltip,
Widget::CurveInput(x) => &mut x.tooltip,
Widget::DropdownInput(x) => &mut x.tooltip,
Widget::FontInput(x) => &mut x.tooltip,
Widget::IconButton(x) => &mut x.tooltip,
Widget::IconLabel(x) => &mut x.tooltip,
Widget::ImageButton(x) => &mut x.tooltip,
Widget::ImageLabel(x) => &mut x.tooltip,
Widget::NumberInput(x) => &mut x.tooltip,
Widget::ParameterExposeButton(x) => &mut x.tooltip,
Widget::PopoverButton(x) => &mut x.tooltip,
Widget::TextAreaInput(x) => &mut x.tooltip,
Widget::TextButton(x) => &mut x.tooltip,
Widget::TextInput(x) => &mut x.tooltip,
Widget::TextLabel(x) => &mut x.tooltip,
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip,
Widget::CheckboxInput(x) => &mut x.tooltip_label,
Widget::ColorInput(x) => &mut x.tooltip_label,
Widget::CurveInput(x) => &mut x.tooltip_label,
Widget::DropdownInput(x) => &mut x.tooltip_label,
Widget::FontInput(x) => &mut x.tooltip_label,
Widget::IconButton(x) => &mut x.tooltip_label,
Widget::IconLabel(x) => &mut x.tooltip_label,
Widget::ImageButton(x) => &mut x.tooltip_label,
Widget::ImageLabel(x) => &mut x.tooltip_label,
Widget::NumberInput(x) => &mut x.tooltip_label,
Widget::ParameterExposeButton(x) => &mut x.tooltip_label,
Widget::PopoverButton(x) => &mut x.tooltip_label,
Widget::TextAreaInput(x) => &mut x.tooltip_label,
Widget::TextButton(x) => &mut x.tooltip_label,
Widget::TextInput(x) => &mut x.tooltip_label,
Widget::TextLabel(x) => &mut x.tooltip_label,
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_label,
Widget::InvisibleStandinInput(_) | Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
};
if val.is_empty() {
val.clone_from(&tooltip);
val.clone_from(&label);
}
}
if is_col { Self::Column { widgets } } else { Self::Row { widgets } }
}
/// Applies a tooltip description to all widgets in this row or column without a tooltip.
pub fn with_tooltip_description(self, description: impl Into<String>) -> Self {
let (is_col, mut widgets) = match self {
LayoutGroup::Column { widgets } => (true, widgets),
LayoutGroup::Row { widgets } => (false, widgets),
_ => unimplemented!(),
};
let description = description.into();
for widget in &mut widgets {
let val = match &mut widget.widget {
Widget::CheckboxInput(x) => &mut x.tooltip_description,
Widget::ColorInput(x) => &mut x.tooltip_description,
Widget::CurveInput(x) => &mut x.tooltip_description,
Widget::DropdownInput(x) => &mut x.tooltip_description,
Widget::FontInput(x) => &mut x.tooltip_description,
Widget::IconButton(x) => &mut x.tooltip_description,
Widget::IconLabel(x) => &mut x.tooltip_description,
Widget::ImageButton(x) => &mut x.tooltip_description,
Widget::ImageLabel(x) => &mut x.tooltip_description,
Widget::NumberInput(x) => &mut x.tooltip_description,
Widget::ParameterExposeButton(x) => &mut x.tooltip_description,
Widget::PopoverButton(x) => &mut x.tooltip_description,
Widget::TextAreaInput(x) => &mut x.tooltip_description,
Widget::TextButton(x) => &mut x.tooltip_description,
Widget::TextInput(x) => &mut x.tooltip_description,
Widget::TextLabel(x) => &mut x.tooltip_description,
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip_description,
Widget::InvisibleStandinInput(_) | Widget::ReferencePointInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsInput(_) | Widget::NodeCatalog(_) => continue,
};
if val.is_empty() {
val.clone_from(&description);
}
}
if is_col { Self::Column { widgets } } else { Self::Row { widgets } }
@@ -513,8 +549,9 @@ impl WidgetHolder {
&& button1.style == button2.style
&& button1.menu_direction == button2.menu_direction
&& button1.icon == button2.icon
&& button1.tooltip == button2.tooltip
&& button1.tooltip_shortcut == button2.tooltip_shortcut
&& button1.tooltip_label == button2.tooltip_label
&& button1.tooltip_description == button2.tooltip_description
&& button1.shortcut_keys == button2.shortcut_keys
&& button1.popover_min_width == button2.popover_min_width
{
let mut new_widget_path = widget_path.to_vec();
@@ -617,36 +654,29 @@ 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<KeysGroup>) {
// 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 = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
let shortcut_text = tooltip_shortcut.to_keys(action_input_mapping);
let apply_shortcut_to_tooltip = |shortcut_keys: &mut ActionKeys, tooltip_shortcut: &mut String| {
let shortcut_text = shortcut_keys.to_keys(action_input_mapping);
if let ActionKeys::Keys(_keys) = tooltip_shortcut
&& !shortcut_text.is_empty()
{
if !tooltip.is_empty() {
tooltip.push(' ');
}
tooltip.push('(');
tooltip.push_str(&shortcut_text);
tooltip.push(')');
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
let convert_tooltip = |widget_holder: &mut WidgetHolder| {
// Handle all the widgets that have tooltips
let mut tooltip_shortcut = match &mut widget_holder.widget {
Widget::BreadcrumbTrailButtons(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::DropdownInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::FontInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ParameterExposeButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::PopoverButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ImageButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
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)),
Widget::IconLabel(_)
| Widget::ImageLabel(_)
| Widget::CurveInput(_)
@@ -660,20 +690,20 @@ impl DiffUpdate {
| Widget::TextLabel(_)
| Widget::WorkingColorsInput(_) => None,
};
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
if let Some((tooltip_shortcut, Some(shortcut_keys))) = &mut shortcut_keys {
apply_shortcut_to_tooltip(shortcut_keys, tooltip_shortcut);
}
// 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,
tooltip_shortcut: Some(tooltip_shortcut),
tooltip_shortcut,
shortcut_keys: Some(shortcut_keys),
..
} = radio_entry_data
{
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
apply_shortcut_to_tooltip(shortcut_keys, tooltip_shortcut);
}
}
}
@@ -22,10 +22,17 @@ pub struct IconButton {
pub active: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -49,10 +56,17 @@ pub struct PopoverButton {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
#[serde(rename = "popoverLayout")]
pub popover_layout: SubLayout,
@@ -83,10 +97,17 @@ pub struct ParameterExposeButton {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -120,10 +141,17 @@ pub struct TextButton {
pub narrow: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
#[serde(rename = "menuListChildren")]
pub menu_list_children: MenuListEntrySections,
@@ -148,10 +176,17 @@ pub struct ImageButton {
pub height: Option<String>,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -186,10 +221,17 @@ pub struct ColorInput {
#[serde(rename = "menuDirection")]
pub menu_direction: Option<MenuDirection>,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -209,10 +251,17 @@ pub struct BreadcrumbTrailButtons {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -16,14 +16,21 @@ pub struct CheckboxInput {
pub icon: String,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(rename = "forLabel")]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub for_label: CheckboxId,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -38,11 +45,13 @@ pub struct CheckboxInput {
impl Default for CheckboxInput {
fn default() -> Self {
Self {
checked: false,
disabled: false,
icon: "Checkmark".into(),
tooltip: Default::default(),
checked: Default::default(),
disabled: Default::default(),
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(),
@@ -90,10 +99,17 @@ pub struct DropdownInput {
pub narrow: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Styling
#[serde(rename = "minWidth")]
@@ -154,10 +170,17 @@ pub struct FontInput {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -190,10 +213,17 @@ pub struct NumberInput {
// Label
pub label: String,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Disabled
pub disabled: bool,
@@ -359,10 +389,17 @@ pub struct RadioEntryData {
pub icon: String,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
pub shortcut_keys: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
@@ -394,7 +431,14 @@ pub struct TextAreaInput {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
// Callbacks
#[serde(skip)]
@@ -418,7 +462,14 @@ pub struct TextInput {
pub narrow: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
pub centered: bool,
@@ -446,7 +497,14 @@ pub struct CurveInput {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
// Callbacks
#[serde(skip)]
@@ -466,7 +524,14 @@ pub struct ReferencePointInput {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
// Callbacks
#[serde(skip)]
@@ -9,7 +9,14 @@ pub struct IconLabel {
pub disabled: bool,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
@@ -60,7 +67,14 @@ pub struct TextLabel {
#[serde(rename = "minWidth")]
pub min_width: String,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
#[serde(rename = "forCheckbox")]
#[derivative(PartialEq = "ignore")]
@@ -81,7 +95,14 @@ pub struct ImageLabel {
pub height: Option<String>,
pub tooltip: String,
#[serde(rename = "tooltipLabel")]
pub tooltip_label: String,
#[serde(rename = "tooltipDescription")]
pub tooltip_description: String,
#[serde(rename = "tooltipShortcut")]
pub tooltip_shortcut: String,
}
// TODO: Add UserInputLabel
@@ -97,13 +97,13 @@ impl DataPanelMessageHandler {
widgets.extend([
if is_layer {
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder()
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_holder()
} else {
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder()
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_holder()
},
Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(network_interface.display_name(&node_id, &[]))
.tooltip(if is_layer { "Name of the selected layer" } else { "Name of the selected node" })
.tooltip_description(if is_layer { "Name of the selected layer." } else { "Name of the selected node." })
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id,
@@ -2211,21 +2211,21 @@ impl DocumentMessageHandler {
let mut widgets = vec![
IconButton::new("PlaybackToStart", 24)
.tooltip("Restart Animation")
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::RestartAnimation))
.tooltip_label("Restart Animation")
.shortcut_keys(action_keys!(AnimationMessageDiscriminant::RestartAnimation))
.on_update(|_| AnimationMessage::RestartAnimation.into())
.disabled(time == Duration::ZERO)
.widget_holder(),
IconButton::new(if animation_is_playing { "PlaybackPause" } else { "PlaybackPlay" }, 24)
.tooltip(if animation_is_playing { "Pause Animation" } else { "Play Animation" })
.tooltip_shortcut(action_keys!(AnimationMessageDiscriminant::ToggleLivePreview))
.tooltip_label(if animation_is_playing { "Pause Animation" } else { "Play Animation" })
.shortcut_keys(action_keys!(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("Overlays")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))
.tooltip_label("Overlays")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleOverlaysVisibility))
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
@@ -2473,8 +2473,8 @@ impl DocumentMessageHandler {
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(snapping_state.snapping_enabled)
.icon("Snapping")
.tooltip("Snapping")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSnapping))
.tooltip_label("Snapping")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSnapping))
.on_update(move |optional_input: &CheckboxInput| {
DocumentMessage::SetSnapping {
closure: Some(|snapping_state| &mut snapping_state.snapping_enabled),
@@ -2494,7 +2494,7 @@ impl DocumentMessageHandler {
},
]
.into_iter()
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
@@ -2506,17 +2506,18 @@ impl DocumentMessageHandler {
}
.into()
})
.tooltip(tooltip)
.tooltip_label(name)
.tooltip_description(description)
.for_label(checkbox_id)
.widget_holder(),
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_holder(),
]
},
}))
.chain([LayoutGroup::Row {
widgets: vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_holder()],
}])
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, tooltip)| LayoutGroup::Row {
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
widgets: {
let checkbox_id = CheckboxId::new();
vec![
@@ -2528,10 +2529,11 @@ impl DocumentMessageHandler {
}
.into()
})
.tooltip(tooltip)
.tooltip_label(name)
.tooltip_description(description)
.for_label(checkbox_id)
.widget_holder(),
TextLabel::new(name).tooltip(tooltip).for_checkbox(checkbox_id).widget_holder(),
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_holder(),
]
},
}))
@@ -2541,8 +2543,8 @@ impl DocumentMessageHandler {
Separator::new(SeparatorType::Related).widget_holder(),
CheckboxInput::new(self.snapping_state.grid_snapping)
.icon("Grid")
.tooltip("Grid")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
.tooltip_label("Grid")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())
.widget_holder(),
PopoverButton::new()
@@ -2553,19 +2555,19 @@ impl DocumentMessageHandler {
RadioInput::new(vec![
RadioEntryData::new("Normal")
.icon("RenderModeNormal")
.tooltip("Render Mode: Normal")
.tooltip_label("Render Mode: Normal")
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Normal }.into()),
RadioEntryData::new("Outline")
.icon("RenderModeOutline")
.tooltip("Render Mode: Outline")
.tooltip_label("Render Mode: Outline")
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()),
// RadioEntryData::new("PixelPreview")
// .icon("RenderModePixels")
// .tooltip("Render Mode: Pixel Preview")
// .tooltip_label("Render Mode: Pixel Preview")
// .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(320) }.into()),
// RadioEntryData::new("SvgPreview")
// .icon("RenderModeSvg")
// .tooltip("Render Mode: SVG Preview")
// .tooltip_label("Render Mode: SVG Preview")
// .on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1845) }.into()),
])
.selected_index(Some(self.render_mode as u32))
@@ -2607,7 +2609,7 @@ impl DocumentMessageHandler {
}
.into()
})
.tooltip("Canvas Tilt")
.tooltip_label("Canvas Tilt")
.on_update(|number_input: &NumberInput| {
NavigationMessage::CanvasTiltSet {
angle_radians: number_input.value.unwrap().to_radians(),
@@ -2623,8 +2625,8 @@ impl DocumentMessageHandler {
TextButton::new("Node Graph")
.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(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.tooltip_label(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
]);
@@ -2723,7 +2725,7 @@ impl DocumentMessageHandler {
.disabled(disabled)
.draw_icon(false)
.max_width(100)
.tooltip("Blend Mode")
.tooltip_label("Blend Mode")
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(opacity)
@@ -2745,7 +2747,7 @@ impl DocumentMessageHandler {
})
.on_commit(|_| DocumentMessage::AddTransaction.into())
.max_width(100)
.tooltip("Opacity")
.tooltip_label("Opacity")
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(fill)
@@ -2767,7 +2769,7 @@ impl DocumentMessageHandler {
})
.on_commit(|_| DocumentMessage::AddTransaction.into())
.max_width(100)
.tooltip("Fill")
.tooltip_label("Fill")
.widget_holder(),
];
let layers_panel_control_bar_left = WidgetLayout::new(vec![LayoutGroup::Row { widgets }]);
@@ -2775,15 +2777,15 @@ impl DocumentMessageHandler {
let widgets = vec![
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedLocked))
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.shortcut_keys(action_keys!(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(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_holder(),
@@ -2816,7 +2818,7 @@ impl DocumentMessageHandler {
PopoverButton::new()
.icon(Some("Node".to_string()))
.menu_direction(Some(MenuDirection::Top))
.tooltip("Add an operation to the end of this layer's chain of nodes")
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
.disabled(!has_selection || has_multiple_selection)
.popover_layout({
// Showing only compatible types for the layer based on the output type of the node upstream from its horizontal input
@@ -2847,8 +2849,8 @@ impl DocumentMessageHandler {
.widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
IconButton::new("Folder", 24)
.tooltip("Group Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.tooltip_label("Group Selected")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.on_update(|_| {
let group_folder_type = GroupFolderType::Layer;
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
@@ -2856,13 +2858,13 @@ impl DocumentMessageHandler {
.disabled(!has_selection)
.widget_holder(),
IconButton::new("NewLayer", 24)
.tooltip("New Layer")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.tooltip_label("New Layer")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
.widget_holder(),
IconButton::new("Trash", 24)
.tooltip("Delete Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.tooltip_label("Delete Selected")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
.disabled(!has_selection)
.widget_holder(),
@@ -3145,18 +3147,18 @@ impl<'a> ClickXRayIter<'a> {
pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHandler, node_graph: bool) -> Vec<WidgetHolder> {
let mut list = vec![
IconButton::new("ZoomIn", 24)
.tooltip("Zoom In")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease))
.tooltip_label("Zoom In")
.shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease))
.on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into())
.widget_holder(),
IconButton::new("ZoomOut", 24)
.tooltip("Zoom Out")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease))
.tooltip_label("Zoom Out")
.shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease))
.on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into())
.widget_holder(),
IconButton::new("ZoomReset", 24)
.tooltip("Reset Tilt and Zoom to 100%")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
.tooltip_label("Reset Tilt and Zoom to 100%")
.shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
.on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into())
.disabled(ptz.tilt().abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4)
.widget_holder(),
@@ -3164,8 +3166,9 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand
if ptz.flip && !node_graph {
list.push(
IconButton::new("Reverse", 24)
.tooltip("Flip the canvas back to its standard orientation")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasFlip))
.tooltip_label("Unflip Canvas")
.tooltip_description("Flip the canvas back to its standard orientation.")
.shortcut_keys(action_keys!(NavigationMessageDiscriminant::CanvasFlip))
.on_update(|_| NavigationMessage::CanvasFlip.into())
.widget_holder(),
);
@@ -3176,7 +3179,7 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand
.unit("%")
.min(0.000001)
.max(1000000.)
.tooltip(if node_graph { "Node Graph Zoom" } else { "Canvas Zoom" })
.tooltip_label(if node_graph { "Node Graph Zoom" } else { "Canvas Zoom" })
.on_update(|number_input: &NumberInput| {
NavigationMessage::CanvasZoomSet {
zoom_factor: number_input.value.unwrap() / 100.,
@@ -137,7 +137,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("A default node network you can use to create your own custom nodes."),
description: Cow::Borrowed("An empty node network you can use to create your own custom nodes."),
properties: None,
},
DocumentNodeDefinition {
@@ -156,7 +156,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
description: Cow::Borrowed(
"Improves rendering performance if used in rare circumstances where automatic caching is not yet advanced enough to handle the situation.
Stores the last evaluated data that flowed through this node, and immediately returns that data on subsequent renders if the context has not changed.",
),
properties: None,
},
DocumentNodeDefinition {
@@ -1153,7 +1156,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("Generates different noise patterns."),
description: Cow::Borrowed("Generates customizable procedural noise patterns."),
properties: None,
},
DocumentNodeDefinition {
@@ -1335,7 +1338,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
description: Cow::Borrowed(
"Decomposes the X and Y components of a vec2.\n\nThe inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.",
"Decomposes the X and Y components of a vec2.\n\
\n\
The inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.",
),
properties: None,
},
@@ -2008,10 +2013,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}),
input_metadata: vec![
("Content", "The shape to be resampled and converted into a polyline.").into(),
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
("Spacing", node_properties::SAMPLE_POLYLINE_DESCRIPTION_SPACING).into(),
InputMetadata::with_name_description_override(
"Separation",
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
node_properties::SAMPLE_POLYLINE_DESCRIPTION_SEPARATION,
WidgetOverride::Number(NumberInputSettings {
min: Some(0.),
unit: Some(" px".to_string()),
@@ -2020,7 +2025,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
),
InputMetadata::with_name_description_override(
"Quantity",
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
node_properties::SAMPLE_POLYLINE_DESCRIPTION_QUANTITY,
WidgetOverride::Number(NumberInputSettings {
min: Some(2.),
is_integer: true,
@@ -2029,7 +2034,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
),
InputMetadata::with_name_description_override(
"Start Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
node_properties::SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET,
WidgetOverride::Number(NumberInputSettings {
min: Some(0.),
unit: Some(" px".to_string()),
@@ -2038,14 +2043,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
),
InputMetadata::with_name_description_override(
"Stop Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
node_properties::SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET,
WidgetOverride::Number(NumberInputSettings {
min: Some(0.),
unit: Some(" px".to_string()),
..Default::default()
}),
),
("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING).into(),
("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING).into(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
@@ -1,6 +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::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
@@ -2105,7 +2106,9 @@ impl NodeGraphMessageHandler {
let mut widgets = vec![
PopoverButton::new()
.icon(Some("Node".to_string()))
.tooltip("New Node (Right Click)")
.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())
.popover_layout({
// Showing only compatible types
let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) {
@@ -2150,8 +2153,8 @@ impl NodeGraphMessageHandler {
Separator::new(SeparatorType::Unrelated).widget_holder(),
//
IconButton::new("Folder", 24)
.tooltip("Group Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.tooltip_label("Group Selected")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::GroupSelectedLayers))
.on_update(|_| {
let group_folder_type = GroupFolderType::Layer;
DocumentMessage::GroupSelectedLayers { group_folder_type }.into()
@@ -2159,13 +2162,13 @@ impl NodeGraphMessageHandler {
.disabled(!has_selection)
.widget_holder(),
IconButton::new("NewLayer", 24)
.tooltip("New Layer")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.tooltip_label("New Layer")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
.widget_holder(),
IconButton::new("Trash", 24)
.tooltip("Delete Selected")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.tooltip_label("Delete Selected")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers))
.on_update(|_| DocumentMessage::DeleteSelectedLayers.into())
.disabled(!has_selection)
.widget_holder(),
@@ -2174,15 +2177,15 @@ impl NodeGraphMessageHandler {
//
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.shortcut_keys(action_keys!(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(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.shortcut_keys(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_holder(),
@@ -2208,7 +2211,7 @@ impl NodeGraphMessageHandler {
if let Some(node_id) = previewing {
let button = TextButton::new("End Preview")
.icon(Some("FrameAll".to_string()))
.tooltip("Restore preview to the graph output")
.tooltip_description("Restore preview to the graph output.")
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_holder();
widgets.extend([Separator::new(SeparatorType::Unrelated).widget_holder(), button]);
@@ -2220,7 +2223,9 @@ impl NodeGraphMessageHandler {
if selection_is_not_already_the_output && no_other_selections {
let button = TextButton::new("Preview")
.icon(Some("FrameAll".to_string()))
.tooltip("Preview selected node/layer (Shortcut: Alt-click node/layer)")
.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())
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
.widget_holder();
widgets.extend([Separator::new(SeparatorType::Unrelated).widget_holder(), button]);
@@ -2262,7 +2267,7 @@ impl NodeGraphMessageHandler {
.percentage()
.display_decimal_places(0)
.label("Fade Artwork")
.tooltip("Opacity of the graph background that covers the artwork")
.tooltip_description("Opacity of the graph background that covers the artwork.")
.on_update(move |number_input: &NumberInput| {
DocumentMessage::SetGraphFadeArtwork {
percentage: number_input.value.unwrap_or(graph_fade_artwork_percentage),
@@ -2278,8 +2283,8 @@ impl NodeGraphMessageHandler {
TextButton::new("Node Graph")
.icon(Some("GraphViewOpen".into()))
.hover_icon(Some("GraphViewClosed".into()))
.tooltip("Hide Node Graph")
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.tooltip_label("Hide Node Graph")
.shortcut_keys(action_keys!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
.widget_holder(),
]);
@@ -2329,10 +2334,10 @@ impl NodeGraphMessageHandler {
properties.push(LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorType::Related).widget_holder(),
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder(),
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
.tooltip("Name of the selected node")
.tooltip_description("Name of the selected node.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id,
@@ -2357,10 +2362,10 @@ impl NodeGraphMessageHandler {
let mut properties = vec![LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorType::Related).widget_holder(),
IconLabel::new("File").tooltip("Name of the current document").widget_holder(),
IconLabel::new("File").tooltip_description("Name of the current document.").widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(context.document_name)
.tooltip("Name of the current document")
.tooltip_description("Name of the current document.")
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
@@ -2404,10 +2409,10 @@ impl NodeGraphMessageHandler {
let mut layer_properties = vec![LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorType::Related).widget_holder(),
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder(),
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
TextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))
.tooltip("Name of the selected layer")
.tooltip_description("Name of the selected layer.")
.on_update(move |text_input| {
NodeGraphMessage::SetDisplayName {
node_id: layer,
@@ -2420,7 +2425,7 @@ impl NodeGraphMessageHandler {
Separator::new(SeparatorType::Related).widget_holder(),
PopoverButton::new()
.icon(Some("Node".to_string()))
.tooltip("Add an operation to the end of this layer's chain of nodes")
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
.popover_layout({
let compatible_type = context
.network_interface
@@ -2680,7 +2685,6 @@ impl NodeGraphMessageHandler {
let data = LayerPanelEntry {
id: node_id,
alias: network_interface.display_name(&node_id, &[]),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
in_selected_network: selection_network_path.is_empty(),
children_allowed,
children_present: layer.has_children(network_interface.document_metadata()),
@@ -50,10 +50,10 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
ParameterExposeButton::new()
.exposed(exposed)
.data_type(data_type)
.tooltip(if exposed {
"Stop exposing this parameter as a node input in the graph"
.tooltip_description(if exposed {
"Stop exposing this parameter as a node input in the graph."
} else {
"Expose this parameter as a node input in the graph"
"Expose this parameter as a node input in the graph."
})
.on_update(move |_parameter| Message::Batched {
messages: Box::new([NodeGraphMessage::ExposeInput {
@@ -97,12 +97,11 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
};
let description = if description != "TODO" { description } else { String::new() };
let mut widgets = Vec::with_capacity(6);
if exposable {
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
}
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
widgets.push(TextLabel::new(name).tooltip_description(description).widget_holder());
if blank_assist {
add_blank_assist(&mut widgets);
}
@@ -232,16 +231,16 @@ pub(crate) fn property_from_type(
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("-")
.tooltip(format!(
"This data can only be supplied through the node graph because no widget exists for its type:\n\
{}",
// TODO: Avoid needing to remove spaces here by fixing how `alias` is generated
.tooltip_label(format!(
"Data Type: {}",
concrete_type
.alias
.as_deref()
// TODO: Avoid needing to remove spaces here by fixing how `alias` is generated
.map(|s| s.to_string().replace(" ", ""))
.unwrap_or_else(|| graphene_std::format_type(concrete_type.name.as_ref()))
.unwrap_or_else(|| graphene_std::format_type(concrete_type.name.as_ref())),
))
.tooltip_description("This data can only be supplied through the node graph because no widget exists for its type.")
.widget_holder(),
]);
return Err(vec![widgets.into()]);
@@ -961,7 +960,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
.widget_holder(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip("Formula used for blending")
LayoutGroup::Row { widgets }.with_tooltip_description("Formula used for blending.")
}
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
@@ -1400,12 +1399,12 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
widgets
}
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_QUANTITY: &str = "Number of points to place along the path.";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
pub(crate) const SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::sample_polyline::*;
@@ -1434,15 +1433,15 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
);
vec![
spacing.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SPACING),
spacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING),
match current_spacing {
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SEPARATION),
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_QUANTITY),
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),
_ => LayoutGroup::Row { widgets: vec![] },
},
LayoutGroup::Row { widgets: start_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_START_OFFSET),
LayoutGroup::Row { widgets: stop_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET),
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING),
LayoutGroup::Row { widgets: start_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),
LayoutGroup::Row { widgets: stop_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),
]
}
@@ -1776,7 +1775,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
Fill::Solid(_) | Fill::None => add_blank_assist(&mut row),
Fill::Gradient(gradient) => {
let reverse_button = IconButton::new("Reverse", 24)
.tooltip("Reverse the gradient color stops")
.tooltip_description("Reverse the gradient color stops.")
.on_update(update_value(
{
let gradient = gradient.clone();
@@ -1826,7 +1825,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
(gradient.start.x + gradient.start.y) < (gradient.end.x + gradient.end.y)
};
let reverse_radial_gradient_button = IconButton::new(if orientation { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" }, 24)
.tooltip("Reverse which end the gradient radiates from")
.tooltip_description("Reverse which end the gradient radiates from.")
.on_update(update_value(
{
let gradient = gradient.clone();
@@ -2027,9 +2026,9 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
vec![
LayoutGroup::Row { widgets: expression }.with_tooltip(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2""#),
LayoutGroup::Row { widgets: operand_b }.with_tooltip(r#"The value of "B" when calculating the expression"#),
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected"#),
LayoutGroup::Row { widgets: expression }.with_tooltip_description(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"."#),
LayoutGroup::Row { widgets: operand_b }.with_tooltip_description(r#"The value of "B" when calculating the expression."#),
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip_description(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected."#),
]
}
@@ -2149,13 +2148,12 @@ pub mod choice {
.map(|(item, var_meta)| {
let updater = updater_factory();
let committer = committer_factory();
let entry = RadioEntryData::new(var_meta.name).on_update(move |_| updater(item)).on_commit(committer);
match (var_meta.icon, var_meta.docstring) {
(None, None) => entry.label(var_meta.label),
(None, Some(doc)) => entry.label(var_meta.label).tooltip(doc),
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label),
(Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)),
}
let entry = RadioEntryData::new(var_meta.name)
.on_update(move |_| updater(item))
.on_commit(committer)
.tooltip_label(var_meta.label)
.tooltip_description(var_meta.description.unwrap_or_default());
if let Some(icon) = var_meta.icon { entry.icon(icon) } else { entry.label(var_meta.label) }
})
.collect();
RadioInput::new(items).selected_index(Some(current.as_u32())).widget_holder()
@@ -2229,7 +2227,7 @@ pub mod choice {
let mut row = LayoutGroup::Row { widgets };
if let Some(desc) = self.widget_factory.description() {
row = row.with_tooltip(desc);
row = row.with_tooltip_label(desc);
}
row
}
@@ -54,7 +54,7 @@ pub struct FrontendGraphInput {
#[serde(rename = "validTypes")]
pub valid_types: Vec<String>,
#[serde(rename = "connectedTo")]
/// Either "nothing", "import index {index}", or "{node name} output {output_index}".
/// Either "nothing", "import #{index}", or "{node name} #{output_index}".
pub connected_to: String,
}
@@ -276,14 +276,14 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
color_widgets.extend([
CheckboxInput::new(grid.dot_display)
.icon("GridDotted")
.tooltip("Display as dotted grid")
.tooltip_label("Display as Dotted Grid")
.on_update(update_display(grid, |grid| Some(&mut grid.dot_display)))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
]);
color_widgets.push(
ColorInput::new(FillChoice::Solid(grid.grid_color.to_gamma_srgb()))
.tooltip("Grid display color")
.tooltip_label("Grid Display Color")
.allow_none(false)
.on_update(update_color(grid, |grid| Some(&mut grid.grid_color)))
.widget_holder(),
@@ -386,55 +386,55 @@ pub const SNAP_FUNCTIONS_FOR_BOUNDING_BOXES: [(&str, GetSnapState, &str); 5] = [
(
"Align with Edges",
(|snapping_state| &mut snapping_state.bounding_box.align_with_edges) as GetSnapState,
"Snaps to horizontal/vertical alignment with the edges of any layer's bounding box",
"Snaps to horizontal/vertical alignment with the edges of any layer's bounding box.",
),
(
"Corner Points",
(|snapping_state| &mut snapping_state.bounding_box.corner_point) as GetSnapState,
"Snaps to the four corners of any layer's bounding box",
"Snaps to the four corners of any layer's bounding box.",
),
(
"Center Points",
(|snapping_state| &mut snapping_state.bounding_box.center_point) as GetSnapState,
"Snaps to the center point of any layer's bounding box",
"Snaps to the center point of any layer's bounding box.",
),
(
"Edge Midpoints",
(|snapping_state| &mut snapping_state.bounding_box.edge_midpoint) as GetSnapState,
"Snaps to any of the four points at the middle of the edges of any layer's bounding box",
"Snaps to any of the four points at the middle of the edges of any layer's bounding box.",
),
(
"Distribute Evenly",
(|snapping_state| &mut snapping_state.bounding_box.distribute_evenly) as GetSnapState,
"Snaps to a consistent distance offset established by the bounding boxes of nearby layers",
"Snaps to a consistent distance offset established by the bounding boxes of nearby layers.",
),
];
pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
(
"Align with Anchor Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.align_with_anchor_point) as GetSnapState,
"Snaps to horizontal/vertical alignment with the anchor points of any vector path",
"Snaps to horizontal/vertical alignment with the anchor points of any vector path.",
),
(
"Anchor Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.anchor_point) as GetSnapState,
"Snaps to the anchor point of any vector path",
"Snaps to the anchor point of any vector path.",
),
(
// TODO: Extend to the midpoints of curved segments and rename to "Segment Midpoint"
"Line Midpoints",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.line_midpoint) as GetSnapState,
"Snaps to the point at the middle of any straight line segment of a vector path",
"Snaps to the point at the middle of any straight line segment of a vector path.",
),
(
"Path Intersection Points",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.path_intersection_point) as GetSnapState,
"Snaps to any points where vector paths intersect",
"Snaps to any points where vector paths intersect.",
),
(
"Along Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.along_path) as GetSnapState,
"Snaps along the length of any vector path",
"Snaps along the length of any vector path.",
),
(
// TODO: This works correctly for line segments, but not curved segments.
@@ -442,7 +442,7 @@ pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
"Normal to Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.normal_to_path) as GetSnapState,
// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled
"Snaps a line to a point perpendicular to a vector path\n(due to a bug, 'Intersections of Paths' must be enabled)",
"Snaps a line to a point perpendicular to a vector path.\n(Due to a bug, 'Intersections of Paths' must be enabled.)",
),
(
// TODO: This works correctly for line segments, but not curved segments.
@@ -450,7 +450,7 @@ pub const SNAP_FUNCTIONS_FOR_PATHS: [(&str, GetSnapState, &str); 7] = [
"Tangent to Paths",
(|snapping_state: &mut SnappingState| &mut snapping_state.path.tangent_to_path) as GetSnapState,
// TODO: Fix the bug/limitation that requires 'Intersections of Paths' to be enabled
"Snaps a line to a point tangent to a vector path\n(due to a bug, 'Intersections of Paths' must be enabled)",
"Snaps a line to a point tangent to a vector path.\n(Due to a bug, 'Intersections of Paths' must be enabled.)",
),
];
@@ -631,15 +631,12 @@ impl NodeNetworkInterface {
.upstream_output_connector(input_connector, network_path)
.map(|output_connector| match output_connector {
OutputConnector::Node { node_id, output_index } => {
let mut name = self.display_name(&node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} output {output_index}")
let name = self.display_name(&node_id, network_path);
format!("Connected to output #{output_index} of \"{name}\", ID: {node_id}.")
}
OutputConnector::Import(import_index) => format!("Import index {import_index}"),
OutputConnector::Import(import_index) => format!("Connected to import #{import_index}."),
})
.unwrap_or("nothing".to_string());
.unwrap_or("Connected to nothing.".to_string());
let (name, description) = match input_connector {
InputConnector::Node { node_id, input_index } => self.displayed_input_name_and_description(node_id, *input_index, network_path),
@@ -659,7 +656,7 @@ impl NodeNetworkInterface {
} else if let Some(export_type_name) = input_type.compiled_nested_type().map(|nested| nested.to_string()) {
export_type_name
} else {
format!("Export index {}", export_index)
format!("Export #{}", export_index)
};
(export_name, String::new())
@@ -709,7 +706,7 @@ impl NodeNetworkInterface {
} else if let Some(import_type_name) = output_type.compiled_nested_type().map(|nested| nested.to_string()) {
import_type_name
} else {
format!("Import index {}", import_index)
format!("Import #{}", import_index)
};
(import_name, description)
@@ -724,19 +721,16 @@ impl NodeNetworkInterface {
.unwrap_or_default()
.iter()
.map(|input| match input {
InputConnector::Node { node_id, input_index } => {
let mut name = self.display_name(node_id, network_path);
if cfg!(debug_assertions) {
name.push_str(&format!(" (id: {node_id})"));
}
format!("{name} input {input_index}")
&InputConnector::Node { node_id, input_index } => {
let name = self.display_name(&node_id, network_path);
format!("Connected to input #{input_index} of \"{name}\", ID: {node_id}.")
}
InputConnector::Export(export_index) => format!("Export index {export_index}"),
InputConnector::Export(export_index) => format!("Connected to export #{export_index}."),
})
.collect::<Vec<_>>();
if connected_to.is_empty() {
connected_to.push("nothing".to_string());
connected_to.push("Connected to nothing.".to_string());
}
Some(FrontendGraphOutput {
@@ -1014,7 +1008,6 @@ impl NodeNetworkInterface {
pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
self.get_node_definition(node_id, network_path)
.map(|node_definition| node_definition.description.to_string())
.filter(|description| description != "TODO")
.unwrap_or_default()
}
@@ -6170,7 +6163,7 @@ pub struct InputPersistentMetadata {
pub widget_override: Option<String>,
/// An empty input name means to use the type as the name.
pub input_name: String,
/// Displayed as the tooltip.
/// Displayed as the tooltip description.
pub input_description: String,
}
@@ -6233,8 +6226,8 @@ impl InputPersistentMetadata {
self
}
pub fn with_description(mut self, tooltip: &str) -> Self {
self.input_description = tooltip.to_string();
pub fn with_description(mut self, description: &str) -> Self {
self.input_description = description.to_string();
self
}
}
@@ -6314,9 +6307,9 @@ impl From<(&str, &str)> for InputMetadata {
}
impl InputMetadata {
pub fn with_name_description_override(input_name: &str, tooltip: &str, widget_override: WidgetOverride) -> Self {
pub fn with_name_description_override(input_name: &str, description: &str, widget_override: WidgetOverride) -> Self {
InputMetadata {
persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(tooltip).with_override(widget_override),
persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(description).with_override(widget_override),
..Default::default()
}
}
@@ -88,7 +88,7 @@ impl TypeSource {
self.compiled_nested_type().map(|ty| format!("type:{ty}"))
}
/// The type to display in the tooltip.
/// The type to display in the tooltip label.
pub fn resolved_type_tooltip_string(&self) -> String {
match self {
TypeSource::Compiled(compiled_type) => format!("Data Type: {:?}", compiled_type.nested_type().to_string()),
@@ -35,7 +35,6 @@ impl serde::Serialize for JsRawBuffer {
pub struct LayerPanelEntry {
pub id: NodeId,
pub alias: String,
pub tooltip: String,
#[serde(rename = "inSelectedNetwork")]
pub in_selected_network: bool,
#[serde(rename = "childrenAllowed")]
@@ -42,8 +42,8 @@ impl std::fmt::Display for GraphWireStyle {
impl GraphWireStyle {
pub fn tooltip_description(&self) -> &'static str {
match self {
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes.",
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes.",
}
}
@@ -19,9 +19,9 @@ impl std::fmt::Display for SelectionMode {
impl SelectionMode {
pub fn tooltip_description(&self) -> &'static str {
match self {
SelectionMode::Touched => "Select all layers at least partially covered by the dragged selection area",
SelectionMode::Enclosed => "Select only layers fully enclosed by the dragged selection area",
SelectionMode::Directional => r#""Touched" for leftward drags, "Enclosed" for rightward drags"#,
SelectionMode::Touched => "Select all layers at least partially covered by the dragged selection area.",
SelectionMode::Enclosed => "Select only layers fully enclosed by the dragged selection area.",
SelectionMode::Directional => r#""Touched" for leftward drags, "Enclosed" for rightward drags."#,
}
}
}
@@ -87,7 +87,7 @@ impl ToolColorOptions {
} else {
let reset = IconButton::new("CloseX", 12)
.disabled(self.custom_color.is_none() && self.color_type == ToolColorType::Custom)
.tooltip("Clear Color")
.tooltip_label("Clear Color")
.on_update(reset_callback);
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
@@ -101,8 +101,8 @@ impl ToolColorOptions {
("CustomColor", "Custom Color", ToolColorType::Custom),
]
.into_iter()
.map(|(icon, tooltip, color_type)| {
let mut entry = RadioEntryData::new(format!("{color_type:?}")).tooltip(tooltip).icon(icon);
.map(|(icon, label, color_type)| {
let mut entry = RadioEntryData::new(format!("{color_type:?}")).tooltip_label(label).icon(icon);
entry.on_update = radio_callback(color_type);
entry
})
@@ -14,7 +14,8 @@ use std::fmt;
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
.tooltip_label(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" })
.tooltip_description("Unless pinned, the pivot will return to its prior reference point when a new selection is made.")
.disabled(!enabled)
.on_update(move |_| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions {
@@ -31,7 +32,8 @@ pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) ->
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
ReferencePointInput::new(reference_point)
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
.tooltip_label("Custom Pivot Reference Point")
.tooltip_description("Places the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
.disabled(disabled)
.on_update(move |pivot_input: &ReferencePointInput| match source {
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
@@ -62,11 +64,13 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
vec![
CheckboxInput::new(!state.disabled)
.tooltip(
"Pivot Gizmo\n\
\n\
.tooltip_label("Pivot Gizmo")
.tooltip_description(
"
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
Disabled: rotation and scaling occurs about the center of the selection bounds.",
Disabled: rotation and scaling occurs about the center of the selection bounds.
"
.trim(),
)
.on_update(move |optional_input: &CheckboxInput| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions {
@@ -86,14 +90,16 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
PivotGizmoType::Average => 1,
PivotGizmoType::Active => 2,
}))
.tooltip(
"Pivot Gizmo Type\n\
\n\
.tooltip_label("Pivot Gizmo Type")
.tooltip_description(
"
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
\n\
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.
"
.trim(),
)
.disabled(state.disabled)
.widget_holder(),
@@ -52,7 +52,7 @@ impl ShapeType {
.into()
}
pub fn tooltip(&self) -> String {
pub fn tooltip_label(&self) -> String {
(match self {
Self::Line => "Line Tool",
Self::Rectangle => "Rectangle Tool",
@@ -62,6 +62,14 @@ impl ShapeType {
.into()
}
pub fn tooltip_description(&self) -> String {
(match self {
// TODO: Add descriptions to all the shape tools
_ => "",
})
.into()
}
pub fn icon_name(&self) -> String {
(match self {
Self::Line => "VectorLineTool",
@@ -42,7 +42,7 @@ impl ToolMetadata for ArtboardTool {
fn icon_name(&self) -> String {
"GeneralArtboardTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Artboard Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -90,7 +90,7 @@ impl ToolMetadata for BrushTool {
fn icon_name(&self) -> String {
"RasterBrushTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Brush Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -215,7 +215,7 @@ impl LayoutHolder for BrushTool {
widgets.push(
DropdownInput::new(blend_mode_entries)
.selected_index(self.options.blend_mode.index_in_list().map(|index| index as u32))
.tooltip("The blend mode used with the background when performing a brush stroke. Only used in draw mode.")
.tooltip_description("The blend mode used with the background when performing a brush stroke. Only used in draw mode.")
.disabled(self.options.draw_mode != DrawMode::Draw)
.widget_holder(),
);
@@ -25,7 +25,7 @@ impl ToolMetadata for EyedropperTool {
fn icon_name(&self) -> String {
"GeneralEyedropperTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Eyedropper Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -27,7 +27,7 @@ impl ToolMetadata for FillTool {
fn icon_name(&self) -> String {
"GeneralFillTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Fill Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -72,7 +72,7 @@ impl ToolMetadata for FreehandTool {
fn icon_name(&self) -> String {
"VectorFreehandTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Freehand Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -45,7 +45,7 @@ impl ToolMetadata for GradientTool {
fn icon_name(&self) -> String {
"GeneralGradientTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Gradient Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -91,13 +91,13 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
impl LayoutHolder for GradientTool {
fn layout(&self) -> Layout {
let gradient_type = RadioInput::new(vec![
RadioEntryData::new("Linear").label("Linear").tooltip("Linear gradient").on_update(move |_| {
RadioEntryData::new("Linear").label("Linear").tooltip_label("Linear Gradient").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::Type(GradientType::Linear),
}
.into()
}),
RadioEntryData::new("Radial").label("Radial").tooltip("Radial gradient").on_update(move |_| {
RadioEntryData::new("Radial").label("Radial").tooltip_label("Radial Gradient").on_update(move |_| {
GradientToolMessage::UpdateOptions {
options: GradientOptionsUpdate::Type(GradientType::Radial),
}
@@ -24,7 +24,7 @@ impl ToolMetadata for NavigateTool {
fn icon_name(&self) -> String {
"GeneralNavigateTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Navigate Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -4,6 +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::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};
@@ -184,7 +185,7 @@ impl ToolMetadata for PathTool {
fn icon_name(&self) -> String {
"VectorPathTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Path Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -235,7 +236,7 @@ impl LayoutHolder for PathTool {
let related_seperator = Separator::new(SeparatorType::Related).widget_holder();
let unrelated_seperator = Separator::new(SeparatorType::Unrelated).widget_holder();
let colinear_handles_tooltip = "Keep both handles unbent, each 180° apart, when moving either";
let colinear_handles_description = "Keep both handles unbent, each 180° apart, when moving either.";
let colinear_handles_state = manipulator_angle.and_then(|angle| match angle {
ManipulatorAngle::Colinear => Some(true),
ManipulatorAngle::Free => Some(false),
@@ -253,32 +254,39 @@ impl LayoutHolder for PathTool {
PathToolMessage::ManipulatorMakeHandlesFree.into()
}
})
.tooltip(colinear_handles_tooltip)
.tooltip_label("Colinear Handles")
.tooltip_description(colinear_handles_description)
.for_label(checkbox_id)
.widget_holder();
let colinear_handles_label = TextLabel::new("Colinear Handles")
.disabled(!self.tool_data.can_toggle_colinearity)
.tooltip(colinear_handles_tooltip)
.tooltip_label("Colinear Handles")
.tooltip_description(colinear_handles_description)
.for_checkbox(checkbox_id)
.widget_holder();
let point_editing_mode = CheckboxInput::new(self.options.path_editing_mode.point_editing_mode)
// TODO(Keavon): Replace with a real icon
.icon("Dot")
.tooltip("Point Editing Mode\n\nShift + click to select both modes.")
.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())
.on_update(|_| PathToolMessage::TogglePointEditing.into())
.widget_holder();
let segment_editing_mode = CheckboxInput::new(self.options.path_editing_mode.segment_editing_mode)
// TODO(Keavon): Replace with a real icon
.icon("Remove")
.tooltip("Segment Editing Mode\n\nShift + click to select both modes.")
.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())
.on_update(|_| PathToolMessage::ToggleSegmentEditing.into())
.widget_holder();
let path_overlay_mode_widget = RadioInput::new(vec![
RadioEntryData::new("all")
.icon("HandleVisibilityAll")
.tooltip("Show all handles regardless of selection")
.tooltip_label("Show All Handles")
.tooltip_description("Show all handles regardless of selection.")
.on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles),
@@ -287,7 +295,8 @@ impl LayoutHolder for PathTool {
}),
RadioEntryData::new("selected")
.icon("HandleVisibilitySelected")
.tooltip("Show only handles of the segments connected to selected points")
.tooltip_label("Show Connected Handles")
.tooltip_description("Show only handles of the segments connected to selected points.")
.on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles),
@@ -296,7 +305,8 @@ impl LayoutHolder for PathTool {
}),
RadioEntryData::new("frontier")
.icon("HandleVisibilityFrontier")
.tooltip("Show only handles at the frontiers of the segments connected to selected points")
.tooltip_label("Show Frontier Handles")
.tooltip_description("Show only handles at the frontiers of the segments connected to selected points.")
.on_update(move |_| {
PathToolMessage::UpdateOptions {
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles),
@@ -310,7 +320,10 @@ impl LayoutHolder for PathTool {
// Works only if a single layer is selected and its type is Vector
let path_node_button = TextButton::new("Make Path Editable")
.icon(Some("NodeShape".into()))
.tooltip("Make Path Editable")
.tooltip_label("Make Path Editable")
.tooltip_description(
"Enables the Pen and Path tools to directly edit layer geometry resulting from nondestructive operations. This inserts a 'Path' node as the last operation of the selected layer.",
)
.on_update(|_| NodeGraphMessage::AddPathNode.into())
.disabled(!self.tool_data.make_path_editable_is_allowed)
.widget_holder();
@@ -128,7 +128,7 @@ impl ToolMetadata for PenTool {
fn icon_name(&self) -> String {
"VectorPenTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Pen Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -215,7 +215,8 @@ impl LayoutHolder for PenTool {
RadioInput::new(vec![
RadioEntryData::new("all")
.icon("HandleVisibilityAll")
.tooltip("Show all handles regardless of selection")
.tooltip_label("Show All Handles")
.tooltip_description("Show all handles regardless of selection.")
.on_update(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles),
@@ -224,7 +225,8 @@ impl LayoutHolder for PenTool {
}),
RadioEntryData::new("frontier")
.icon("HandleVisibilityFrontier")
.tooltip("Show only handles at the frontiers of the segments connected to selected points")
.tooltip_label("Show Frontier Handles")
.tooltip_description("Show only handles at the frontiers of the segments connected to selected points.")
.on_update(move |_| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles),
@@ -122,7 +122,7 @@ impl ToolMetadata for SelectTool {
fn icon_name(&self) -> String {
"GeneralSelectTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Select Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -146,10 +146,9 @@ impl SelectTool {
DropdownInput::new(vec![layer_selection_behavior_entries])
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
.tooltip(
"Selection Mode\n\
\n\
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
.tooltip_label("Selection Mode")
.tooltip_description(
"Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
)
.widget_holder()
@@ -160,7 +159,7 @@ impl SelectTool {
.into_iter()
.flat_map(|axis| [(axis, AlignAggregate::Min), (axis, AlignAggregate::Center), (axis, AlignAggregate::Max)])
.map(move |(axis, aggregate)| {
let (icon, tooltip) = match (axis, aggregate) {
let (icon, label) = match (axis, aggregate) {
(AlignAxis::X, AlignAggregate::Min) => ("AlignLeft", "Align Left"),
(AlignAxis::X, AlignAggregate::Center) => ("AlignHorizontalCenter", "Align Horizontal Center"),
(AlignAxis::X, AlignAggregate::Max) => ("AlignRight", "Align Right"),
@@ -169,7 +168,7 @@ impl SelectTool {
(AlignAxis::Y, AlignAggregate::Max) => ("AlignBottom", "Align Bottom"),
};
IconButton::new(icon, 24)
.tooltip(tooltip)
.tooltip_label(label)
.on_update(move |_| DocumentMessage::AlignSelectedLayers { axis, aggregate }.into())
.disabled(disabled)
.widget_holder()
@@ -177,21 +176,23 @@ impl SelectTool {
}
fn flip_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
[(FlipAxis::X, "Horizontal"), (FlipAxis::Y, "Vertical")].into_iter().map(move |(flip_axis, name)| {
IconButton::new("Flip".to_string() + name, 24)
.tooltip("Flip ".to_string() + name)
.on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into())
.disabled(disabled)
.widget_holder()
})
[(FlipAxis::X, "FlipHorizontal", "Flip Horizontal"), (FlipAxis::Y, "FlipVertical", "Flip Vertical")]
.into_iter()
.map(move |(flip_axis, icon, label)| {
IconButton::new(icon, 24)
.tooltip_label(label)
.on_update(move |_| DocumentMessage::FlipSelectedLayers { flip_axis }.into())
.disabled(disabled)
.widget_holder()
})
}
fn turn_widgets(&self, disabled: bool) -> impl Iterator<Item = WidgetHolder> + use<> {
[(-90., "TurnNegative90", "Turn -90°"), (90., "TurnPositive90", "Turn 90°")]
.into_iter()
.map(move |(degrees, icon, name)| {
.map(move |(degrees, icon, label)| {
IconButton::new(icon, 24)
.tooltip(name)
.tooltip_label(label)
.on_update(move |_| DocumentMessage::RotateSelectedLayers { degrees }.into())
.disabled(disabled)
.widget_holder()
@@ -201,13 +202,9 @@ impl SelectTool {
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
let list = <BooleanOperation as graphene_std::choice_type::ChoiceTypeStatic>::list();
list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring {
tooltip.push_str("\n\n");
tooltip.push_str(doc);
}
IconButton::new(info.icon.unwrap(), 24)
.tooltip(tooltip)
.tooltip_label(info.label)
.tooltip_description(info.description.unwrap_or_default())
.disabled(selected_count == 0)
.on_update(move |_| {
let group_folder_type = GroupFolderType::BooleanOperation(*operation);
@@ -432,7 +432,7 @@ impl ToolMetadata for ShapeTool {
fn icon_name(&self) -> String {
"VectorPolygonTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Shape Tool".into()
}
fn tool_type(&self) -> ToolType {
@@ -79,7 +79,7 @@ impl ToolMetadata for SplineTool {
fn icon_name(&self) -> String {
"VectorSplineTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Spline Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
@@ -88,7 +88,7 @@ impl ToolMetadata for TextTool {
fn icon_name(&self) -> String {
"VectorTextTool".into()
}
fn tooltip(&self) -> String {
fn tooltip_label(&self) -> String {
"Text Tool".into()
}
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
+50 -19
View File
@@ -124,13 +124,13 @@ impl DocumentToolData {
LayoutGroup::Row {
widgets: vec![
IconButton::new("SwapVertical", 16)
.tooltip("Swap")
.tooltip_shortcut(action_keys!(ToolMessageDiscriminant::SwapColors))
.tooltip_label("Swap")
.shortcut_keys(action_keys!(ToolMessageDiscriminant::SwapColors))
.on_update(|_| ToolMessage::SwapColors.into())
.widget_holder(),
IconButton::new("WorkingColors", 16)
.tooltip("Reset")
.tooltip_shortcut(action_keys!(ToolMessageDiscriminant::ResetColors))
.tooltip_label("Reset")
.shortcut_keys(action_keys!(ToolMessageDiscriminant::ResetColors))
.on_update(|_| ToolMessage::ResetColors.into())
.widget_holder(),
],
@@ -201,7 +201,11 @@ pub trait ToolTransition {
pub trait ToolMetadata {
fn icon_name(&self) -> String;
fn tooltip(&self) -> String;
fn tooltip_label(&self) -> String;
fn tooltip_description(&self) -> String {
// TODO: Remove this to make tool descriptions mandatory once we've written them all
String::new()
}
fn tool_type(&self) -> ToolType;
}
@@ -240,12 +244,13 @@ impl LayoutHolder for ToolData {
match tool_availability {
ToolAvailability::Available(tool) =>
ToolEntry::new(tool.tool_type(), tool.icon_name())
.tooltip(tool.tooltip())
.tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(tool.tool_type()))),
.tooltip_label(tool.tooltip_label())
.shortcut_keys(action_keys!(tool_type_to_activate_tool_message(tool.tool_type()))),
ToolAvailability::AvailableAsShape(shape) =>
ToolEntry::new(shape.tool_type(), shape.icon_name())
.tooltip(shape.tooltip())
.tooltip_shortcut(action_keys!(tool_type_to_activate_tool_message(shape.tool_type()))),
.tooltip_label(shape.tooltip_label())
.tooltip_description(shape.tooltip_description())
.shortcut_keys(action_keys!(tool_type_to_activate_tool_message(shape.tool_type()))),
ToolAvailability::ComingSoon(tool) => tool.clone(),
}
})
@@ -253,15 +258,19 @@ 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, tooltip_shortcut, tool_type, icon_name }| {
let buttons = group.into_iter().map(|ToolEntry { tooltip_label, tooltip_description, tooltip_shortcut, shortcut_keys, tool_type, icon_name }| {
let coming_soon = tooltip_description.contains("Coming soon.");
IconButton::new(icon_name, 32)
.disabled(false)
.active(match tool_type {
ToolType::Line | ToolType::Ellipse | ToolType::Rectangle => { self.active_shape_type.is_some() && active_tool == tool_type }
_ => active_tool == tool_type,
})
.tooltip(tooltip.clone())
.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(),
@@ -269,7 +278,7 @@ impl LayoutHolder for ToolData {
ToolType::Ellipse => ToolMessage::ActivateToolShapeEllipse.into(),
ToolType::Shape => ToolMessage::ActivateToolShape.into(),
_ => {
if !tooltip.contains("Coming Soon") { (ToolMessage::ActivateTool { tool_type }).into() } else { (DialogMessage::RequestComingSoonDialog { issue: None }).into() }
if !coming_soon { (ToolMessage::ActivateTool { tool_type }).into() } else { (DialogMessage::RequestComingSoonDialog { issue: None }).into() }
}
}
})
@@ -295,8 +304,10 @@ pub struct ToolEntry {
pub tool_type: ToolType,
#[widget_builder(constructor)]
pub icon_name: String,
pub tooltip: String,
pub tooltip_shortcut: Option<ActionKeys>,
pub tooltip_label: String,
pub tooltip_description: String,
pub tooltip_shortcut: String,
pub shortcut_keys: Option<ActionKeys>,
}
#[derive(Debug)]
@@ -414,11 +425,31 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
vec![
// Raster tool group
ToolAvailability::Available(Box::<brush_tool::BrushTool>::default()),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Heal, "RasterHealTool").tooltip("Coming Soon: Heal Tool (J)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Clone, "RasterCloneTool").tooltip("Coming Soon: Clone Tool (C)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip("Coming Soon: Patch Tool")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Detail, "RasterDetailTool").tooltip("Coming Soon: Detail Tool (D)")),
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Relight, "RasterRelightTool").tooltip("Coming Soon: Relight Tool (O)")),
ToolAvailability::ComingSoon(
ToolEntry::new(ToolType::Heal, "RasterHealTool")
.tooltip_label("Heal Tool")
.tooltip_description("Coming soon.")
.tooltip_shortcut(Key::KeyJ.to_string()),
),
ToolAvailability::ComingSoon(
ToolEntry::new(ToolType::Clone, "RasterCloneTool")
.tooltip_label("Clone Tool")
.tooltip_description("Coming soon.")
.tooltip_shortcut(Key::KeyC.to_string()),
),
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()),
),
ToolAvailability::ComingSoon(
ToolEntry::new(ToolType::Relight, "RasterRelightTool")
.tooltip_label("Relight Tool")
.tooltip_description("Coming soon.")
.tooltip_shortcut(Key::KeyO.to_string()),
),
],
]
}