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