mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Diff simple layout changes to avoid excessive DOM replacements (#910)
* Add UI diffs to rust * Clean up some js * Fix lints * Fix test * Remove one unnecessary keyword * Rename to widget path * Rename new_val to new_value * Rename newVal to layoutGroup in createLayoutGroup * Extract get_widget_path to a function * Base skipping on the layout rather than the target * Rename to ResendActiveWidget * Switch info to trace * Add a link to the documentation about Object.assign * knitpick js changes * Add more comments to diff functions Co-authored-by: mfish33 <maxmfishernj@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
01931939ff
commit
694890642e
@@ -252,6 +252,7 @@ impl Dispatcher {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::application::Editor;
|
||||
use crate::messages::layout::utility_types::layout_widget::DiffUpdate;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::test_utils::EditorTestUtils;
|
||||
@@ -570,10 +571,12 @@ mod test {
|
||||
|
||||
for response in responses {
|
||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||
if let FrontendMessage::UpdateDialogDetails { layout_target: _, layout } = response {
|
||||
if let LayoutGroup::Row { widgets } = &layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
if let FrontendMessage::UpdateDialogDetails { layout_target: _, diff } = response {
|
||||
if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
|
||||
if let LayoutGroup::Row { widgets } = &sub_layout[0] {
|
||||
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
|
||||
print_problem_to_terminal_on_failure(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon};
|
||||
use crate::messages::layout::utility_types::layout_widget::SubLayout;
|
||||
use crate::messages::layout::utility_types::layout_widget::WidgetDiff;
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuBarEntry;
|
||||
use crate::messages::portfolio::document::node_graph::{FrontendNode, FrontendNodeLink, FrontendNodeType};
|
||||
@@ -143,7 +143,7 @@ pub enum FrontendMessage {
|
||||
UpdateDialogDetails {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateDocumentArtboards {
|
||||
svg: String,
|
||||
@@ -154,7 +154,7 @@ pub enum FrontendMessage {
|
||||
UpdateDocumentBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateDocumentLayerDetails {
|
||||
data: LayerPanelEntry,
|
||||
@@ -170,7 +170,7 @@ pub enum FrontendMessage {
|
||||
UpdateDocumentModeLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateDocumentOverlays {
|
||||
svg: String,
|
||||
@@ -208,7 +208,7 @@ pub enum FrontendMessage {
|
||||
UpdateLayerTreeOptionsLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateMenuBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
@@ -225,7 +225,7 @@ pub enum FrontendMessage {
|
||||
UpdateNodeGraphBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateNodeGraphSelection {
|
||||
selected: Vec<NodeId>,
|
||||
@@ -244,26 +244,26 @@ pub enum FrontendMessage {
|
||||
UpdatePropertyPanelOptionsLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdatePropertyPanelSectionsLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateToolOptionsLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateToolShelfLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
UpdateWorkingColorsLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
layout: SubLayout,
|
||||
diff: Vec<WidgetDiff>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
#[impl_message(Message, Layout)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum LayoutMessage {
|
||||
RefreshLayout { layout_target: LayoutTarget },
|
||||
ResendActiveWidget { layout_target: LayoutTarget, dirty_id: u64 },
|
||||
SendLayout { layout: Layout, layout_target: LayoutTarget },
|
||||
UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value },
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::utility_types::layout_widget::{LayoutGroup, WidgetDiff, WidgetHolder};
|
||||
use super::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::layout::utility_types::layout_widget::Layout;
|
||||
use crate::messages::layout::utility_types::layout_widget::Widget;
|
||||
use crate::messages::layout::utility_types::layout_widget::{DiffUpdate, Widget};
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, WidgetLayout};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::color::Color;
|
||||
@@ -16,6 +17,32 @@ pub struct LayoutMessageHandler {
|
||||
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
||||
}
|
||||
|
||||
impl LayoutMessageHandler {
|
||||
/// Get the widget path for the widget with the specified id
|
||||
fn get_widget_path(widget_layout: &WidgetLayout, id: u64) -> Option<(&WidgetHolder, Vec<usize>)> {
|
||||
let mut stack = widget_layout.layout.iter().enumerate().map(|(index, val)| (vec![index], val)).collect::<Vec<_>>();
|
||||
while let Some((mut widget_path, group)) = stack.pop() {
|
||||
match group {
|
||||
// Check if any of the widgets in the current column or row have the correct id
|
||||
LayoutGroup::Column { widgets } | LayoutGroup::Row { widgets } => {
|
||||
for (index, widget) in widgets.iter().enumerate() {
|
||||
// Return if this is the correct ID
|
||||
if widget.widget_id == id {
|
||||
widget_path.push(index);
|
||||
return Some((widget, widget_path));
|
||||
}
|
||||
}
|
||||
}
|
||||
// A section contains more LayoutGroups which we add to the stack.
|
||||
LayoutGroup::Section { layout, .. } => {
|
||||
stack.extend(layout.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val)));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(&mut self, message: LayoutMessage, data: F, responses: &mut std::collections::VecDeque<Message>) {
|
||||
@@ -24,14 +51,22 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||
use LayoutMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
RefreshLayout { layout_target } => {
|
||||
self.send_layout(layout_target, responses, &action_input_mapping);
|
||||
}
|
||||
SendLayout { layout, layout_target } => {
|
||||
self.layouts[layout_target as usize] = layout;
|
||||
|
||||
self.send_layout(layout_target, responses, &action_input_mapping);
|
||||
ResendActiveWidget { layout_target, dirty_id } => {
|
||||
// Find the updated diff based on the specified layout target
|
||||
let Some(diff) = (match &self.layouts[layout_target as usize] {
|
||||
Layout::MenuLayout(_) => return,
|
||||
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, dirty_id).map(|(widget, widget_path)| {
|
||||
// Create a widget update diff for the relevant id
|
||||
let new_value = DiffUpdate::Widget(widget.clone());
|
||||
WidgetDiff { widget_path, new_value }
|
||||
}),
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
// Resend that diff
|
||||
self.send_diff(vec![diff], layout_target, responses, &action_input_mapping);
|
||||
}
|
||||
SendLayout { layout, layout_target } => self.send_layout(layout_target, layout, responses, &action_input_mapping),
|
||||
UpdateLayout { layout_target, widget_id, value } => {
|
||||
// Look up the layout
|
||||
let layout = if let Some(layout) = self.layouts.get_mut(layout_target as usize) {
|
||||
@@ -84,7 +119,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||
Some(None)
|
||||
}
|
||||
})()
|
||||
.unwrap_or_else(|| panic!("ColorInput update was not able to be parsed with color data: {:?}", color_input));
|
||||
.unwrap_or_else(|| panic!("ColorInput update was not able to be parsed with color data: {color_input:?}"));
|
||||
color_input.value = parsed_color;
|
||||
let callback_message = (color_input.on_update.callback)(color_input);
|
||||
responses.push_back(callback_message);
|
||||
@@ -197,7 +232,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||
}
|
||||
Widget::TextLabel(_) => {}
|
||||
};
|
||||
responses.push_back(RefreshLayout { layout_target }.into());
|
||||
responses.push_back(ResendActiveWidget { layout_target, dirty_id: widget_id }.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,55 +243,57 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||
}
|
||||
|
||||
impl LayoutMessageHandler {
|
||||
/// Diff the update and send to the frontend where necessary
|
||||
fn send_layout(&mut self, layout_target: LayoutTarget, new_layout: Layout, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
// We don't diff the menu bar layout yet.
|
||||
if matches!(new_layout, Layout::MenuLayout(_)) {
|
||||
// Skip update if the same
|
||||
if self.layouts[layout_target as usize] == new_layout {
|
||||
return;
|
||||
}
|
||||
// Update the backend storage
|
||||
self.layouts[layout_target as usize] = new_layout;
|
||||
// Update the UI
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateMenuBarLayout {
|
||||
layout_target,
|
||||
layout: self.layouts[layout_target as usize].clone().unwrap_menu_layout(action_input_mapping).layout,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut widget_diffs = Vec::new();
|
||||
self.layouts[layout_target as usize].diff(new_layout, &mut Vec::new(), &mut widget_diffs);
|
||||
// Skip sending if no diff.
|
||||
if widget_diffs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.send_diff(widget_diffs, layout_target, responses, action_input_mapping);
|
||||
}
|
||||
|
||||
/// Send a diff to the frontend based on the layout target.
|
||||
#[remain::check]
|
||||
fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
let layout = &self.layouts[layout_target as usize];
|
||||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_shortcut(action_input_mapping));
|
||||
|
||||
trace!("{layout_target:?} diff {diff:#?}");
|
||||
|
||||
#[remain::sorted]
|
||||
let message = match layout_target {
|
||||
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_menu_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||
},
|
||||
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { layout_target, diff },
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
||||
|
||||
#[remain::unsorted]
|
||||
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
|
||||
|
||||
@@ -34,85 +34,113 @@ pub enum Layout {
|
||||
MenuLayout(MenuLayout),
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> WidgetLayout {
|
||||
if let Layout::WidgetLayout(mut widget_layout) = self {
|
||||
// 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);
|
||||
/// 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)]
|
||||
pub enum DiffUpdate {
|
||||
#[serde(rename = "subLayout")]
|
||||
SubLayout(SubLayout),
|
||||
#[serde(rename = "layoutGroup")]
|
||||
LayoutGroup(LayoutGroup),
|
||||
#[serde(rename = "widget")]
|
||||
Widget(WidgetHolder),
|
||||
}
|
||||
|
||||
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(')');
|
||||
/// A single change to part of the UI, containing the location of the change and the new value.
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
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);
|
||||
}
|
||||
|
||||
// Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip
|
||||
for widget_holder in &mut widget_layout.iter_mut() {
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
widget_layout
|
||||
} else {
|
||||
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
|
||||
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 Layout::MenuLayout(mut menu_layout) = self {
|
||||
for menu_column in &mut menu_layout.layout {
|
||||
menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
|
||||
}
|
||||
|
||||
menu_layout
|
||||
if let Self::MenuLayout(mut menu) = self {
|
||||
menu.layout
|
||||
.iter_mut()
|
||||
.for_each(|menu_column| menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping));
|
||||
menu
|
||||
} else {
|
||||
panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self)
|
||||
panic!("Called unwrap_menu_layout on a widget layout");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +157,24 @@ impl Layout {
|
||||
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Diffing updates self (where self is old) based on new, updating the list of modifications as it does so.
|
||||
pub fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
||||
match (self, new) {
|
||||
// Simply diff the internal layout
|
||||
(Self::WidgetLayout(current), Self::WidgetLayout(new)) => current.diff(new, widget_path, widget_diffs),
|
||||
(current, Self::WidgetLayout(widget_layout)) => {
|
||||
// Upate current to the new value
|
||||
*current = Self::WidgetLayout(widget_layout.clone());
|
||||
|
||||
// Push an update sublayout value
|
||||
let new_value = DiffUpdate::SubLayout(widget_layout.layout);
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
}
|
||||
(_, Self::MenuLayout(_)) => panic!("Cannot diff menu layout"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
@@ -160,6 +206,30 @@ impl WidgetLayout {
|
||||
current_slice: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Diffing updates self (where self is old) based on new, updating the list of modifications as it does so.
|
||||
pub fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
||||
// Check if the length of items is different
|
||||
// TODO: Diff insersion and deletion of items
|
||||
if self.layout.len() != new.layout.len() {
|
||||
// Update the layout to the new layout
|
||||
self.layout = new.layout.clone();
|
||||
|
||||
// Push an update sublayout to the diff
|
||||
let new = DiffUpdate::SubLayout(new.layout);
|
||||
widget_diffs.push(WidgetDiff {
|
||||
widget_path: widget_path.to_vec(),
|
||||
new_value: new,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Diff all of the children
|
||||
for (index, (current_child, new_child)) in self.layout.iter_mut().zip(new.layout.into_iter()).enumerate() {
|
||||
widget_path.push(index);
|
||||
current_child.diff(new_child, widget_path, widget_diffs);
|
||||
widget_path.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -296,6 +366,73 @@ impl LayoutGroup {
|
||||
Self::Row { widgets }
|
||||
}
|
||||
}
|
||||
|
||||
/// Diffing updates self (where self is old) based on new, updating the list of modifications as it does so.
|
||||
pub fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
||||
let is_column = matches!(new, Self::Column { .. });
|
||||
match (self, new) {
|
||||
(Self::Column { widgets: current_widgets }, Self::Column { widgets: new_widgets }) | (Self::Row { widgets: current_widgets }, Self::Row { widgets: new_widgets }) => {
|
||||
// If the lengths are different then resend the entire panel
|
||||
// TODO: Diff insersion and deletion of items
|
||||
if current_widgets.len() != new_widgets.len() {
|
||||
// Update to the new value
|
||||
*current_widgets = new_widgets.clone();
|
||||
|
||||
// Push back a LayoutGroup update to the diff
|
||||
let new_value = DiffUpdate::LayoutGroup(if is_column { Self::Column { widgets: new_widgets } } else { Self::Row { widgets: new_widgets } });
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
return;
|
||||
}
|
||||
// Diff all of the children
|
||||
for (index, (current_child, new_child)) in current_widgets.iter_mut().zip(new_widgets.into_iter()).enumerate() {
|
||||
widget_path.push(index);
|
||||
current_child.diff(new_child, widget_path, widget_diffs);
|
||||
widget_path.pop();
|
||||
}
|
||||
}
|
||||
(
|
||||
Self::Section {
|
||||
name: current_name,
|
||||
layout: current_layout,
|
||||
},
|
||||
Self::Section { name: new_name, layout: new_layout },
|
||||
) => {
|
||||
// If the lengths are different then resend the entire panel
|
||||
// TODO: Diff insersion and deletion of items
|
||||
if *current_name != new_name || current_layout.len() != new_layout.len() {
|
||||
// Update self to reflect new changes
|
||||
*current_name = new_name.clone();
|
||||
*current_layout = new_layout.clone();
|
||||
|
||||
// Push an update layout group to the diff
|
||||
let new_value = DiffUpdate::LayoutGroup(Self::Section { name: new_name, layout: new_layout });
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
return;
|
||||
}
|
||||
// Diff all of the children
|
||||
for (index, (current_child, new_child)) in current_layout.iter_mut().zip(new_layout.into_iter()).enumerate() {
|
||||
widget_path.push(index);
|
||||
current_child.diff(new_child, widget_path, widget_diffs);
|
||||
widget_path.pop();
|
||||
}
|
||||
}
|
||||
(current, new) => {
|
||||
*current = new.clone();
|
||||
let new_value = DiffUpdate::LayoutGroup(new);
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
|
||||
WidgetIterMut {
|
||||
stack: vec![self],
|
||||
current_slice: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -334,6 +471,22 @@ impl WidgetHolder {
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
/// Diffing updates self (where self is old) based on new, updating the list of modifications as it does so.
|
||||
pub fn diff(&mut self, new: Self, widget_path: &mut [usize], widget_diffs: &mut Vec<WidgetDiff>) {
|
||||
// If there have been changes to the acutal widget (not just the id)
|
||||
if self.widget != new.widget {
|
||||
// We should update to the new widget value as well as a new widget id
|
||||
*self = new.clone();
|
||||
|
||||
// Push a widget update to the diff
|
||||
let new_value = DiffUpdate::Widget(new);
|
||||
let widget_path = widget_path.to_vec();
|
||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||
} else {
|
||||
// Required to update the callback function, which the PartialEq check above skips
|
||||
self.widget = new.widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
Reference in New Issue
Block a user