mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 02:18:12 +08:00
Fix panel docking bugs and polish its behavior (#4087)
* Fix panel docking bugs and polish its behavior * Fix bug
This commit is contained in:
@@ -29,6 +29,12 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
use crate::messages::viewport::{Position, ToPhysical};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
// Discard detached canvas after a panel reorganization remounts the DOM
|
||||
if self.canvas.as_ref().is_some_and(|canvas| !canvas.is_connected()) {
|
||||
self.canvas = None;
|
||||
self.context = None;
|
||||
}
|
||||
|
||||
let canvas = match &self.canvas {
|
||||
Some(canvas) => canvas,
|
||||
None => {
|
||||
|
||||
@@ -195,10 +195,6 @@ pub enum PortfolioMessage {
|
||||
UpdateOpenDocumentsList,
|
||||
UpdateWorkspacePanelLayout,
|
||||
ResetWorkspaceLayout,
|
||||
ResetPanelGroupSizes {
|
||||
/// Path of child indices from the root to the split node whose children's sizes should be reset to defaults.
|
||||
split_path: Vec<usize>,
|
||||
},
|
||||
SetPanelGroupSizes {
|
||||
/// Path of child indices from the root to the split node whose children's sizes are being set.
|
||||
split_path: Vec<usize>,
|
||||
|
||||
@@ -453,6 +453,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
if let Some(layout) = state.workspace_layout {
|
||||
self.workspace_panel_layout = layout;
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
|
||||
// Refill panels whose content was lost when the layout load remounted their frontend components
|
||||
for group_id in self.workspace_panel_layout.root.all_group_ids() {
|
||||
if let Some(panel_type) = self.workspace_panel_layout.panel_group(group_id).and_then(|g| g.active_panel_type()) {
|
||||
self.refresh_panel_content(panel_type, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let PersistedState {
|
||||
@@ -1330,13 +1337,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
Self::destroy_panel_layouts(target_active, responses);
|
||||
}
|
||||
|
||||
// Preserve the source panel's visual weight at its new location
|
||||
let source_slot_size = self.workspace_panel_layout.find_source_slot_size(&tabs);
|
||||
|
||||
// Remove the dragged tabs from their current panel groups (without pruning, so the target group survives)
|
||||
for &panel_type in &tabs {
|
||||
self.remove_panel_from_layout(panel_type);
|
||||
}
|
||||
|
||||
// Create the new panel group adjacent to the target, then prune empty groups
|
||||
let Some(new_id) = self.workspace_panel_layout.split_panel_group(target_group, direction, tabs.clone(), active_tab_index) else {
|
||||
let Some(new_id) = self.workspace_panel_layout.split_panel_group(target_group, direction, tabs.clone(), active_tab_index, source_slot_size) else {
|
||||
log::error!("Failed to insert split adjacent to panel group {target_group:?}");
|
||||
return;
|
||||
};
|
||||
@@ -1611,20 +1621,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
}
|
||||
PortfolioMessage::ResetPanelGroupSizes { split_path } => {
|
||||
// Walk the tree to the target split node using the path
|
||||
let mut node = &mut self.workspace_panel_layout.root;
|
||||
for &index in &split_path {
|
||||
let PanelLayoutSubdivision::Split { children } = node else { return };
|
||||
let Some(child) = children.get_mut(index) else { return };
|
||||
node = &mut child.subdivision;
|
||||
}
|
||||
|
||||
// Recalculate default sizes for this split node
|
||||
node.recalculate_default_sizes();
|
||||
|
||||
responses.add(PortfolioMessage::UpdateWorkspacePanelLayout);
|
||||
}
|
||||
PortfolioMessage::SetPanelGroupSizes { split_path, sizes } => {
|
||||
// Walk the tree to the target split node using the path
|
||||
let mut node = &mut self.workspace_panel_layout.root;
|
||||
@@ -1977,7 +1973,12 @@ impl PortfolioMessageHandler {
|
||||
PanelType::Data => {
|
||||
// The Data panel's content is populated automatically as a side effect of the graph run completing, so there's nothing to do here
|
||||
}
|
||||
PanelType::Document | PanelType::Welcome => {}
|
||||
PanelType::Document | PanelType::Welcome => {
|
||||
// Re-send the welcome screen buttons layout to repopulate after a remount
|
||||
if self.document_ids.is_empty() {
|
||||
responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::text::{Font, FontCache};
|
||||
|
||||
/// Proportional share (0-1) for the document panel's side when splitting adjacent to non-document panels.
|
||||
const DOCUMENT_PANEL_SHARE: f64 = 0.8;
|
||||
/// Proportional share for each side when neither (or both) contain the document panel.
|
||||
const EQUAL_PANEL_SHARE: f64 = 0.5;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CachedData {
|
||||
pub font_cache: FontCache,
|
||||
@@ -244,25 +249,54 @@ impl WorkspacePanelLayout {
|
||||
/// The direction determines where the new group goes relative to the target.
|
||||
/// Left/Right creates a horizontal (row) split, Top/Bottom creates a vertical (column) split.
|
||||
/// Returns the ID of the newly created panel group, or `None` if insertion failed.
|
||||
pub fn split_panel_group(&mut self, target_group_id: PanelGroupId, direction: DockingSplitDirection, tabs: Vec<PanelType>, active_tab_index: usize) -> Option<PanelGroupId> {
|
||||
///
|
||||
/// `source_slot_size` overrides the new panel's size (for moves where the old slot will be pruned).
|
||||
/// If `None`, target's slot is split in the default ratio.
|
||||
pub fn split_panel_group(
|
||||
&mut self,
|
||||
target_group_id: PanelGroupId,
|
||||
direction: DockingSplitDirection,
|
||||
tabs: Vec<PanelType>,
|
||||
active_tab_index: usize,
|
||||
source_slot_size: Option<f64>,
|
||||
) -> Option<PanelGroupId> {
|
||||
let new_id = self.next_id();
|
||||
let new_group = SplitChild {
|
||||
subdivision: PanelLayoutSubdivision::PanelGroup {
|
||||
id: new_id,
|
||||
state: PanelGroupState { tabs, active_tab_index },
|
||||
},
|
||||
size: 50.,
|
||||
size: source_slot_size.unwrap_or(EQUAL_PANEL_SHARE),
|
||||
};
|
||||
|
||||
let insert_before = matches!(direction, DockingSplitDirection::Left | DockingSplitDirection::Top);
|
||||
let needs_horizontal = matches!(direction, DockingSplitDirection::Left | DockingSplitDirection::Right);
|
||||
|
||||
self.root.insert_split_adjacent(target_group_id, new_group, insert_before, needs_horizontal, 0).then_some(new_id)
|
||||
self.root
|
||||
.insert_split_adjacent(target_group_id, new_group, insert_before, needs_horizontal, 0, source_slot_size)
|
||||
.then_some(new_id)
|
||||
}
|
||||
|
||||
/// Find the slot size of the panel group whose entire content is exactly the given tabs.
|
||||
/// Returns `None` if the tabs span multiple groups or don't fill their group exactly.
|
||||
pub fn find_source_slot_size(&self, tabs: &[PanelType]) -> Option<f64> {
|
||||
if tabs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let group_id = self.find_panel(tabs[0])?;
|
||||
if !tabs.iter().all(|&t| self.find_panel(t) == Some(group_id)) {
|
||||
return None;
|
||||
}
|
||||
let group = self.panel_group(group_id)?;
|
||||
if group.tabs.len() != tabs.len() {
|
||||
return None;
|
||||
}
|
||||
self.root.find_slot_size_by_group_id(group_id)
|
||||
}
|
||||
|
||||
/// Recalculate the default sizes for all splits in the tree based on document panel proximity.
|
||||
pub fn recalculate_default_sizes(&mut self) {
|
||||
self.root.recalculate_default_sizes();
|
||||
self.root.recalculate_default_sizes_recursive();
|
||||
}
|
||||
|
||||
/// Remember which panel group and tab index a panel was in before removal, so it can be restored there later.
|
||||
@@ -311,10 +345,10 @@ impl WorkspacePanelLayout {
|
||||
},
|
||||
},
|
||||
size: match panel_type {
|
||||
PanelType::Data => 30.,
|
||||
PanelType::Properties => 45.,
|
||||
PanelType::Layers => 55.,
|
||||
_ => 50.,
|
||||
PanelType::Data => 0.3,
|
||||
PanelType::Properties => 0.45,
|
||||
PanelType::Layers => 0.55,
|
||||
_ => EQUAL_PANEL_SHARE,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -330,7 +364,10 @@ impl WorkspacePanelLayout {
|
||||
if !matches!(&self.root, PanelLayoutSubdivision::Split { .. }) {
|
||||
let old_root = std::mem::replace(&mut self.root, PanelLayoutSubdivision::Split { children: vec![] });
|
||||
if let PanelLayoutSubdivision::Split { children } = &mut self.root {
|
||||
children.push(SplitChild { subdivision: old_root, size: 80. });
|
||||
children.push(SplitChild {
|
||||
subdivision: old_root,
|
||||
size: DOCUMENT_PANEL_SHARE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +377,7 @@ impl WorkspacePanelLayout {
|
||||
while root_children.len() <= root_child_index {
|
||||
root_children.push(SplitChild {
|
||||
subdivision: PanelLayoutSubdivision::Split { children: vec![] },
|
||||
size: 20.,
|
||||
size: (1. - DOCUMENT_PANEL_SHARE),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -351,7 +388,7 @@ impl WorkspacePanelLayout {
|
||||
if let PanelLayoutSubdivision::Split { children } = target {
|
||||
children.push(SplitChild {
|
||||
subdivision: old_subdivision,
|
||||
size: 50.,
|
||||
size: EQUAL_PANEL_SHARE,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -386,10 +423,10 @@ impl Default for WorkspacePanelLayout {
|
||||
active_tab_index: 0,
|
||||
},
|
||||
},
|
||||
size: 100.,
|
||||
size: 1.,
|
||||
}],
|
||||
},
|
||||
size: 80.,
|
||||
size: DOCUMENT_PANEL_SHARE,
|
||||
},
|
||||
SplitChild {
|
||||
subdivision: PanelLayoutSubdivision::Split {
|
||||
@@ -402,7 +439,7 @@ impl Default for WorkspacePanelLayout {
|
||||
active_tab_index: 0,
|
||||
},
|
||||
},
|
||||
size: 50.,
|
||||
size: EQUAL_PANEL_SHARE,
|
||||
},
|
||||
SplitChild {
|
||||
subdivision: PanelLayoutSubdivision::PanelGroup {
|
||||
@@ -412,11 +449,11 @@ impl Default for WorkspacePanelLayout {
|
||||
active_tab_index: 0,
|
||||
},
|
||||
},
|
||||
size: 50.,
|
||||
size: EQUAL_PANEL_SHARE,
|
||||
},
|
||||
],
|
||||
},
|
||||
size: 20.,
|
||||
size: (1. - DOCUMENT_PANEL_SHARE),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -427,6 +464,15 @@ impl Default for WorkspacePanelLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/// The share of the slot that should go to the old side when splitting it with a new side.
|
||||
fn document_split_share(old_side: &PanelLayoutSubdivision, new_side: &PanelLayoutSubdivision) -> f64 {
|
||||
match (old_side.contains_document(), new_side.contains_document()) {
|
||||
(true, false) => DOCUMENT_PANEL_SHARE,
|
||||
(false, true) => 1. - DOCUMENT_PANEL_SHARE,
|
||||
_ => EQUAL_PANEL_SHARE,
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelLayoutSubdivision {
|
||||
/// Find the panel group state for a given ID.
|
||||
pub fn find_group(&self, target_id: PanelGroupId) -> Option<&PanelGroupState> {
|
||||
@@ -463,9 +509,10 @@ impl PanelLayoutSubdivision {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove empty panel groups and collapse unnecessary nesting.
|
||||
/// Does NOT collapse single-child splits into their child, as that would change subdivision depths
|
||||
/// and break the direction-by-depth alternation system.
|
||||
/// Remove empty groups/splits and flatten single-child `Split`-in-`Split` nesting (which docking sequences can create).
|
||||
///
|
||||
/// Flattening preserves depth parity (and therefore direction). `PanelGroup`-only single-child splits are left
|
||||
/// alone since collapsing would change the panel's depth and alter future wrap orientation.
|
||||
pub fn prune(&mut self) {
|
||||
let PanelLayoutSubdivision::Split { children } = self else { return };
|
||||
|
||||
@@ -477,6 +524,54 @@ impl PanelLayoutSubdivision {
|
||||
|
||||
// Remove empty splits (splits that lost all their children after pruning)
|
||||
children.retain(|child| !matches!(&child.subdivision, PanelLayoutSubdivision::Split { children } if children.is_empty()));
|
||||
|
||||
// Flatten single-child `Split`-in-`Split` nesting, rescaling sizes to preserve visual proportions
|
||||
let mut i = 0;
|
||||
while i < children.len() {
|
||||
// Must be a `Split`...
|
||||
let PanelLayoutSubdivision::Split { children: outer } = &children[i].subdivision else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
// ...with exactly one child...
|
||||
let [only_child] = outer.as_slice() else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
// ...that is itself a `Split`
|
||||
let PanelLayoutSubdivision::Split { .. } = &only_child.subdivision else {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
// Remove the redundant wrapper
|
||||
let removed = children.remove(i);
|
||||
let outer_size = removed.size;
|
||||
|
||||
// Extract the inner grandchildren
|
||||
let PanelLayoutSubdivision::Split { children: mut outer_children } = removed.subdivision else {
|
||||
continue;
|
||||
};
|
||||
let Some(inner_split) = outer_children.pop() else { continue };
|
||||
let PanelLayoutSubdivision::Split { children: inner_children } = inner_split.subdivision else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Splice grandchildren in at the same position, scaling their sizes to fill the removed slot
|
||||
let inner_total: f64 = inner_children.iter().map(|c| c.size).sum();
|
||||
for (offset, mut grandchild) in inner_children.into_iter().enumerate() {
|
||||
grandchild.size = if inner_total > 0. { grandchild.size / inner_total * outer_size } else { outer_size };
|
||||
children.insert(i + offset, grandchild);
|
||||
}
|
||||
}
|
||||
|
||||
// Renormalize to sum=1 since dock/prune cycles can compound shrinkage
|
||||
let total: f64 = children.iter().map(|c| c.size).sum();
|
||||
if total > 0. && (total - 1.).abs() > 0.001 {
|
||||
for child in children.iter_mut() {
|
||||
child.size /= total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all non-document/non-welcome tabs from panel groups, leaving only document-related panels.
|
||||
@@ -503,7 +598,9 @@ impl PanelLayoutSubdivision {
|
||||
/// Inserts a new split child adjacent to a target panel group and returns whether the insertion was successful.
|
||||
/// Recurses to the deepest split closest to the target that matches the requested split direction.
|
||||
/// If the target is a direct child of a mismatched-direction split, this wraps it in a new sub-split.
|
||||
pub fn insert_split_adjacent(&mut self, target_id: PanelGroupId, new_child: SplitChild, insert_before: bool, needs_horizontal: bool, depth: usize) -> bool {
|
||||
///
|
||||
/// `source_slot_size` preserves the moved panel's visual weight. If `None`, uses the default split ratio.
|
||||
pub fn insert_split_adjacent(&mut self, target_id: PanelGroupId, new_child: SplitChild, insert_before: bool, needs_horizontal: bool, depth: usize, source_slot_size: Option<f64>) -> bool {
|
||||
let PanelLayoutSubdivision::Split { children } = self else { return false };
|
||||
|
||||
let is_horizontal = depth.is_multiple_of(2);
|
||||
@@ -517,18 +614,35 @@ impl PanelLayoutSubdivision {
|
||||
// If the target is a direct child: we can certainly insert the new split, either as a sibling (if direction matches) or wrapping the target in a new split (if direction is mismatched)
|
||||
let target_is_direct_child = matches!(&children[containing_index].subdivision, PanelLayoutSubdivision::PanelGroup { id, .. } if *id == target_id);
|
||||
if target_is_direct_child {
|
||||
// Direction matches and target is right here: insert as a sibling
|
||||
// Direction matches: insert as sibling, sizing based on whether target will be pruned, source size hint, or default ratio
|
||||
if direction_matches {
|
||||
let mut new_child = new_child;
|
||||
let target_will_be_pruned = matches!(&children[containing_index].subdivision, PanelLayoutSubdivision::PanelGroup { state, .. } if state.tabs.is_empty());
|
||||
if target_will_be_pruned {
|
||||
new_child.size = children[containing_index].size;
|
||||
} else if let Some(hint) = source_slot_size {
|
||||
new_child.size = hint;
|
||||
} else {
|
||||
let target_share = document_split_share(&children[containing_index].subdivision, &new_child.subdivision);
|
||||
let total = children[containing_index].size;
|
||||
children[containing_index].size = total * target_share;
|
||||
new_child.size = total * (1. - target_share);
|
||||
}
|
||||
|
||||
let insert_index = if insert_before { containing_index } else { containing_index + 1 };
|
||||
children.insert(insert_index, new_child);
|
||||
}
|
||||
// Direction mismatch: wrap the target in a new sub-split (at depth+1, which has the opposite direction of this and thus is the requested direction)
|
||||
// Direction mismatch: wrap target in a sub-split at depth+1, sharing the slot in the default ratio
|
||||
else {
|
||||
let old_child_subdivision = std::mem::replace(&mut children[containing_index].subdivision, PanelLayoutSubdivision::Split { children: vec![] });
|
||||
|
||||
let old_share = document_split_share(&old_child_subdivision, &new_child.subdivision);
|
||||
let old_child = SplitChild {
|
||||
subdivision: old_child_subdivision,
|
||||
size: 50.,
|
||||
size: old_share,
|
||||
};
|
||||
let mut new_child = new_child;
|
||||
new_child.size = 1. - old_share;
|
||||
|
||||
if let PanelLayoutSubdivision::Split { children: sub_children } = &mut children[containing_index].subdivision {
|
||||
if insert_before {
|
||||
@@ -547,7 +661,25 @@ impl PanelLayoutSubdivision {
|
||||
// The target is deeper, so recurse into the containing child's subtree and return its insertion outcome
|
||||
children[containing_index]
|
||||
.subdivision
|
||||
.insert_split_adjacent(target_id, new_child.clone(), insert_before, needs_horizontal, depth + 1)
|
||||
.insert_split_adjacent(target_id, new_child.clone(), insert_before, needs_horizontal, depth + 1, source_slot_size)
|
||||
}
|
||||
|
||||
/// Find the size of the `SplitChild` slot whose subdivision is the panel group with the given ID, if it exists.
|
||||
pub fn find_slot_size_by_group_id(&self, group_id: PanelGroupId) -> Option<f64> {
|
||||
let PanelLayoutSubdivision::Split { children } = self else { return None };
|
||||
for child in children {
|
||||
if let PanelLayoutSubdivision::PanelGroup { id, .. } = &child.subdivision
|
||||
&& *id == group_id
|
||||
{
|
||||
return Some(child.size);
|
||||
}
|
||||
}
|
||||
for child in children {
|
||||
if let Some(size) = child.subdivision.find_slot_size_by_group_id(group_id) {
|
||||
return Some(size);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if this subtree contains the document panel.
|
||||
@@ -558,39 +690,46 @@ impl PanelLayoutSubdivision {
|
||||
}
|
||||
}
|
||||
|
||||
/// Recalculate the default sizes for this subdivision's children based on proximity to the document panel.
|
||||
/// Recalculate the default sizes for this subdivision's direct children based on proximity to the document panel.
|
||||
/// Splits directly surrounding the document panel use 80-20 weighting.
|
||||
/// All other splits use equal division.
|
||||
/// Does not recurse into descendants: use [`Self::recalculate_default_sizes_recursive`] for that.
|
||||
pub fn recalculate_default_sizes(&mut self) {
|
||||
if let PanelLayoutSubdivision::Split { children } = self {
|
||||
let child_count = children.len();
|
||||
if child_count == 0 {
|
||||
return;
|
||||
let PanelLayoutSubdivision::Split { children } = self else { return };
|
||||
|
||||
let child_count = children.len();
|
||||
if child_count == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if any child directly contains (or is) the document panel
|
||||
let document_child_index = children.iter().position(|child| child.subdivision.contains_document());
|
||||
|
||||
if let Some(document_index) = document_child_index {
|
||||
// This split directly surrounds the document panel
|
||||
let non_document_count = child_count - 1;
|
||||
let document_share = if non_document_count > 0 { DOCUMENT_PANEL_SHARE } else { 1. };
|
||||
let other_share = if non_document_count > 0 { (1. - DOCUMENT_PANEL_SHARE) / non_document_count as f64 } else { 0. };
|
||||
|
||||
for (i, child) in children.iter_mut().enumerate() {
|
||||
child.size = if i == document_index { document_share } else { other_share };
|
||||
}
|
||||
|
||||
// Check if any child directly contains (or is) the document panel
|
||||
let document_child_index = children.iter().position(|child| child.subdivision.contains_document());
|
||||
|
||||
if let Some(document_index) = document_child_index {
|
||||
// This split directly surrounds the document panel, so use 80-20 weighting
|
||||
let non_document_count = child_count - 1;
|
||||
let document_share = if non_document_count > 0 { 80. } else { 100. };
|
||||
let other_share = if non_document_count > 0 { 20. / non_document_count as f64 } else { 0. };
|
||||
|
||||
for (i, child) in children.iter_mut().enumerate() {
|
||||
child.size = if i == document_index { document_share } else { other_share };
|
||||
}
|
||||
} else {
|
||||
// This split doesn't directly contain the document, use equal division
|
||||
let equal_share = 100. / child_count as f64;
|
||||
for child in children.iter_mut() {
|
||||
child.size = equal_share;
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
} else {
|
||||
// This split doesn't directly contain the document, use equal division
|
||||
let equal_share = 1. / child_count as f64;
|
||||
for child in children.iter_mut() {
|
||||
child.subdivision.recalculate_default_sizes();
|
||||
child.size = equal_share;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recalculate the default sizes for this subdivision and all its descendant splits.
|
||||
pub fn recalculate_default_sizes_recursive(&mut self) {
|
||||
self.recalculate_default_sizes();
|
||||
|
||||
if let PanelLayoutSubdivision::Split { children } = self {
|
||||
for child in children.iter_mut() {
|
||||
child.subdivision.recalculate_default_sizes_recursive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user