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:
0HyperCube
2022-12-25 18:56:35 +00:00
committed by Keavon Chambers
parent 01931939ff
commit 694890642e
12 changed files with 464 additions and 264 deletions

View File

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

View File

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

View File

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

View File

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

View File

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