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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
94 changed files with 1323 additions and 580 deletions

View File

@ -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![

View File

@ -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 {

View File

@ -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 }),

View File

@ -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,
}

View File

@ -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);
}
}
}

View File

@ -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)]

View File

@ -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)]

View File

@ -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

View File

@ -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,

View File

@ -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.,

View File

@ -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()

View File

@ -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()),

View File

@ -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
}

View File

@ -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,
}

View File

@ -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(),

View File

@ -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.)",
),
];

View File

@ -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()
}
}

View File

@ -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()),

View File

@ -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")]

View File

@ -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.",
}
}

View File

@ -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."#,
}
}
}

View File

@ -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
})

View File

@ -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(),

View File

@ -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",

View File

@ -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 {

View File

@ -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(),
);

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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),
}

View File

@ -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 {

View File

@ -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();

View File

@ -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),

View File

@ -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);

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

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()),
),
],
]
}

View File

@ -465,7 +465,8 @@ impl NodeRuntime {
if old.is_none_or(|v| !v.is_empty()) {
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
id: parent_network_node_id,
value: "<svg viewBox=\"0 0 10 10\"><title>Dense thumbnail omitted for performance</title><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>".to_string(),
value: "<svg viewBox=\"0 0 10 10\" data-tooltip-description=\"Dense thumbnail omitted for performance.\"><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>"
.to_string(),
});
}
return;

View File

@ -48,7 +48,7 @@ impl EditorTestUtils {
Err(e) => return Err(format!("update_node_graph_instrumented failed\n\n{e}")),
};
if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), UVec2::ONE, 1.0, Default::default()) {
if let Err(e) = exector.submit_current_node_graph_evaluation(document, DocumentId(0), UVec2::ONE, 1., Default::default()) {
return Err(format!("submit_current_node_graph_evaluation failed\n\n{e}"));
}
runtime.run().await;
@ -302,7 +302,7 @@ impl EditorTestUtils {
y: top_left.y,
width: bottom_right.x - top_left.x,
height: bottom_right.y - top_left.y,
scale: 1.0,
scale: 1.,
})
.await;
}

View File

@ -16,7 +16,7 @@ During development when HMR (hot-module replacement) occurs, these are also unmo
TypeScript files which provide reactive state and importable functions to Svelte components. Each module defines a Svelte writable store `const { subscribe, update } = writable({ .. });` and exports the `subscribe` method from the module in the returned object. Other functions may also be defined in the module and exported after `subscribe`, which provide a way for Svelte components to call functions to manipulate the state.
In `Editor.svelte`, an instance of each of these are given to Svelte's [`setContext()`](https://svelte.dev/docs#run-time-svelte-setcontext) function. This allows any component to access the state provider instance using `const exampleStateProvider = getContext<ExampleStateProvider>("exampleStateProvider");`.
In `Editor.svelte`, an instance of each of these are given to Svelte's `setContext()` function. This allows any component to access the state provider instance using `const exampleStateProvider = getContext<ExampleStateProvider>("exampleStateProvider");`.
## _I/O managers vs. state providers_

View File

@ -3,7 +3,7 @@
import { type Editor } from "@graphite/editor";
import { createClipboardManager } from "@graphite/io-managers/clipboard";
import { createHyperlinkManager } from "@graphite/io-managers/hyperlinks";
import { createHyperlinkManager } from "@graphite/io-managers/hyperlink";
import { createInputManager } from "@graphite/io-managers/input";
import { createLocalizationManager } from "@graphite/io-managers/localization";
import { createPanicManager } from "@graphite/io-managers/panic";
@ -15,6 +15,7 @@
import { createFullscreenState } from "@graphite/state-providers/fullscreen";
import { createNodeGraphState } from "@graphite/state-providers/node-graph";
import { createPortfolioState } from "@graphite/state-providers/portfolio";
import { createTooltipState } from "@graphite/state-providers/tooltip";
import { operatingSystem } from "@graphite/utility-functions/platform";
import MainWindow from "@graphite/components/window/MainWindow.svelte";
@ -26,6 +27,8 @@
// State provider systems
let dialog = createDialogState(editor);
setContext("dialog", dialog);
let tooltip = createTooltipState();
setContext("tooltip", tooltip);
let document = createDocumentState(editor);
setContext("document", document);
let fonts = createFontsState(editor);

View File

@ -31,6 +31,14 @@
blue: [0, 0, 1],
magenta: [1, 0, 1],
};
const PURE_COLORS_GRAYABLE = [
["Red", "#ff0000", "#4c4c4c"],
["Yellow", "#ffff00", "#e3e3e3"],
["Green", "#00ff00", "#969696"],
["Cyan", "#00ffff", "#b2b2b2"],
["Blue", "#0000ff", "#1c1c1c"],
["Magenta", "#ff00ff", "#696969"],
];
const editor = getContext<Editor>("editor");
@ -420,7 +428,13 @@
>
<LayoutCol class="pickers-and-gradient">
<LayoutRow class="pickers">
<LayoutCol class="saturation-value-picker" title={disabled ? "Saturation and value (disabled)" : "Saturation and value"} on:pointerdown={onPointerDown} data-saturation-value-picker>
<LayoutCol
class="saturation-value-picker"
data-tooltip-label="Saturation and Value"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
on:pointerdown={onPointerDown}
data-saturation-value-picker
>
{#if !isNone}
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} />
{/if}
@ -434,12 +448,24 @@
/>
{/if}
</LayoutCol>
<LayoutCol class="hue-picker" title={disabled ? "Hue (disabled)" : "Hue"} on:pointerdown={onPointerDown} data-hue-picker>
<LayoutCol
class="hue-picker"
data-tooltip-label="Hue"
data-tooltip-description={"The shade along the spectrum of the rainbow." + (disabled ? "\n\nDisabled (read-only)." : "")}
on:pointerdown={onPointerDown}
data-hue-picker
>
{#if !isNone}
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} />
{/if}
</LayoutCol>
<LayoutCol class="alpha-picker" title={disabled ? "Alpha (disabled)" : "Alpha"} on:pointerdown={onPointerDown} data-alpha-picker>
<LayoutCol
class="alpha-picker"
data-tooltip-label="Alpha"
data-tooltip-description={"The level of translucency." + (disabled ? "\n\nDisabled (read-only)." : "")}
on:pointerdown={onPointerDown}
data-alpha-picker
>
{#if !isNone}
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} />
{/if}
@ -479,11 +505,11 @@
class="choice-preview"
classes={{ outlined, transparency }}
styles={{ "--outline-amount": outlineFactor }}
tooltip={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before any change was made (right)" : "The present color choice"}
tooltipDescription={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before it was changed (right)." : "The present color choice."}
>
{#if !newColor.equals(oldColor) && !disabled}
<div class="swap-button-background"></div>
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltip="Swap" />
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltipLabel="Swap" />
{/if}
<LayoutCol class="new-color" classes={{ none: isNone }}>
{#if !newColor.equals(oldColor)}
@ -496,9 +522,12 @@
</LayoutCol>
{/if}
</LayoutRow>
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltip="Color model, color space, and HDR (coming soon)" /> -->
<!-- <DropdownInput entries={[[{ label: "sRGB" }]]} selectedIndex={0} disabled={true} tooltipDescription="Color model, color space, and HDR (coming soon)." /> -->
<LayoutRow>
<TextLabel tooltip={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}>Hex</TextLabel>
<TextLabel
tooltipLabel="Hex Color Code"
tooltipDescription={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}>Hex</TextLabel
>
<Separator type="Related" />
<LayoutRow>
<TextInput
@ -509,13 +538,14 @@
setColorCode(detail);
}}
centered={true}
tooltip={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}
tooltipLabel="Hex Color Code"
tooltipDescription={"Color code in hexadecimal format. 6 digits if opaque, 8 with alpha.\nAccepts input of CSS color values including named colors."}
bind:this={hexCodeInputWidget}
/>
</LayoutRow>
</LayoutRow>
<LayoutRow>
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0255">RGB</TextLabel>
<TextLabel tooltipLabel="Red/Green/Blue" tooltipDescription="Integers 0255.">RGB</TextLabel>
<Separator type="Related" />
<LayoutRow>
{#each rgbChannels as [channel, strength], index}
@ -535,15 +565,17 @@
min={0}
max={255}
minWidth={1}
tooltip={`${{ r: "Red", g: "Green", b: "Blue" }[channel]} channel, integers 0255`}
tooltipLabel={{ r: "Red Channel", g: "Green Channel", b: "Blue Channel" }[channel]}
tooltipDescription="Integers 0255."
/>
{/each}
</LayoutRow>
</LayoutRow>
<LayoutRow>
<TextLabel tooltip={"Hue/Saturation/Value, also known as Hue/Saturation/Brightness (HSB).\nNot to be confused with Hue/Saturation/Lightness (HSL), a different color model."}>
HSV
</TextLabel>
<TextLabel
tooltipLabel="Hue/Saturation/Value"
tooltipDescription="Also known as Hue/Saturation/Brightness (HSB). Not to be confused with Hue/Saturation/Lightness (HSL), a different color model.">HSV</TextLabel
>
<Separator type="Related" />
<LayoutRow>
{#each hsvChannels as [channel, strength], index}
@ -565,17 +597,22 @@
unit={channel === "h" ? "°" : "%"}
minWidth={1}
displayDecimalPlaces={1}
tooltip={{
h: `Hue component, the shade along the spectrum of the rainbow`,
s: `Saturation component, the vividness from grayscale to full color`,
v: "Value component, the brightness from black to full color",
tooltipLabel={{
h: "Hue Component",
s: "Saturation Component",
v: "Value Component",
}[channel]}
tooltipDescription={{
h: "The shade along the spectrum of the rainbow.",
s: "The vividness from grayscale to full color.",
v: "The brightness from black to full color.",
}[channel]}
/>
{/each}
</LayoutRow>
</LayoutRow>
<LayoutRow>
<TextLabel tooltip="Scale of translucency, from transparent (0%) to opaque (100%), for the color's alpha channel">Alpha</TextLabel>
<TextLabel tooltipLabel="Alpha" tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%).">Alpha</TextLabel>
<Separator type="Related" />
<NumberInput
value={!isNone ? alpha * 100 : undefined}
@ -594,29 +631,54 @@
unit="%"
mode="Range"
displayDecimalPlaces={1}
tooltip="Scale of translucency, from transparent (0%) to opaque (100%), for the color's alpha channel"
tooltipLabel="Alpha"
tooltipDescription="The level of translucency, from transparent (0%) to opaque (100%)."
/>
</LayoutRow>
<LayoutRow class="leftover-space" />
<LayoutRow>
{#if allowNone && !gradient}
<button class="preset-color none" {disabled} on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
<button
class="preset-color none"
{disabled}
on:click={() => setColorPreset("none")}
data-tooltip-label="Set to No Color"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
tabindex="0"
></button>
<Separator type="Related" />
{/if}
<button class="preset-color black" {disabled} on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
<button
class="preset-color black"
{disabled}
on:click={() => setColorPreset("black")}
data-tooltip-label="Set to Black"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
tabindex="0"
></button>
<Separator type="Related" />
<button class="preset-color white" {disabled} on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
<button
class="preset-color white"
{disabled}
on:click={() => setColorPreset("white")}
data-tooltip-label="Set to White"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
tabindex="0"
></button>
<Separator type="Related" />
<button class="preset-color pure" {disabled} on:click={setColorPresetSubtile} tabindex="-1">
<div data-pure-tile="red" style="--pure-color: #ff0000; --pure-color-gray: #4c4c4c" title="Set to red" />
<div data-pure-tile="yellow" style="--pure-color: #ffff00; --pure-color-gray: #e3e3e3" title="Set to yellow" />
<div data-pure-tile="green" style="--pure-color: #00ff00; --pure-color-gray: #969696" title="Set to green" />
<div data-pure-tile="cyan" style="--pure-color: #00ffff; --pure-color-gray: #b2b2b2" title="Set to cyan" />
<div data-pure-tile="blue" style="--pure-color: #0000ff; --pure-color-gray: #1c1c1c" title="Set to blue" />
<div data-pure-tile="magenta" style="--pure-color: #ff00ff; --pure-color-gray: #696969" title="Set to magenta" />
{#each PURE_COLORS_GRAYABLE as [name, color, gray]}
<div
data-pure-tile={name.toLowerCase()}
style:--pure-color={color}
style:--pure-color-gray={gray}
data-tooltip-label="Set to Red"
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
/>
{/each}
</button>
<Separator type="Related" />
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltipLabel="Eyedropper" tooltipDescription="Sample a pixel color from the document." />
</LayoutRow>
</LayoutCol>
</LayoutRow>

View File

@ -30,7 +30,9 @@
export let interactive = false;
export let scrollableY = false;
export let virtualScrollingEntryHeight = 0;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Keep the child references outside of the entries array so as to avoid infinite recursion.
let childReferences: MenuList[][] = [];
@ -423,7 +425,9 @@
class="row"
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
styles={{ height: virtualScrollingEntryHeight || "20px" }}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
on:click={() => !entry.disabled && onEntryClick(entry)}
on:pointerenter={() => !entry.disabled && onEntryPointerEnter(entry)}
on:pointerleave={() => !entry.disabled && onEntryPointerLeave(entry)}

View File

@ -118,7 +118,13 @@
<TextLabel>{nodeCategory[0]}</TextLabel>
</summary>
{#each nodeCategory[1].nodes as nodeType}
<TextButton {disabled} label={nodeType.name} tooltip={$nodeGraph.nodeDescriptions.get(nodeType.name)} action={() => dispatch("selectNodeType", nodeType.name)} />
<TextButton
{disabled}
label={nodeType.name}
tooltipLabel={nodeType.name}
tooltipDescription={$nodeGraph.nodeDescriptions.get(nodeType.name)}
action={() => dispatch("selectNodeType", nodeType.name)}
/>
{/each}
</details>
{:else}

View File

@ -0,0 +1,81 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const tooltip = getContext<TooltipState>("tooltip");
const editor = getContext<Editor>("editor");
let self: FloatingMenu | undefined;
$: label = filterTodo($tooltip.element?.getAttribute("data-tooltip-label")?.trim());
$: description = filterTodo($tooltip.element?.getAttribute("data-tooltip-description")?.trim());
$: shortcut = filterTodo($tooltip.element?.getAttribute("data-tooltip-shortcut")?.trim());
// TODO: Once all TODOs are replaced with real text, remove this function
function filterTodo(text: string | undefined): string | undefined {
if (text?.trim().toUpperCase() === "TODO" && !editor.handle.inDevelopmentMode()) return "";
return text;
}
</script>
<div class="tooltip" style:top={`${$tooltip.position.y}px`} style:left={`${$tooltip.position.x}px`}>
{#if label || description}
<FloatingMenu open={true} type="Tooltip" direction="Bottom" bind:this={self}>
{#if label || shortcut}
<LayoutRow class="tooltip-header">
{#if label}
<TextLabel class="tooltip-label">{label}</TextLabel>
{/if}
{#if shortcut}
<TextLabel class="tooltip-shortcut">{shortcut}</TextLabel>
{/if}
</LayoutRow>
{/if}
{#if description}
<TextLabel class="tooltip-description">{description}</TextLabel>
{/if}
</FloatingMenu>
{/if}
</div>
<style lang="scss" global>
.tooltip {
position: absolute;
pointer-events: none;
width: 0;
height: 0;
.floating-menu-content {
max-width: Min(400px, 50vw);
.tooltip-header + .tooltip-description {
margin-top: 4px;
}
.text-label {
white-space: pre-wrap;
}
.text-label + .tooltip-shortcut {
margin-left: 8px;
}
.tooltip-shortcut {
color: var(--color-b-lightgray);
background: var(--color-3-darkgray);
padding: 0 4px;
border-radius: 4px;
}
.tooltip-description {
color: var(--color-b-lightgray);
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts" context="module">
export type MenuType = "Popover" | "Dropdown" | "Dialog" | "Cursor";
export type MenuType = "Popover" | "Tooltip" | "Dropdown" | "Dialog" | "Cursor";
/// Prevents the escape key from closing the parent floating menu of the given element.
/// This works by momentarily setting the `data-escape-does-not-close` attribute on the parent floating menu element.
@ -64,7 +64,6 @@
let measuringOngoingGuard = false;
let minWidthParentWidth = 0;
let pointerStillDown = false;
let workspaceBounds = new DOMRect();
let floatingMenuBounds = new DOMRect();
let floatingMenuContentBounds = new DOMRect();
@ -174,34 +173,50 @@
function positionAndStyleFloatingMenu() {
if (type === "Cursor") return;
const workspace = document.querySelector("[data-workspace]");
const floatingMenuContentDiv = floatingMenuContent?.div?.();
if (!workspace || !self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
if (!self || !floatingMenuContainer || !floatingMenuContent || !floatingMenuContentDiv) return;
const viewportBounds = document.documentElement.getBoundingClientRect();
workspaceBounds = workspace.getBoundingClientRect();
const windowBounds = document.documentElement.getBoundingClientRect();
floatingMenuBounds = self.getBoundingClientRect();
const floatingMenuContainerBounds = floatingMenuContainer.getBoundingClientRect();
floatingMenuContentBounds = floatingMenuContentDiv.getBoundingClientRect();
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
const overflowingLeft = floatingMenuContentBounds.left - windowEdgeMargin <= windowBounds.left;
const overflowingRight = floatingMenuContentBounds.right + windowEdgeMargin >= windowBounds.right;
const overflowingTop = floatingMenuContentBounds.top - windowEdgeMargin <= windowBounds.top;
const overflowingBottom = floatingMenuContentBounds.bottom + windowEdgeMargin >= windowBounds.bottom;
// TODO: Make this work for all types. This is currently limited to tooltips because they're inherently small and transient.
// TODO: But on popovers and dropdowns, it's a bit harder to do this right. First we check if it's overflowing and flip the direction to avoid the overflow.
// TODO: But once it's flipped, if the position moves and the menu would no longer be overflowing, we're still flipped and thus unable to automatically notice the need to flip back.
// TODO: So as a result, once flipped, it stays flipped forever even if the menu spawner element is moved back away from the edge of the window.
if (type === "Tooltip") {
// Flip direction if overflowing the edge of the window
if (direction === "Top" && overflowingTop) direction = "Bottom";
else if (direction === "Bottom" && overflowingBottom) direction = "Top";
else if (direction === "Left" && overflowingLeft) direction = "Right";
else if (direction === "Right" && overflowingRight) direction = "Left";
}
const inParentFloatingMenu = Boolean(floatingMenuContainer.closest("[data-floating-menu-content]"));
if (!inParentFloatingMenu) {
// Required to correctly position content when scrolled (it has a `position: fixed` to prevent clipping)
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
const tailOffset = type === "Popover" ? 10 : 0;
let tailOffset = 0;
if (type === "Popover") tailOffset = 10;
if (type === "Tooltip") tailOffset = direction === "Bottom" ? 20 : 10;
if (direction === "Bottom") floatingMenuContentDiv.style.top = `${tailOffset + floatingMenuBounds.y}px`;
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + (viewportBounds.height - floatingMenuBounds.y)}px`;
if (direction === "Top") floatingMenuContentDiv.style.bottom = `${tailOffset + (windowBounds.height - floatingMenuBounds.y)}px`;
if (direction === "Right") floatingMenuContentDiv.style.left = `${tailOffset + floatingMenuBounds.x}px`;
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + (viewportBounds.width - floatingMenuBounds.x)}px`;
if (direction === "Left") floatingMenuContentDiv.style.right = `${tailOffset + (windowBounds.width - floatingMenuBounds.x)}px`;
// Required to correctly position tail when scrolled (it has a `position: fixed` to prevent clipping)
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
if (tail && direction === "Bottom") tail.style.top = `${floatingMenuBounds.y}px`;
if (tail && direction === "Top") tail.style.bottom = `${viewportBounds.height - floatingMenuBounds.y}px`;
if (tail && direction === "Top") tail.style.bottom = `${windowBounds.height - floatingMenuBounds.y}px`;
if (tail && direction === "Right") tail.style.left = `${floatingMenuBounds.x}px`;
if (tail && direction === "Left") tail.style.right = `${viewportBounds.width - floatingMenuBounds.x}px`;
if (tail && direction === "Left") tail.style.right = `${windowBounds.width - floatingMenuBounds.x}px`;
}
type Edge = "Top" | "Bottom" | "Left" | "Right";
@ -212,31 +227,31 @@
zeroedBorderVertical = direction === "Top" ? "Bottom" : "Top";
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
if (floatingMenuContentBounds.left - windowEdgeMargin <= workspaceBounds.left) {
if (overflowingLeft) {
floatingMenuContentDiv.style.left = `${windowEdgeMargin}px`;
if (workspaceBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = "Left";
if (windowBounds.left + floatingMenuContainerBounds.left === 12) zeroedBorderHorizontal = "Left";
}
if (floatingMenuContentBounds.right + windowEdgeMargin >= workspaceBounds.right) {
if (overflowingRight) {
floatingMenuContentDiv.style.right = `${windowEdgeMargin}px`;
if (workspaceBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = "Right";
if (windowBounds.right - floatingMenuContainerBounds.right === 12) zeroedBorderHorizontal = "Right";
}
}
if (direction === "Left" || direction === "Right") {
zeroedBorderHorizontal = direction === "Left" ? "Right" : "Left";
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
if (floatingMenuContentBounds.top - windowEdgeMargin <= workspaceBounds.top) {
if (overflowingTop) {
floatingMenuContentDiv.style.top = `${windowEdgeMargin}px`;
if (workspaceBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = "Top";
if (windowBounds.top + floatingMenuContainerBounds.top === 12) zeroedBorderVertical = "Top";
}
if (floatingMenuContentBounds.bottom + windowEdgeMargin >= workspaceBounds.bottom) {
if (overflowingBottom) {
floatingMenuContentDiv.style.bottom = `${windowEdgeMargin}px`;
if (workspaceBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = "Bottom";
if (windowBounds.bottom - floatingMenuContainerBounds.bottom === 12) zeroedBorderVertical = "Bottom";
}
}
// Remove the rounded corner from the content where the tail perfectly meets the corner
if (type === "Popover" && windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {
if (displayTail && windowEdgeMargin === 6 && zeroedBorderVertical && zeroedBorderHorizontal) {
// We use `.style` on a div (instead of a style DOM attribute binding) because the binding causes the `afterUpdate()` hook to call the function we're in recursively forever
switch (`${zeroedBorderVertical}${zeroedBorderHorizontal}`) {
case "TopLeft":
@ -585,14 +600,18 @@
flex-direction: column;
}
&.top .tail {
&.top .tail,
&.topleft .tail,
&.topright .tail {
border-width: 8px 6px 0 6px;
border-color: var(--color-2-mildblack) transparent transparent transparent;
margin-left: -6px;
margin-bottom: 2px;
}
&.bottom .tail {
&.bottom .tail,
&.bottomleft .tail,
&.bottomright .tail {
border-width: 0 6px 8px 6px;
border-color: transparent transparent var(--color-2-mildblack) transparent;
margin-left: -6px;

View File

@ -5,7 +5,9 @@
let styleName = "";
export { styleName as style };
export let styles: Record<string, string | number | undefined> = {};
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// TODO: Add middle-click drag scrolling
export let scrollableX = false;
export let scrollableY = false;
@ -26,13 +28,15 @@
<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->
<div
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
data-scrollable-x={scrollableX ? "" : undefined}
data-scrollable-y={scrollableY ? "" : undefined}
class={`layout-col ${className} ${extraClasses}`.trim()}
class:scrollable-x={scrollableX}
class:scrollable-y={scrollableY}
style={`${styleName} ${extraStyles}`.trim() || undefined}
title={tooltip}
bind:this={self}
on:auxclick
on:blur

View File

@ -5,7 +5,9 @@
let styleName = "";
export { styleName as style };
export let styles: Record<string, string | number | undefined> = {};
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// TODO: Add middle-click drag scrolling
export let scrollableX = false;
export let scrollableY = false;
@ -26,13 +28,15 @@
<!-- Excluded events because these require `|passive` or `|nonpassive` modifiers. Use a <div> for these instead: `on:wheel`, `on:touchmove`, `on:touchstart` -->
<div
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
data-scrollable-x={scrollableX ? "" : undefined}
data-scrollable-y={scrollableY ? "" : undefined}
class={`layout-row ${className} ${extraClasses}`.trim()}
class:scrollable-x={scrollableX}
class:scrollable-y={scrollableY}
style={`${styleName} ${extraStyles}`.trim() || undefined}
title={tooltip}
bind:this={self}
on:auxclick
on:blur

View File

@ -698,7 +698,7 @@
.icon-button {
margin: 0;
&[title^="Coming Soon"] {
&[data-tooltip-description^="Coming soon."] {
opacity: 0.25;
transition: opacity 0.1s;

View File

@ -609,7 +609,6 @@
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
data-layer
data-index={index}
tooltip={listing.entry.tooltip}
on:pointerdown={(e) => layerPointerDown(e, listing)}
on:click={(e) => selectLayerWithModifiers(e, listing)}
>
@ -618,9 +617,12 @@
class="expand-arrow"
class:expanded={listing.entry.expanded}
disabled={!listing.entry.childrenPresent}
title={listing.entry.expanded
? "Collapse (Click) / Collapse All (Alt Click)"
: `Expand (Click) / Expand All (Alt Click)${listing.entry.ancestorOfSelected ? "\n(A selected layer is contained within)" : ""}`}
data-tooltip-label={listing.entry.expanded ? "Collapse (All)" : "Expand (All)"}
data-tooltip-description={(listing.entry.expanded
? "Hide the layers nested within. (To affect all open descendants, perform the shortcut shown.)"
: "Show the layers nested within. (To affect all closed descendants, perform the shortcut shown.)") +
(listing.entry.ancestorOfSelected && !listing.entry.expanded ? "\n\nNote: a selected layer is currently contained within.\n" : "")}
data-tooltip-shortcut="Alt Click"
on:click={(e) => handleExpandArrowClickWithModifiers(e, listing.entry.id)}
tabindex="0"
></button>
@ -628,7 +630,12 @@
<div class="expand-arrow-none"></div>
{/if}
{#if listing.entry.clipped}
<IconLabel icon="Clipped" class="clipped-arrow" tooltip="Clipping mask is active (Alt-click border to release)" />
<IconLabel
icon="Clipped"
class="clipped-arrow"
tooltipDescription="Clipping mask is active. To release it, perform the shortcut on the layer border."
tooltipShortcut="Alt Click"
/>
{/if}
<div class="thumbnail">
{#if $nodeGraph.thumbnails.has(listing.entry.id)}
@ -659,7 +666,8 @@
size={24}
icon={listing.entry.unlocked ? "PadlockUnlocked" : "PadlockLocked"}
hoverIcon={listing.entry.unlocked ? "PadlockLocked" : "PadlockUnlocked"}
tooltip={(listing.entry.unlocked ? "Lock" : "Unlock") + (!listing.entry.parentsUnlocked ? "\n(A parent of this layer is locked and that status is being inherited)" : "")}
tooltipLabel={listing.entry.unlocked ? "Lock" : "Unlock"}
tooltipDescription={!listing.entry.parentsUnlocked ? "A parent of this layer is locked and that status is being inherited." : ""}
/>
{/if}
<IconButton
@ -669,7 +677,8 @@
size={24}
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"}
tooltip={(listing.entry.visible ? "Hide" : "Show") + (!listing.entry.parentsVisible ? "\n(A parent of this layer is hidden and that status is being inherited)" : "")}
tooltipLabel={listing.entry.visible ? "Hide" : "Show"}
tooltipDescription={!listing.entry.parentsVisible ? "A parent of this layer is hidden and that status is being inherited." : ""}
/>
</LayoutRow>
{/each}

View File

@ -164,7 +164,7 @@
return `M-2,-2 L${nodeWidth + 2},-2 L${nodeWidth + 2},${nodeHeight + 2} L-2,${nodeHeight + 2}z ${rectangles.join(" ")}`;
}
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
function dataTypeTooltipLabel(value: FrontendGraphInput | FrontendGraphOutput): string {
return `Data Type: ${value.resolvedType}`;
}
@ -174,13 +174,11 @@
}
function outputConnectedToText(output: FrontendGraphOutput): string {
if (output.connectedTo.length === 0) return "Connected to nothing";
return `Connected to:\n${output.connectedTo.join("\n")}`;
return editor.handle.inDevelopmentMode() ? output.connectedTo.join("\n") : "";
}
function inputConnectedToText(input: FrontendGraphInput): string {
return `Connected to:\n${input.connectedTo}`;
return editor.handle.inDevelopmentMode() ? input.connectedTo : "";
}
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
@ -310,13 +308,14 @@
viewBox="0 0 8 8"
class="connector"
data-connector="output"
data-tooltip-label={dataTypeTooltipLabel(frontendOutput)}
data-tooltip-description={outputConnectedToText(frontendOutput)}
data-datatype={frontendOutput.dataType}
style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
>
<title>{`${dataTypeTooltip(frontendOutput)}\n\n${outputConnectedToText(frontendOutput)}`}</title>
{#if frontendOutput.connectedTo.length > 0}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -360,7 +359,7 @@
}}
/>
{#if index > 0}
<div class="reorder-drag-grip" title="Reorder this export" />
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" />
{/if}
{/if}
</div>
@ -382,14 +381,15 @@
viewBox="0 0 8 8"
class="connector"
data-connector="input"
data-tooltip-label={dataTypeTooltipLabel(frontendInput)}
data-tooltip-description={inputConnectedToText(frontendInput)}
data-datatype={frontendInput.dataType}
style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index}
>
<title>{`${dataTypeTooltip(frontendInput)}\n\n${inputConnectedToText(frontendInput)}`}</title>
{#if frontendInput.connectedTo !== "nothing"}
{#if frontendInput.connectedTo !== "Connected to nothing."}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -406,7 +406,7 @@
>
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
{#if index > 0}
<div class="reorder-drag-grip" title="Reorder this export" />
<div class="reorder-drag-grip" data-tooltip-description="Reorder this export" />
{/if}
<IconButton
size={16}
@ -505,7 +505,10 @@
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--layer-area-width={layerAreaWidth}
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
`.trim()}
data-node={node.id}
>
<div class="thumbnail">
@ -519,11 +522,12 @@
viewBox="0 0 8 12"
class="connector top"
data-connector="output"
data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}
data-tooltip-description={outputConnectedToText(node.primaryOutput)}
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo.length > 0}
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
{#if node.primaryOutputConnectedToLayer}
@ -540,14 +544,13 @@
viewBox="0 0 8 12"
class="connector bottom"
data-connector="input"
data-tooltip-label={node.primaryInput ? dataTypeTooltipLabel(node.primaryInput) : ""}
data-tooltip-description={node.primaryInput ? `${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}` : ""}
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{/if}
{#if node.primaryInput?.connectedTo !== "nothing"}
{#if node.primaryInput?.connectedTo !== "Connected to nothing."}
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
{#if node.primaryInputConnectedToLayer}
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
@ -564,12 +567,13 @@
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="connector"
data-tooltip-label={dataTypeTooltipLabel(stackDataInput)}
data-tooltip-description={`${validTypesText(stackDataInput).trim()}\n\n${inputConnectedToText(stackDataInput)}`}
data-connector="input"
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
{#if stackDataInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -582,7 +586,7 @@
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel>{node.displayName}</TextLabel>
</div>
<div class="solo-drag-grip" title="Drag only this layer without pushing others outside the stack"></div>
<div class="solo-drag-grip" data-tooltip-description="Drag only this layer without pushing others outside the stack"></div>
<IconButton
class="visibility"
data-visibility-button
@ -591,7 +595,7 @@
action={() => {
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
}}
tooltip={node.visible ? "Visible" : "Hidden"}
tooltipLabel={node.visible ? "Visible" : "Hidden"}
/>
<svg class="border-mask" width="0" height="0">
@ -650,7 +654,10 @@
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
data-tooltip-label={node.displayName === node.reference ? node.displayName : `${node.displayName} (${node.reference})`}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position.x}, ${node.position.y}).` : ""}
`.trim()}
data-node={node.id}
>
<!-- Primary row -->
@ -664,7 +671,7 @@
<div class="secondary" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
{#each exposedInputsOutputs as [input, output]}
<div class={`secondary-row expanded ${input !== undefined ? "input" : "output"}`}>
<TextLabel tooltip={(input !== undefined ? `${input.name}\n\n${input.description}` : `${output.name}\n\n${output.description}`).trim()}>
<TextLabel tooltipLabel={input !== undefined ? input.name : output.name} tooltipDescription={input !== undefined ? input.description : output.description}>
{input !== undefined ? input.name : output.name}
</TextLabel>
</div>
@ -679,11 +686,12 @@
viewBox="0 0 8 8"
class="connector primary-connector"
data-connector="input"
data-tooltip-label={dataTypeTooltipLabel(node.primaryInput)}
data-tooltip-description={`${validTypesText(node.primaryInput).trim()}\n\n${inputConnectedToText(node.primaryInput)}`}
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{#if node.primaryInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -698,11 +706,12 @@
viewBox="0 0 8 8"
class="connector"
data-connector="input"
data-tooltip-label={dataTypeTooltipLabel(secondary)}
data-tooltip-description={`${validTypesText(secondary).trim()}\n\n${inputConnectedToText(secondary)}`}
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -720,11 +729,12 @@
viewBox="0 0 8 8"
class="connector primary-connector"
data-connector="output"
data-tooltip-label={dataTypeTooltipLabel(node.primaryOutput)}
data-tooltip-description={`${outputConnectedToText(node.primaryOutput)}`}
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -738,11 +748,12 @@
viewBox="0 0 8 8"
class="connector"
data-connector="output"
data-tooltip-label={dataTypeTooltipLabel(secondary)}
data-tooltip-description={`${outputConnectedToText(secondary)}`}
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
>
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
@ -957,6 +968,7 @@
.imports-and-exports {
width: 100%;
height: 100%;
pointer-events: none;
position: absolute;
// Keeps the connectors above the wires
z-index: 1;
@ -1054,8 +1066,14 @@
.layers-and-nodes {
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
// Zero specificity with `:where()` to allow other rules to override `pointer-events`
:where(& > *) {
pointer-events: auto;
}
}
.layer,

View File

@ -26,10 +26,10 @@
<LayoutCol class={`widget-section ${className}`.trim()} {classes}>
<button class="header" class:expanded on:click|stopPropagation={() => (expanded = !expanded)} tabindex="0">
<div class="expand-arrow" />
<TextLabel tooltip={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
<TextLabel tooltipLabel={widgetData.name} tooltipDescription={widgetData.description} bold={true}>{widgetData.name}</TextLabel>
<IconButton
icon={widgetData.pinned ? "PinActive" : "PinInactive"}
tooltip={widgetData.pinned ? "Unpin this node so it's no longer shown here when nothing is selected" : "Pin this node so it's shown here when nothing is selected"}
tooltipDescription={widgetData.pinned ? "Unpin this node so it's no longer shown here when nothing is selected." : "Pin this node so it's shown here when nothing is selected."}
size={24}
action={(e) => {
editor.handle.setNodePinned(widgetData.id, !widgetData.pinned);
@ -39,7 +39,7 @@
/>
<IconButton
icon="Trash"
tooltip="Delete this node from the layer chain"
tooltipDescription="Delete this node from the layer chain."
size={24}
action={(e) => {
editor.handle.deleteNode(widgetData.id);
@ -50,7 +50,7 @@
<IconButton
icon={widgetData.visible ? "EyeVisible" : "EyeHidden"}
hoverIcon={widgetData.visible ? "EyeHide" : "EyeShow"}
tooltip={widgetData.visible ? "Hide this node" : "Show this node"}
tooltipDescription={widgetData.visible ? "Hide this node." : "Show this node."}
size={24}
action={(e) => {
editor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);

View File

@ -4,12 +4,14 @@
export let labels: string[];
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Callbacks
export let action: (index: number) => void;
</script>
<LayoutRow class="breadcrumb-trail-buttons" {tooltip}>
<LayoutRow class="breadcrumb-trail-buttons" {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
{#each labels as label, index}
<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />
{/each}

View File

@ -8,7 +8,9 @@
export let size: IconSize;
export let disabled = false;
export let active = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Callbacks
export let action: (e?: MouseEvent) => void;
@ -28,7 +30,9 @@
class:active
on:click={action}
{disabled}
title={tooltip}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
tabindex={active ? -1 : 0}
{...$$restProps}
>

View File

@ -8,7 +8,9 @@
export let image: string;
export let width: string | undefined;
export let height: string | undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Callbacks
export let action: (e?: MouseEvent) => void;
@ -17,7 +19,17 @@
.join(" ");
</script>
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-button ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
<img
src={IMAGE_BASE64_STRINGS[image]}
style:width
style:height
class={`image-button ${className} ${extraClasses}`.trim()}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
alt=""
on:click={action}
/>
<style lang="scss" global>
.image-button {

View File

@ -5,7 +5,9 @@
export let exposed: boolean;
export let dataType: FrontendGraphDataType;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Callbacks
export let action: (e?: MouseEvent) => void;
</script>
@ -16,7 +18,9 @@
style:--data-type-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-type-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
on:click={action}
title={tooltip}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
tabindex="-1"
>
{#if !exposed}

View File

@ -11,7 +11,9 @@
export let style: PopoverButtonStyle = "DropdownArrow";
export let menuDirection: MenuDirection = "Bottom";
export let icon: IconName | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let disabled = false;
export let popoverMinWidth = 1;
@ -27,9 +29,20 @@
</script>
<LayoutRow class="popover-button" classes={{ "has-icon": icon !== undefined, "direction-top": menuDirection === "Top" }}>
<IconButton class="dropdown-icon" classes={{ open }} {disabled} action={() => onClick()} icon={style || "DropdownArrow"} size={16} {tooltip} data-floating-menu-spawner />
<IconButton
class="dropdown-icon"
classes={{ open }}
{disabled}
action={() => onClick()}
icon={style || "DropdownArrow"}
size={16}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
data-floating-menu-spawner
/>
{#if icon !== undefined}
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltip} />
<IconLabel class="descriptive-icon" classes={{ open }} {disabled} {icon} {tooltipLabel} {tooltipDescription} {tooltipShortcut} />
{/if}
<FloatingMenu {open} on:open={({ detail }) => (open = detail)} minWidth={popoverMinWidth} type="Popover" direction={menuDirection || "Bottom"}>

View File

@ -20,7 +20,9 @@
export let minWidth = 0;
export let disabled = false;
export let narrow = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let menuListChildren: MenuListEntry[][] | undefined = undefined;
// Callbacks
@ -59,7 +61,9 @@
class:narrow
class:flush
style:min-width={minWidth > 0 ? `${minWidth}px` : undefined}
title={tooltip}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
data-emphasized={emphasized || undefined}
data-disabled={disabled || undefined}
data-text-button

View File

@ -11,7 +11,9 @@
export let checked = false;
export let disabled = false;
export let icon: IconName = "Checkmark";
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let forLabel: bigint | undefined = undefined;
let inputElement: HTMLInputElement | undefined;
@ -46,7 +48,15 @@
tabindex={disabled ? -1 : 0}
bind:this={inputElement}
/>
<label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}>
<label
class:disabled
class:checked
for={`checkbox-input-${id}`}
on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
>
<LayoutRow class="checkbox-box">
<IconLabel icon={displayIcon} />
</LayoutRow>

View File

@ -17,7 +17,9 @@
export let allowNone = false;
export let menuDirection: MenuDirection = "Bottom";
// export let allowTransparency = false; // TODO: Implement
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
$: outlineFactor = contrastingOutlineFactor(value, ["--color-1-nearblack", "--color-3-darkgray"], 0.01);
$: outlined = outlineFactor > 0.0001;
@ -26,7 +28,7 @@
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
</script>
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltip}>
<LayoutCol class="color-button" classes={{ open, disabled, narrow, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
<!-- {#if disabled && value instanceof Color && !value.none}
<TextLabel>sRGB</TextLabel>

View File

@ -16,7 +16,9 @@
export let styles: Record<string, string | number | undefined> = {};
export let value: Curve;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
const GRID_SIZE = 4;
@ -77,7 +79,7 @@
}
function handleManipulatorPointerDown(e: PointerEvent, i: number) {
// Delete an anchor with RMB or MMB
// Delete an anchor with right click or middle click
if (e.button > 0 && i > 0 && i < manipulatorsList.length - 1) {
draggedNodeIndex = undefined;
selectedNodeIndex = undefined;
@ -188,7 +190,7 @@
}
</script>
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltip}>
<LayoutRow class="curve-input" classes={{ disabled, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
<svg viewBox="0 0 1 1" on:pointermove={handlePointerMove} on:pointerup={handlePointerUp}>
{#each { length: GRID_SIZE - 1 } as _, i}
<path class="grid" d={`M 0 ${(i + 1) / GRID_SIZE} L 1 ${(i + 1) / GRID_SIZE}`} />

View File

@ -21,7 +21,9 @@
export let interactive = true;
export let disabled = false;
export let narrow = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let minWidth = 0;
export let maxWidth = 0;
@ -98,7 +100,9 @@
<LayoutRow
class="dropdown-box"
classes={{ disabled, open }}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
on:click={() => !disabled && (open = true)}
on:blur={unFocusDropdownBox}
tabindex={disabled ? -1 : 0}

View File

@ -25,7 +25,9 @@
export let disabled = false;
export let narrow = false;
export let textarea = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
export let hideContextMenu = false;
@ -74,7 +76,7 @@
</script>
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltip}>
<LayoutRow class={`field-input ${className}`} classes={{ disabled, narrow, ...classes }} style={styleName} {styles} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
{#if !textarea}
<input
type="text"

View File

@ -23,7 +23,9 @@
export let fontStyle: string;
export let isStyle = false;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
let open = false;
let entries: MenuListEntry[] = [];
@ -108,7 +110,9 @@
class="dropdown-box"
classes={{ disabled }}
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
tabindex={disabled ? -1 : 0}
on:click={toggleOpen}
data-floating-menu-spawner

View File

@ -18,7 +18,9 @@
// Label
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
// Disabled
export let disabled = false;
@ -688,7 +690,9 @@
{label}
{disabled}
{narrow}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
{styles}
hideContextMenu={true}
spellcheck={false}

View File

@ -27,7 +27,15 @@
<LayoutRow class="radio-input" classes={{ disabled, narrow, mixed }} styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}>
{#each entries as entry, index}
<button class:active={!mixed ? index === selectedIndex : undefined} on:click={() => handleEntryClick(entry)} title={entry.tooltip} tabindex={index === selectedIndex ? -1 : 0} {disabled}>
<button
class:active={!mixed ? index === selectedIndex : undefined}
on:click={() => handleEntryClick(entry)}
data-tooltip-label={entry.tooltipLabel}
data-tooltip-description={entry.tooltipDescription}
data-tooltip-shortcut={entry.tooltipShortcut}
tabindex={index === selectedIndex ? -1 : 0}
{disabled}
>
{#if entry.icon}
<IconLabel icon={entry.icon} />
{/if}

View File

@ -7,14 +7,16 @@
export let value: string;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
function setValue(newValue: ReferencePoint) {
dispatch("value", newValue);
}
</script>
<div class="reference-point-input" class:disabled title={tooltip}>
<div class="reference-point-input" class:disabled data-tooltip-label={tooltipLabel} data-tooltip-description={tooltipDescription} data-tooltip-shortcut={tooltipShortcut}>
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>

View File

@ -16,7 +16,9 @@
export let disabled = false;
export let activeMarkerIndex = 0 as number | undefined;
// export let disabled = false;
// export let tooltip: string | undefined = undefined;
// export let tooltipLabel: string | undefined = undefined;
// export let tooltipDescription: string | undefined = undefined;
// export let tooltipShortcut: string | undefined = undefined;
let markerTrack: LayoutRow | undefined = undefined;
let positionRestore: number | undefined = undefined;

View File

@ -7,7 +7,9 @@
export let value: string;
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let disabled = false;
let self: FieldInput | undefined;
@ -55,7 +57,9 @@
spellcheck={true}
{label}
{disabled}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
bind:this={self}
/>

View File

@ -7,7 +7,9 @@
// Label
export let label: string | undefined = undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let placeholder: string | undefined = undefined;
// Disabled
export let disabled = false;
@ -79,7 +81,9 @@
{label}
{disabled}
{narrow}
{tooltip}
{tooltipLabel}
{tooltipDescription}
{tooltipShortcut}
{placeholder}
bind:this={self}
/>

View File

@ -9,7 +9,9 @@
export let icon: IconName;
export let iconSizeOverride: number | undefined = undefined;
export let disabled = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
$: iconSizeClass = ((icon: IconName) => {
const iconData = ICONS[icon];
@ -26,7 +28,7 @@
.join(" ");
</script>
<LayoutRow class={`icon-label ${iconSizeClass} ${className} ${extraClasses}`.trim()} classes={{ disabled }} {tooltip}>
<LayoutRow class={`icon-label ${iconSizeClass} ${className} ${extraClasses}`.trim()} classes={{ disabled }} {tooltipLabel} {tooltipDescription} {tooltipShortcut}>
{@html ICON_SVG_STRINGS[icon] || "<22>"}
</LayoutRow>

View File

@ -6,14 +6,25 @@
export let url: string;
export let width: string | undefined;
export let height: string | undefined;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
$: extraClasses = Object.entries(classes)
.flatMap(([className, stateName]) => (stateName ? [className] : []))
.join(" ");
</script>
<img src={url} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" />
<img
src={url}
style:width
style:height
class={`image-label ${className} ${extraClasses}`.trim()}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
alt=""
/>
<style lang="scss" global>
.image-label {

View File

@ -14,7 +14,9 @@
export let tableAlign = false;
export let minWidth = "";
export let multiline = false;
export let tooltip: string | undefined = undefined;
export let tooltipLabel: string | undefined = undefined;
export let tooltipDescription: string | undefined = undefined;
export let tooltipShortcut: string | undefined = undefined;
export let forCheckbox: bigint | undefined = undefined;
$: extraClasses = Object.entries(classes)
@ -37,7 +39,9 @@
class:table-align={tableAlign}
style:min-width={minWidth || undefined}
style={`${styleName} ${extraStyles}`.trim() || undefined}
title={tooltip}
data-tooltip-label={tooltipLabel}
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut}
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
>
<slot />

View File

@ -45,7 +45,7 @@
$: displayKeyboardLockNotice = requiresLock && !$fullscreen.keyboardLocked;
function watchKeyboardLockInfoMessage(keyboardLockApiSupported: boolean): string {
const RESERVED = "This hotkey is reserved by the browser. ";
const RESERVED = "This keyboard shortcut is reserved by the browser.";
const USE_FULLSCREEN = "It is made available in fullscreen mode.";
const USE_SECURE_CTX = "It is made available in fullscreen mode when this website is served from a secure context (https or localhost).";
const SWITCH_BROWSER = "Use a Chromium-based browser (like Chrome or Edge) in fullscreen mode to directly use the shortcut.";
@ -118,7 +118,7 @@
</script>
{#if displayKeyboardLockNotice}
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltipDescription={keyboardLockInfoMessage} />
{:else}
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
@ -189,11 +189,13 @@
font-weight: 400;
text-align: center;
height: 16px;
box-sizing: border-box;
border: 1px solid;
border-radius: 4px;
border-color: var(--color-5-dullgray);
color: var(--color-e-nearwhite);
background: var(--color-3-darkgray);
color: var(--color-b-lightgray);
.icon-label {
fill: var(--color-b-lightgray);
}
.text-label {
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height,

View File

@ -2,21 +2,31 @@
import { getContext } from "svelte";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import type { DialogState } from "@graphite/state-providers/dialog";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
import Tooltip from "@graphite/components/floating-menus/Tooltip.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
const dialog = getContext<DialogState>("dialog");
const tooltip = getContext<TooltipState>("tooltip");
const appWindow = getContext<AppWindowState>("appWindow");
</script>
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": $appWindow.viewportHolePunch }}>
<TitleBar />
<Workspace />
<StatusBar />
{#if $dialog.visible}
<Dialog />
{/if}
{#if $tooltip.visible}
<Tooltip />
{/if}
</LayoutCol>
<style lang="scss" global>

View File

@ -11,13 +11,13 @@
const editor = getContext<Editor>("editor");
</script>
<LayoutRow class="window-button linux" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<LayoutRow class="window-button linux" tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<IconLabel icon="WindowButtonWinMinimize" />
</LayoutRow>
<LayoutRow class="window-button linux" tooltip={$appWindow.maximized ? "Unmaximize" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<LayoutRow class="window-button linux" tooltipLabel={$appWindow.maximized ? "Unmaximize" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
</LayoutRow>
<LayoutRow class="window-button linux" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
<LayoutRow class="window-button linux" tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
<IconLabel icon="WindowButtonWinClose" />
</LayoutRow>

View File

@ -8,8 +8,6 @@
const fullscreen = getContext<FullscreenState>("fullscreen");
$: requestFullscreenHotkeys = $fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
async function handleClick() {
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
else fullscreen.enterFullscreen();
@ -19,8 +17,9 @@
<LayoutRow
class="window-buttons-web"
on:click={handleClick}
tooltip={($fullscreen.windowFullscreen ? "Exit Fullscreen (F11)" : "Enter Fullscreen (F11)") +
(requestFullscreenHotkeys ? "\n\nThis provides access to hotkeys normally reserved by the browser" : "")}
tooltipLabel={$fullscreen.windowFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
tooltipDescription={$fullscreen.keyboardLockApiSupported ? "While fullscreen, keyboard shortcuts normally reserved by the browser become available." : ""}
tooltipShortcut="F11"
>
<IconLabel icon={$fullscreen.windowFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
</LayoutRow>

View File

@ -11,13 +11,13 @@
const editor = getContext<Editor>("editor");
</script>
<LayoutRow class="window-button windows" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<LayoutRow class="window-button windows" tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<IconLabel icon="WindowButtonWinMinimize" />
</LayoutRow>
<LayoutRow class="window-button windows" tooltip={$appWindow.maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<LayoutRow class="window-button windows" tooltipLabel={$appWindow.maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
</LayoutRow>
<LayoutRow class="window-button windows" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
<LayoutRow class="window-button windows" tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
<IconLabel icon="WindowButtonWinClose" />
</LayoutRow>

View File

@ -38,7 +38,7 @@
export let tabMinWidths = false;
export let tabCloseButtons = false;
export let tabLabels: { name: string; unsaved?: boolean; tooltip?: string }[];
export let tabLabels: { name: string; unsaved?: boolean; tooltipDescription?: string; tooltipShortcut?: string }[];
export let tabActiveIndex: number;
export let panelType: PanelType | undefined = undefined;
export let clickAction: ((index: number) => void) | undefined = undefined;
@ -118,7 +118,8 @@
<LayoutRow
class="tab"
classes={{ active: tabIndex === tabActiveIndex }}
tooltip={tabLabel.tooltip || undefined}
tooltipLabel={tabLabel.name}
tooltipDescription={tabLabel.tooltipDescription}
on:click={(e) => {
e.stopPropagation();
clickAction?.(tabIndex);

View File

@ -3,10 +3,8 @@
import type { Editor } from "@graphite/editor";
import type { OpenDocument } from "@graphite/messages";
import type { DialogState } from "@graphite/state-providers/dialog";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Panel from "@graphite/components/window/workspace/Panel.svelte";
@ -34,13 +32,12 @@
const unsaved = !doc.details.isSaved;
if (!editor.handle.inDevelopmentMode()) return { name, unsaved };
const tooltip = `Document ID: ${doc.id}`;
return { name, unsaved, tooltip };
const tooltipDescription = `Document ID: ${doc.id}`;
return { name, unsaved, tooltipDescription };
});
const editor = getContext<Editor>("editor");
const portfolio = getContext<PortfolioState>("portfolio");
const dialog = getContext<DialogState>("dialog");
function resizePanel(e: PointerEvent) {
const gutter = (e.target || undefined) as HTMLDivElement | undefined;
@ -175,9 +172,6 @@
</LayoutCol>
{/if}
</LayoutRow>
{#if $dialog.visible}
<Dialog />
{/if}
</LayoutRow>
<style lang="scss" global>

View File

@ -335,7 +335,7 @@ export type Key = { key: KeyRaw; label: string };
export type LayoutKeysGroup = Key[];
export type ActionKeys = { keys: LayoutKeysGroup };
export type MouseMotion = string;
export type MouseMotion = "None" | "Lmb" | "Rmb" | "Mmb" | "ScrollUp" | "ScrollDown" | "Drag" | "LmbDouble" | "LmbDrag" | "RmbDrag" | "RmbDouble" | "MmbDrag";
// Channels can have any range (0-1, 0-255, 0-100, 0-360) in the context they are being used in, these are just containers for the numbers
export type HSVA = { h: number; s: number; v: number; a: number };
@ -828,7 +828,7 @@ export class LayerPanelEntry {
alias!: string;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
debugLayerIdTooltip!: string | undefined;
inSelectedNetwork!: boolean;
@ -905,7 +905,13 @@ export class CheckboxInput extends WidgetProps {
icon!: IconName;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
forLabel!: bigint | undefined;
}
@ -943,7 +949,13 @@ export class ColorInput extends WidgetProps {
// allowTransparency!: boolean; // TODO: Implement
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export type FillChoice = Color | Gradient;
@ -1000,7 +1012,8 @@ export type MenuListEntry = MenuEntryCommon & {
value: string;
shortcutRequiresLock?: boolean;
disabled?: boolean;
tooltip?: string;
tooltipLabel?: string;
tooltipDescription?: string;
font?: URL;
};
@ -1021,7 +1034,13 @@ export class CurveInput extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class DropdownInput extends WidgetProps {
@ -1038,7 +1057,13 @@ export class DropdownInput extends WidgetProps {
narrow!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
// Styling
@ -1057,7 +1082,13 @@ export class FontInput extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class IconButton extends WidgetProps {
@ -1072,7 +1103,13 @@ export class IconButton extends WidgetProps {
active!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class IconLabel extends WidgetProps {
@ -1081,7 +1118,13 @@ export class IconLabel extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class ImageButton extends WidgetProps {
@ -1094,7 +1137,13 @@ export class ImageButton extends WidgetProps {
height!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class ImageLabel extends WidgetProps {
@ -1107,7 +1156,13 @@ export class ImageLabel extends WidgetProps {
height!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
@ -1119,7 +1174,13 @@ export class NumberInput extends WidgetProps {
label!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
// Disabled
@ -1179,7 +1240,13 @@ export class PopoverButton extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
// Body
popoverLayout!: LayoutGroup[];
@ -1193,7 +1260,9 @@ export type RadioEntryData = {
value?: string;
label?: string;
icon?: IconName;
tooltip?: string;
tooltipLabel?: string;
tooltipDescription?: string;
tooltipShortcut?: string;
// Callbacks
action?: () => void;
@ -1237,7 +1306,13 @@ export class TextAreaInput extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class ParameterExposeButton extends WidgetProps {
@ -1246,7 +1321,13 @@ export class ParameterExposeButton extends WidgetProps {
dataType!: FrontendGraphDataType;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class TextButton extends WidgetProps {
@ -1267,13 +1348,20 @@ export class TextButton extends WidgetProps {
narrow!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
menuListChildren!: MenuListEntry[][];
}
export type TextButtonWidget = {
tooltip?: string;
tooltipLabel?: string;
tooltipDescription?: string;
message?: string | object;
callback?: () => void;
props: {
@ -1284,7 +1372,8 @@ export type TextButtonWidget = {
flush?: boolean;
minWidth?: number;
disabled?: boolean;
tooltip?: string;
tooltipLabel?: string;
tooltipDescription?: string;
// Callbacks
// `action` is used via `IconButtonWidget.callback`
@ -1297,7 +1386,13 @@ export class BreadcrumbTrailButtons extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class TextInput extends WidgetProps {
@ -1314,7 +1409,13 @@ export class TextInput extends WidgetProps {
maxWidth!: number;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
export class TextLabel extends WidgetProps {
@ -1341,7 +1442,13 @@ export class TextLabel extends WidgetProps {
minWidth!: string;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
forCheckbox!: bigint | undefined;
}
@ -1354,7 +1461,13 @@ export class ReferencePointInput extends WidgetProps {
disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
tooltipLabel!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipDescription!: string | undefined;
@Transform(({ value }: { value: string }) => value || undefined)
tooltipShortcut!: string | undefined;
}
// WIDGET

View File

@ -0,0 +1,64 @@
import { writable } from "svelte/store";
const SHOW_TOOLTIP_DELAY_MS = 500;
export function createTooltipState() {
const { subscribe, update } = writable({
visible: false,
element: undefined as Element | undefined,
position: { x: 0, y: 0 },
});
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
// Listen for mouse movements onto tooltip-bearing HTML elements to track the future target of a tooltip
document.addEventListener("mouseover", (e) => {
const element = (e.target instanceof Element && e.target.closest("[data-tooltip-label], [data-tooltip-description], [data-tooltip-shortcut]")) || undefined;
update((state) => {
state.visible = false;
state.element = element;
return state;
});
});
// Listen for mouse movements to schedule and position the tooltip, or hide it immediately upon further movement
document.addEventListener("mousemove", (e) => {
// Hide the tooltip now that the cursor has moved
update((state) => {
state.visible = false;
return state;
});
// Before we schedule a new future tooltip appearance, we clear the existing one
if (tooltipTimeout) clearTimeout(tooltipTimeout);
// Schedule the tooltip to appear at this cursor position after a delay
tooltipTimeout = setTimeout(() => {
update((state) => {
if (state.element) {
state.visible = true;
state.position = { x: e.clientX, y: e.clientY };
}
return state;
});
}, SHOW_TOOLTIP_DELAY_MS);
});
document.addEventListener("mousedown", closeTooltip);
document.addEventListener("keydown", closeTooltip);
// Stop showing a tooltip if the user clicks or presses a key, and require the user to first move out of the element before it can re-appear
function closeTooltip() {
update((state) => {
state.visible = false;
state.element = undefined;
return state;
});
}
return {
subscribe,
};
}
export type TooltipState = ReturnType<typeof createTooltipState>;

View File

@ -215,7 +215,7 @@ impl ProtoNetwork {
fn check_ref(&self, ref_id: &NodeId, id: &NodeId) {
debug_assert!(
self.nodes.iter().any(|(check_id, _)| check_id == ref_id),
"Node id:{id} has a reference which uses node id:{ref_id} which doesn't exist in network {self:#?}"
"Node with ID {id} has a reference which uses the node with ID {ref_id} which doesn't exist in network {self:#?}"
);
}

View File

@ -19,7 +19,7 @@ pub struct VariantMetadata {
pub label: &'static str,
/// User-facing documentation text.
pub docstring: Option<&'static str>,
pub description: Option<&'static str>,
/// Name of icon to display in radio buttons and such.
pub icon: Option<&'static str>,

View File

@ -254,11 +254,9 @@ impl<'i> Convert<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
}
}
/// Node for uploading textures from CPU to GPU. This Is now deprecated and
/// we should use the Convert node in the future.
/// Uploads an raster texture from the CPU to the GPU. This Is now deprecated and the Convert node should be used in the future.
///
/// Accepts either individual rasters or tables of rasters and converts them
/// to GPU format using the WgpuExecutor's device and queue.
/// Accepts either individual raster data or a table of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue.
#[node_macro::node(category(""))]
pub async fn upload_texture<'a: 'n, T: Convert<Table<Raster<GPU>>, &'a WgpuExecutor>>(
_: impl Ctx,

View File

@ -141,7 +141,7 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
let vname = &variant.name;
let vname_str = variant.name.to_string();
let label = &variant.basic_item.label;
let docstring = match &variant.basic_item.description {
let description = match &variant.basic_item.description {
Some(s) => {
let s = s.trim();
quote! { Some(#s) }
@ -157,7 +157,7 @@ fn derive_enum(enum_attributes: &[Attribute], name: Ident, input: syn::DataEnum)
#name::#vname, #crate_name::choice_type::VariantMetadata {
name: #vname_str,
label: #label,
docstring: #docstring,
description: #description,
icon: #icon,
}
),

View File

@ -27,7 +27,7 @@ pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
///
/// `#[icon("tag"))]` sets the icon to use when a variant is shown in a menu or radio button.
///
/// Doc comments on a variant become tooltip text.
/// Doc comments on a variant become tooltip description text.
#[proc_macro_derive(ChoiceType, attributes(widget, menu_separator, label, icon))]
pub fn derive_choice_type(input_item: TokenStream) -> TokenStream {
derive_choice_type::derive_choice_type_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()).into()

View File

@ -77,7 +77,7 @@ fn string_split(
#[default("\\n")]
delimeter: String,
/// Whether to convert escape sequences found in the delimeter into their corresponding characters:
/// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash)
/// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash).
#[default(true)]
delimeter_escaping: bool,
) -> Vec<String> {

View File

@ -63,7 +63,7 @@ pub mod memo {
pub const IDENTIFIER: ProtoNodeIdentifier = ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
}
/// Caches the output of the last graph evaluation for introspection
/// Caches the output of the last graph evaluation for introspection.
#[derive(Default)]
pub struct MonitorNode<I, T, N> {
#[allow(clippy::type_complexity)]

View File

@ -31,17 +31,17 @@ impl ValueProvider for MathNodeContext {
}
}
/// Calculates a mathematical expression with input values "A" and "B"
/// Calculates a mathematical expression with input values "A" and "B".
#[node_macro::node(category("Math: Arithmetic"), properties("math_properties"))]
fn math<T: num_traits::float::Float>(
_: impl Ctx,
/// The value of "A" when calculating the expression
/// The value of "A" when calculating the expression.
#[implementations(f64, f32)]
operand_a: T,
/// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"
/// A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2".
#[default(A + B)]
expression: String,
/// The value of "B" when calculating the expression
/// The value of "B" when calculating the expression.
#[implementations(f64, f32)]
#[default(1.)]
operand_b: T,

View File

@ -296,7 +296,6 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
result_table
}
/// Constructs a raster image.
#[node_macro::node(category(""))]
pub fn image_value(_: impl Ctx, _primary: (), image: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image

View File

@ -189,8 +189,8 @@ async fn stroke<V, L: IntoF64Vec>(
/// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio.
#[default(4.)]
miter_limit: f64,
// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>
/// The order to paint the stroke on top of the fill, or the fill on top of the stroke.
/// <https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty>
paint_order: PaintOrder,
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
#[implementations(Vec<f64>, f64, String, Vec<f64>, f64, String)]