Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ProportionalStackPanel layout update when IsEmpty of its children changes #322

Merged
merged 2 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading