|
|
|
@@ -6,129 +6,61 @@ use super::widgets::menu_widgets::MenuLayout;
|
|
|
|
|
use crate::application::generate_uuid;
|
|
|
|
|
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
|
|
|
|
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
|
|
|
|
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
|
|
|
|
use crate::messages::prelude::*;
|
|
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
pub trait PropertyHolder {
|
|
|
|
|
fn properties(&self) -> Layout {
|
|
|
|
|
Layout::WidgetLayout(WidgetLayout::default())
|
|
|
|
|
}
|
|
|
|
|
#[remain::sorted]
|
|
|
|
|
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
#[repr(u8)]
|
|
|
|
|
pub enum LayoutTarget {
|
|
|
|
|
/// Contains the contents of the dialog, including the title and action buttons. Must be shown with the `FrontendMessage::DisplayDialog` message.
|
|
|
|
|
DialogDetails,
|
|
|
|
|
/// Contains the widgets located directly above the canvas to the right, for example the zoom in and out buttons.
|
|
|
|
|
DocumentBar,
|
|
|
|
|
/// Contains the dropdown for design / select / guide mode found on the top left of the canvas.
|
|
|
|
|
DocumentMode,
|
|
|
|
|
/// Options for opacity seen at the top of the Layers panel.
|
|
|
|
|
LayerTreeOptions,
|
|
|
|
|
/// The dropdown menu at the very top of the application: File, Edit, etc.
|
|
|
|
|
MenuBar,
|
|
|
|
|
/// Bar at the top of the node graph containing the location and the 'preview' and 'hide' buttons.
|
|
|
|
|
NodeGraphBar,
|
|
|
|
|
/// The bar at the top of the Properties panel containing the layer name and icon.
|
|
|
|
|
PropertiesOptions,
|
|
|
|
|
/// The body of the Properties panel containing many collapsable sections.
|
|
|
|
|
PropertiesSections,
|
|
|
|
|
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
|
|
|
|
|
ToolOptions,
|
|
|
|
|
/// The vertical buttons for all of the tools on the left of the canvas.
|
|
|
|
|
ToolShelf,
|
|
|
|
|
/// The color swatch for the working colors and a flip and reset button found at the bottom of the tool shelf.
|
|
|
|
|
WorkingColors,
|
|
|
|
|
|
|
|
|
|
fn register_properties(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
|
|
|
|
responses.add(LayoutMessage::SendLayout {
|
|
|
|
|
layout: self.properties(),
|
|
|
|
|
layout_target,
|
|
|
|
|
})
|
|
|
|
|
// KEEP THIS ENUM LAST
|
|
|
|
|
// This is a marker that is used to define an array that is used to hold widgets
|
|
|
|
|
#[remain::unsorted]
|
|
|
|
|
LayoutTargetLength,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// For use by structs that define a UI widget layout by implementing the layout() function belonging to this trait.
|
|
|
|
|
/// The send_layout() function can then be called by other code which is a part of the same struct so as to send the layout to the frontend.
|
|
|
|
|
pub trait LayoutHolder {
|
|
|
|
|
fn layout(&self) -> Layout;
|
|
|
|
|
|
|
|
|
|
fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
|
|
|
|
responses.add(LayoutMessage::SendLayout { layout: self.layout(), layout_target });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Wraps a choice of layout type. The chosen layout contains an arrangement of widgets mounted somewhere specific in the frontend.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
pub enum Layout {
|
|
|
|
|
WidgetLayout(WidgetLayout),
|
|
|
|
|
MenuLayout(MenuLayout),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The new value of the UI, sent as part of a diff.
|
|
|
|
|
///
|
|
|
|
|
/// An update can represent a single widget or an entire SubLayout, or just a single layout group.
|
|
|
|
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
pub enum DiffUpdate {
|
|
|
|
|
#[serde(rename = "subLayout")]
|
|
|
|
|
SubLayout(SubLayout),
|
|
|
|
|
#[serde(rename = "layoutGroup")]
|
|
|
|
|
LayoutGroup(LayoutGroup),
|
|
|
|
|
#[serde(rename = "widget")]
|
|
|
|
|
Widget(WidgetHolder),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A single change to part of the UI, containing the location of the change and the new value.
|
|
|
|
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
pub struct WidgetDiff {
|
|
|
|
|
/// A path to the change
|
|
|
|
|
/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.
|
|
|
|
|
/// An empty path [] shows that the entire panel has changed and is sent when the UI is first created.
|
|
|
|
|
#[serde(rename = "widgetPath")]
|
|
|
|
|
pub widget_path: Vec<usize>,
|
|
|
|
|
/// What the specified part of the UI has changed to.
|
|
|
|
|
#[serde(rename = "newValue")]
|
|
|
|
|
pub new_value: DiffUpdate,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DiffUpdate {
|
|
|
|
|
/// Append the shortcut to the tooltip where applicable
|
|
|
|
|
pub fn apply_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<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);
|
|
|
|
|
|
|
|
|
|
if let ActionKeys::Keys(_keys) = tooltip_shortcut {
|
|
|
|
|
if !shortcut_text.is_empty() {
|
|
|
|
|
if !tooltip.is_empty() {
|
|
|
|
|
tooltip.push(' ');
|
|
|
|
|
}
|
|
|
|
|
tooltip.push('(');
|
|
|
|
|
tooltip.push_str(&shortcut_text);
|
|
|
|
|
tooltip.push(')');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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::LayerReferenceInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
|
|
|
|
Widget::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
|
|
|
|
Widget::OptionalInput(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::IconLabel(_)
|
|
|
|
|
| Widget::InvisibleStandinInput(_)
|
|
|
|
|
| Widget::PivotAssist(_)
|
|
|
|
|
| Widget::RadioInput(_)
|
|
|
|
|
| Widget::Separator(_)
|
|
|
|
|
| Widget::SwatchPairInput(_)
|
|
|
|
|
| Widget::TextAreaInput(_)
|
|
|
|
|
| Widget::TextInput(_)
|
|
|
|
|
| Widget::TextLabel(_) => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
|
|
|
|
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
..
|
|
|
|
|
} = radio_entry_data
|
|
|
|
|
{
|
|
|
|
|
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|group| group.iter_mut()).for_each(convert_tooltip),
|
|
|
|
|
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(convert_tooltip),
|
|
|
|
|
Self::Widget(widget_holder) => convert_tooltip(widget_holder),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Layout {
|
|
|
|
|
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> MenuLayout {
|
|
|
|
|
if let Self::MenuLayout(mut menu) = self {
|
|
|
|
@@ -517,3 +449,101 @@ pub enum Widget {
|
|
|
|
|
TextInput(TextInput),
|
|
|
|
|
TextLabel(TextLabel),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A single change to part of the UI, containing the location of the change and the new value.
|
|
|
|
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
pub struct WidgetDiff {
|
|
|
|
|
/// A path to the change
|
|
|
|
|
/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.
|
|
|
|
|
/// An empty path [] shows that the entire panel has changed and is sent when the UI is first created.
|
|
|
|
|
#[serde(rename = "widgetPath")]
|
|
|
|
|
pub widget_path: Vec<usize>,
|
|
|
|
|
/// What the specified part of the UI has changed to.
|
|
|
|
|
#[serde(rename = "newValue")]
|
|
|
|
|
pub new_value: DiffUpdate,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The new value of the UI, sent as part of a diff.
|
|
|
|
|
///
|
|
|
|
|
/// An update can represent a single widget or an entire SubLayout, or just a single layout group.
|
|
|
|
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
|
|
|
|
pub enum DiffUpdate {
|
|
|
|
|
#[serde(rename = "subLayout")]
|
|
|
|
|
SubLayout(SubLayout),
|
|
|
|
|
#[serde(rename = "layoutGroup")]
|
|
|
|
|
LayoutGroup(LayoutGroup),
|
|
|
|
|
#[serde(rename = "widget")]
|
|
|
|
|
Widget(WidgetHolder),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl DiffUpdate {
|
|
|
|
|
/// Append the shortcut to the tooltip where applicable
|
|
|
|
|
pub fn apply_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<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);
|
|
|
|
|
|
|
|
|
|
if let ActionKeys::Keys(_keys) = tooltip_shortcut {
|
|
|
|
|
if !shortcut_text.is_empty() {
|
|
|
|
|
if !tooltip.is_empty() {
|
|
|
|
|
tooltip.push(' ');
|
|
|
|
|
}
|
|
|
|
|
tooltip.push('(');
|
|
|
|
|
tooltip.push_str(&shortcut_text);
|
|
|
|
|
tooltip.push(')');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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::LayerReferenceInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
|
|
|
|
Widget::NumberInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
|
|
|
|
Widget::OptionalInput(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::IconLabel(_)
|
|
|
|
|
| Widget::InvisibleStandinInput(_)
|
|
|
|
|
| Widget::PivotAssist(_)
|
|
|
|
|
| Widget::RadioInput(_)
|
|
|
|
|
| Widget::Separator(_)
|
|
|
|
|
| Widget::SwatchPairInput(_)
|
|
|
|
|
| Widget::TextAreaInput(_)
|
|
|
|
|
| Widget::TextInput(_)
|
|
|
|
|
| Widget::TextLabel(_) => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
|
|
|
|
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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),
|
|
|
|
|
..
|
|
|
|
|
} = radio_entry_data
|
|
|
|
|
{
|
|
|
|
|
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
Self::SubLayout(sub_layout) => sub_layout.iter_mut().flat_map(|group| group.iter_mut()).for_each(convert_tooltip),
|
|
|
|
|
Self::LayoutGroup(layout_group) => layout_group.iter_mut().for_each(convert_tooltip),
|
|
|
|
|
Self::Widget(widget_holder) => convert_tooltip(widget_holder),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|