Skip to content

Commit

Permalink
Merge pull request #322 from BAndysc/isempty_fix
Browse files Browse the repository at this point in the history
Fix ProportionalStackPanel layout update when IsEmpty of its children changes
  • Loading branch information
wieslawsoltes authored Mar 11, 2024
2 parents 8bf68bd + 89a7a4f commit 8e0a5e9
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 246 deletions.
6 changes: 3 additions & 3 deletions samples/DockMvvmSample/Views/ProportionalStackPanelView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<TabControl>
<TabItem Header="Default">
<ProportionalStackPanel Orientation="Horizontal">
<ProportionalStackPanel ProportionalStackPanelSplitter.Proportion="0.5">
<Rectangle Fill="Red" ProportionalStackPanelSplitter.Proportion="0.5" />
<ProportionalStackPanel ProportionalStackPanel.Proportion="0.5">
<Rectangle Fill="Red" ProportionalStackPanel.Proportion="0.5" />
<ProportionalStackPanelSplitter />
<Rectangle Fill="Green" />
<ProportionalStackPanelSplitter />
Expand All @@ -35,7 +35,7 @@
<ProportionalStackPanelSplitter />
<Rectangle Fill="Blue" />
<ProportionalStackPanelSplitter />
<Rectangle Fill="Red" ProportionalStackPanelSplitter.Proportion="0.5" />
<Rectangle Fill="Red" ProportionalStackPanel.Proportion="0.5" />
</ProportionalStackPanel>
</ProportionalStackPanel>
</TabItem>
Expand Down
3 changes: 0 additions & 3 deletions src/Dock.Avalonia/Controls/DockDockControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@

<ControlTheme x:Key="{x:Type DockDockControl}" TargetType="DockDockControl">

<Setter Property="(ProportionalStackPanelSplitter.Proportion)" Value="{Binding Proportion}" x:DataType="core:IDock" />
<Setter Property="(ProportionalStackPanelSplitter.IsEmpty)" Value="{Binding IsEmpty}" x:DataType="core:IDock" />

<Setter Property="Template">
<ControlTemplate>
<DockableControl TrackingMode="Visible">
Expand Down
6 changes: 1 addition & 5 deletions src/Dock.Avalonia/Controls/DocumentDockControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@

<ControlTheme x:Key="{x:Type DocumentDockControl}" TargetType="DocumentDockControl">

<Setter Property="(ProportionalStackPanelSplitter.Proportion)" Value="{Binding Proportion}" x:DataType="core:IDock" />
<Setter Property="(ProportionalStackPanelSplitter.IsEmpty)" Value="{Binding IsEmpty}" x:DataType="core:IDock" />

<Setter Property="Template">
<ControlTemplate>
<DockableControl TrackingMode="Visible"
ProportionalStackPanelSplitter.Proportion="{Binding Proportion}">
<DockableControl TrackingMode="Visible">
<DocumentControl IsActive="{Binding IsActive}" />
</DockableControl>
</ControlTemplate>
Expand Down
18 changes: 11 additions & 7 deletions src/Dock.Avalonia/Controls/ProportionalDockControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@

<ControlTheme x:Key="{x:Type ProportionalDockControl}" TargetType="ProportionalDockControl">

<Setter Property="(ProportionalStackPanelSplitter.Proportion)" Value="{Binding Proportion}" x:DataType="core:IDock" />
<Setter Property="(ProportionalStackPanelSplitter.IsEmpty)" Value="{Binding IsEmpty}" x:DataType="core:IDock" />

<Setter Property="Template">
<ControlTemplate>
<DockableControl TrackingMode="Visible"
ProportionalStackPanelSplitter.Proportion="{Binding Proportion}">
<DockableControl TrackingMode="Visible">
<ItemsControl ItemsSource="{Binding VisibleDockables}">
<ItemsControl.Styles>
<Style Selector="ItemsControl > ContentPresenter > :is(core|IDock)" x:DataType="core:IDock">
<Setter Property="(ProportionalStackPanelSplitter.Proportion)" Value="{Binding Proportion}" />
<Style Selector="ItemsControl > ContentPresenter">
<Setter x:DataType="core:IDock" Property="(ProportionalStackPanel.Proportion)" Value="{Binding Proportion}" />
<Setter Property="(ProportionalStackPanel.IsCollapsed)">
<Setter.Value>
<MultiBinding Converter="{x:Static BoolConverters.And}" x:DataType="core:IDock">
<CompiledBinding Path="IsCollapsable" />
<CompiledBinding Path="IsEmpty" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Styles>
<ItemsControl.ItemsPanel>
Expand Down
143 changes: 102 additions & 41 deletions src/Dock.Avalonia/Controls/ProportionalStackPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data;
using Avalonia.Layout;

namespace Dock.Avalonia.Controls;
Expand All @@ -28,6 +29,58 @@ public Orientation Orientation
set => SetValue(OrientationProperty, value);
}

/// <summary>
/// Defines the Proportion attached property.
/// </summary>
public static readonly AttachedProperty<double> ProportionProperty =
AvaloniaProperty.RegisterAttached<ProportionalStackPanel, Control, double>("Proportion", double.NaN, false, BindingMode.TwoWay);

/// <summary>
/// Gets the value of the Proportion attached property on the specified control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The Proportion attached property.</returns>
public static double GetProportion(AvaloniaObject control)
{
return control.GetValue(ProportionProperty);
}

/// <summary>
/// Sets the value of the Proportion attached property on the specified control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The value of the Proportion property.</param>
public static void SetProportion(AvaloniaObject control, double value)
{
control.SetValue(ProportionProperty, value);
}

/// <summary>
/// Defines the IsCollapsed attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsCollapsedProperty =
AvaloniaProperty.RegisterAttached<ProportionalStackPanel, Control, bool>("IsCollapsed", false, false, BindingMode.TwoWay);

/// <summary>
/// Gets the value of the IsCollapsed attached property on the specified control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The IsCollapsed attached property.</returns>
public static bool GetIsCollapsed(AvaloniaObject control)
{
return control.GetValue(IsCollapsedProperty);
}

/// <summary>
/// Sets the value of the IsCollapsed attached property on the specified control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The value of the IsCollapsed property.</param>
public static void SetIsCollapsed(AvaloniaObject control, bool value)
{
control.SetValue(IsCollapsedProperty, value);
}

private void AssignProportions(global::Avalonia.Controls.Controls children)
{
var assignedProportion = 0.0;
Expand All @@ -36,14 +89,14 @@ private void AssignProportions(global::Avalonia.Controls.Controls children)
for (var i = 0; i < children.Count; i++)
{
var control = children[i];
var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control);
var isCollapsed = GetIsCollapsed(control);
var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _);

if (!isSplitter)
{
var proportion = ProportionalStackPanelSplitter.GetControlProportion(control);
var proportion = GetProportion(control);

if (isEmpty)
if (isCollapsed)
{
proportion = 0.0;
}
Expand All @@ -64,14 +117,14 @@ private void AssignProportions(global::Avalonia.Controls.Controls children)
var toAssign = assignedProportion;
foreach (var control in children.Where(c =>
{
var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c);
return !isEmpty && double.IsNaN(ProportionalStackPanelSplitter.GetControlProportion(c));
var isCollapsed = GetIsCollapsed(c);
return !isCollapsed && double.IsNaN(GetProportion(c));
}))
{
if (!ProportionalStackPanelSplitter.IsSplitter(control, out _))
{
var proportion = (1.0 - toAssign) / unassignedProportions;
ProportionalStackPanelSplitter.SetControlProportion(control, proportion);
SetProportion(control, proportion);
assignedProportion += (1.0 - toAssign) / unassignedProportions;
}
}
Expand All @@ -85,12 +138,12 @@ private void AssignProportions(global::Avalonia.Controls.Controls children)

foreach (var child in children.Where(c =>
{
var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c);
return !isEmpty && !ProportionalStackPanelSplitter.IsSplitter(c, out _);
var isCollapsed = GetIsCollapsed(c);
return !isCollapsed && !ProportionalStackPanelSplitter.IsSplitter(c, out _);
}))
{
var proportion = ProportionalStackPanelSplitter.GetControlProportion(child) + toAdd;
ProportionalStackPanelSplitter.SetControlProportion(child, proportion);
var proportion = GetProportion(child) + toAdd;
SetProportion(child, proportion);
}
}
else if (assignedProportion > 1)
Expand All @@ -101,19 +154,19 @@ private void AssignProportions(global::Avalonia.Controls.Controls children)

foreach (var child in children.Where(c =>
{
var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c);
return !isEmpty && !ProportionalStackPanelSplitter.IsSplitter(c, out _);
var isCollapsed = GetIsCollapsed(c);
return !isCollapsed && !ProportionalStackPanelSplitter.IsSplitter(c, out _);
}))
{
var proportion = ProportionalStackPanelSplitter.GetControlProportion(child) - toRemove;
ProportionalStackPanelSplitter.SetControlProportion(child, proportion);
var proportion = GetProportion(child) - toRemove;
SetProportion(child, proportion);
}
}
}

private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls children)
{
var previousIsEmpty = false;
var previousisCollapsed = false;
var totalThickness = 0.0;

for (var i = 0; i < children.Count; i++)
Expand All @@ -123,17 +176,17 @@ private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls chil

if (isSplitter && proportionalStackPanelSplitter is not null)
{
if (previousIsEmpty)
if (previousisCollapsed)
{
previousIsEmpty = false;
previousisCollapsed = false;
continue;
}

if (i + 1 < Children.Count)
{
var nextControl = Children[i + 1];
var nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl);
if (nextIsEmpty)
var nextisCollapsed = GetIsCollapsed(nextControl);
if (nextisCollapsed)
{
continue;
}
Expand All @@ -144,7 +197,7 @@ private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls chil
}
else
{
previousIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c);
previousisCollapsed = GetIsCollapsed(c);
}
}

Expand All @@ -171,7 +224,7 @@ protected override Size MeasureOverride(Size constraint)

AssignProportions(Children);

var previousIsEmpty = false;
var previousisCollapsed = false;

// Measure each of the Children
for (var i = 0; i < Children.Count; i++)
Expand All @@ -184,13 +237,13 @@ protected override Size MeasureOverride(Size constraint)
Math.Max(0.0, constraint.Width - usedWidth - splitterThickness),
Math.Max(0.0, constraint.Height - usedHeight - splitterThickness));

var proportion = ProportionalStackPanelSplitter.GetControlProportion(control);
var proportion = GetProportion(control);

var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control);
if (isEmpty)
var isCollapsed = !isSplitter && GetIsCollapsed(control);
if (isCollapsed)
{
// TODO: Also handle next is empty.
previousIsEmpty = true;
previousisCollapsed = true;
var size = new Size();
control.Measure(size);
continue;
Expand Down Expand Up @@ -220,25 +273,25 @@ protected override Size MeasureOverride(Size constraint)
}
else
{
var nextIsEmpty = false;
var nextisCollapsed = false;
if (i + 1 < Children.Count)
{
var nextControl = Children[i + 1];
nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl);
nextisCollapsed = !ProportionalStackPanelSplitter.IsSplitter(nextControl, out _ ) && GetIsCollapsed(nextControl);
}

if (previousIsEmpty || nextIsEmpty)
if (previousisCollapsed || nextisCollapsed)
{
var size = new Size();
control.Measure(size);
previousIsEmpty = true;
previousisCollapsed = true;
continue;
}

control.Measure(remainingSize);
}

previousIsEmpty = false;
previousisCollapsed = false;

var desiredSize = control.DesiredSize;

Expand Down Expand Up @@ -298,41 +351,41 @@ protected override Size ArrangeOverride(Size arrangeSize)

AssignProportions(Children);

var previousIsEmpty = false;
var previousisCollapsed = false;

for (var i = 0; i < Children.Count; i++)
{
var control = Children[i];

var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control);
if (isEmpty)
var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _);

var isCollapsed = !isSplitter && GetIsCollapsed(control);
if (isCollapsed)
{
// TODO: Also handle next is empty.
previousIsEmpty = true;
previousisCollapsed = true;
var rect = new Rect();
control.Arrange(rect);
index++;
continue;
}

var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _);

var nextIsEmpty = false;
var nextisCollapsed = false;
if (i + 1 < Children.Count)
{
var nextControl = Children[i + 1];
nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl);
nextisCollapsed = !ProportionalStackPanelSplitter.IsSplitter(nextControl, out _) && GetIsCollapsed(nextControl);
}

if (isSplitter && (previousIsEmpty || nextIsEmpty))
if (isSplitter && (previousisCollapsed || nextisCollapsed))
{
var rect = new Rect();
control.Arrange(rect);
index++;
continue;
}

previousIsEmpty = false;
previousisCollapsed = false;

// Determine the remaining space left to arrange the element
var remainingRect = new Rect(
Expand All @@ -347,7 +400,7 @@ protected override Size ArrangeOverride(Size arrangeSize)
if (index < Children.Count)
{
var desiredSize = control.DesiredSize;
var proportion = ProportionalStackPanelSplitter.GetControlProportion(control);
var proportion = GetProportion(control);

switch (Orientation)
{
Expand Down Expand Up @@ -405,4 +458,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
InvalidateMeasure();
}
}

static ProportionalStackPanel()
{
AffectsParentMeasure<ProportionalStackPanel>(IsCollapsedProperty);
AffectsParentArrange<ProportionalStackPanel>(IsCollapsedProperty);
AffectsParentMeasure<ProportionalStackPanel>(ProportionProperty);
AffectsParentArrange<ProportionalStackPanel>(ProportionProperty);
}
}
Loading

0 comments on commit 8e0a5e9

Please sign in to comment.