diff --git a/LanMountainDesktop.Tests/ComponentLibraryCollapseStateTests.cs b/LanMountainDesktop.Tests/ComponentLibraryCollapseStateTests.cs
new file mode 100644
index 0000000..22dcd9f
--- /dev/null
+++ b/LanMountainDesktop.Tests/ComponentLibraryCollapseStateTests.cs
@@ -0,0 +1,60 @@
+using Avalonia;
+using LanMountainDesktop.DesktopEditing;
+using Xunit;
+
+namespace LanMountainDesktop.Tests;
+
+public sealed class ComponentLibraryCollapseStateTests
+{
+ [Fact]
+ public void CreateExpanded_InitializesExpandedStateAndHidesChip()
+ {
+ var margin = new Thickness(24, 24, 24, 100);
+ var state = ComponentLibraryCollapseState.CreateExpanded(margin, 0.75);
+
+ Assert.Equal(ComponentLibraryCollapseVisualState.Expanded, state.VisualState);
+ Assert.Equal(margin, state.ExpandedMargin);
+ Assert.Equal(0.75, state.ExpandedOpacity, 3);
+ Assert.False(state.IsChipVisible);
+ }
+
+ [Fact]
+ public void WithVisualState_PreservesStableExpandedSnapshotAcrossTransitions()
+ {
+ var margin = new Thickness(20, 18, 20, 96);
+ var expanded = ComponentLibraryCollapseState.CreateExpanded(margin, 1);
+
+ var collapsing = expanded.WithVisualState(ComponentLibraryCollapseVisualState.Collapsing, isChipVisible: true);
+ var collapsed = collapsing.WithVisualState(ComponentLibraryCollapseVisualState.Collapsed, isChipVisible: true);
+ var restoring = collapsed.WithVisualState(ComponentLibraryCollapseVisualState.Restoring, isChipVisible: false);
+
+ Assert.Equal(ComponentLibraryCollapseVisualState.Collapsing, collapsing.VisualState);
+ Assert.Equal(ComponentLibraryCollapseVisualState.Collapsed, collapsed.VisualState);
+ Assert.Equal(ComponentLibraryCollapseVisualState.Restoring, restoring.VisualState);
+
+ Assert.Equal(margin, collapsing.ExpandedMargin);
+ Assert.Equal(margin, collapsed.ExpandedMargin);
+ Assert.Equal(margin, restoring.ExpandedMargin);
+
+ Assert.Equal(1, collapsing.ExpandedOpacity, 3);
+ Assert.Equal(1, collapsed.ExpandedOpacity, 3);
+ Assert.Equal(1, restoring.ExpandedOpacity, 3);
+
+ Assert.True(collapsing.IsChipVisible);
+ Assert.True(collapsed.IsChipVisible);
+ Assert.False(restoring.IsChipVisible);
+ }
+
+ [Fact]
+ public void CreateExpanded_ProducesRestorableSnapshotEvenWhenOriginalOpacityIsLow()
+ {
+ var margin = new Thickness(18, 22, 18, 88);
+ var expanded = ComponentLibraryCollapseState.CreateExpanded(margin, 0.15);
+ var restored = expanded.WithVisualState(ComponentLibraryCollapseVisualState.Expanded, isChipVisible: false);
+
+ Assert.Equal(margin, restored.ExpandedMargin);
+ Assert.Equal(0.15, restored.ExpandedOpacity, 3);
+ Assert.Equal(ComponentLibraryCollapseVisualState.Expanded, restored.VisualState);
+ Assert.False(restored.IsChipVisible);
+ }
+}
diff --git a/LanMountainDesktop.Tests/DesktopPlacementMathTests.cs b/LanMountainDesktop.Tests/DesktopPlacementMathTests.cs
index 23eedc8..85d4866 100644
--- a/LanMountainDesktop.Tests/DesktopPlacementMathTests.cs
+++ b/LanMountainDesktop.Tests/DesktopPlacementMathTests.cs
@@ -134,5 +134,40 @@ public sealed class DesktopPlacementMathTests
Assert.False(DesktopPlacementMath.CanCommitPlacement(placementRect, occludingLibraryBounds));
Assert.True(DesktopPlacementMath.CanCommitPlacement(placementRect, distantLibraryBounds));
+ Assert.True(DesktopPlacementMath.CanCommitPlacement(placementRect, componentLibraryBounds: null));
+ }
+
+ [Fact]
+ public void Session_AllowsCommitWhenComponentLibraryBoundsAreCleared()
+ {
+ var pendingSession = DesktopEditSession.CreatePendingNew(
+ componentId: "demo",
+ pageIndex: 0,
+ widthCells: 2,
+ heightCells: 2,
+ startPointerInViewport: new Point(80, 80),
+ pointerOffsetInViewport: new Point(60, 60),
+ componentLibraryBounds: null)
+ .WithCurrentPointer(new Point(200, 180));
+
+ Assert.True(pendingSession.HasExceededThreshold(DesktopPlacementMath.ComputeDragStartThreshold(80)));
+ Assert.False(pendingSession.IsPointerInsideComponentLibrary());
+ Assert.False(pendingSession.IsPreviewOccludedByComponentLibrary(new Rect(100, 100, 40, 40)));
+ Assert.False(pendingSession.CanCommit);
+
+ var resizeSession = DesktopEditSession.CreateResizingExisting(
+ componentId: "demo",
+ placementId: "placement-1",
+ pageIndex: 0,
+ widthCells: 2,
+ heightCells: 2,
+ startPointerInViewport: new Point(80, 80),
+ componentLibraryBounds: null)
+ .WithCurrentPointer(new Point(200, 180))
+ .WithTargetCell(row: 2, column: 3);
+
+ Assert.False(resizeSession.IsPointerInsideComponentLibrary());
+ Assert.False(resizeSession.IsPreviewOccludedByComponentLibrary(new Rect(100, 100, 40, 40)));
+ Assert.True(resizeSession.CanCommit);
}
}
diff --git a/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapsePresenter.cs b/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapsePresenter.cs
new file mode 100644
index 0000000..630b21c
--- /dev/null
+++ b/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapsePresenter.cs
@@ -0,0 +1,280 @@
+using System;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Threading;
+
+namespace LanMountainDesktop.DesktopEditing;
+
+internal sealed class ComponentLibraryCollapsePresenter
+{
+ private static readonly TimeSpan TransitionDuration = TimeSpan.FromMilliseconds(150);
+ private static readonly Easing TransitionEasing = new CubicEaseOut();
+ private const double StableOpacityThreshold = 0.01;
+
+ private readonly Border _componentLibraryWindow;
+ private readonly Border _collapsedChipHost;
+ private readonly TextBlock _collapsedChipTextBlock;
+ private readonly Control? _collapsedChipIcon;
+ private readonly TranslateTransform _windowTranslate = new();
+ private readonly TranslateTransform _chipTranslate = new();
+ private readonly ScaleTransform _chipScale = new(1, 1);
+
+ private ComponentLibraryCollapseState _state;
+ private int _transitionVersion;
+
+ public ComponentLibraryCollapsePresenter(
+ Border componentLibraryWindow,
+ Border collapsedChipHost,
+ TextBlock collapsedChipTextBlock,
+ Control? collapsedChipIcon = null)
+ {
+ _componentLibraryWindow = componentLibraryWindow ?? throw new ArgumentNullException(nameof(componentLibraryWindow));
+ _collapsedChipHost = collapsedChipHost ?? throw new ArgumentNullException(nameof(collapsedChipHost));
+ _collapsedChipTextBlock = collapsedChipTextBlock ?? throw new ArgumentNullException(nameof(collapsedChipTextBlock));
+ _collapsedChipIcon = collapsedChipIcon;
+
+ EnsureTransforms();
+ _state = ComponentLibraryCollapseState.CreateExpanded(
+ _componentLibraryWindow.Margin,
+ _componentLibraryWindow.Opacity <= 0 ? 1 : _componentLibraryWindow.Opacity);
+ ApplyExpandedSnapshot();
+ _collapsedChipHost.IsVisible = false;
+ _collapsedChipHost.IsHitTestVisible = false;
+ _collapsedChipHost.Opacity = 0;
+ }
+
+ public bool IsCollapsed => _state.VisualState is ComponentLibraryCollapseVisualState.Collapsing or ComponentLibraryCollapseVisualState.Collapsed;
+
+ public ComponentLibraryCollapseVisualState VisualState => _state.VisualState;
+
+ public void SyncExpandedState(Thickness margin, double opacity)
+ {
+ var hasStableOpacity = IsStableExpandedOpacity(opacity);
+ var nextExpandedOpacity = hasStableOpacity ? Math.Clamp(opacity, 0, 1) : _state.ExpandedOpacity;
+ _state = _state with
+ {
+ ExpandedMargin = margin,
+ ExpandedOpacity = nextExpandedOpacity
+ };
+
+ if (_state.VisualState is ComponentLibraryCollapseVisualState.Expanded or ComponentLibraryCollapseVisualState.Restoring)
+ {
+ ApplyExpandedSnapshot(applyOpacity: hasStableOpacity);
+ }
+ }
+
+ public void Collapse(string title)
+ {
+ _collapsedChipTextBlock.Text = string.IsNullOrWhiteSpace(title) ? "Widgets" : title;
+
+ if (_state.VisualState is ComponentLibraryCollapseVisualState.Collapsing or ComponentLibraryCollapseVisualState.Collapsed)
+ {
+ ShowCollapsedChip(_transitionVersion);
+ return;
+ }
+
+ var version = ++_transitionVersion;
+ _state = _state.WithVisualState(ComponentLibraryCollapseVisualState.Collapsing, isChipVisible: true);
+
+ ApplyExpandedSnapshot();
+ ShowCollapsedChip(version);
+ SetCollapsedWindowTargets();
+
+ DispatcherTimer.RunOnce(
+ () =>
+ {
+ if (version != _transitionVersion)
+ {
+ return;
+ }
+
+ _state = _state.WithVisualState(ComponentLibraryCollapseVisualState.Collapsed, isChipVisible: true);
+ _componentLibraryWindow.IsVisible = false;
+ _componentLibraryWindow.IsHitTestVisible = false;
+ },
+ TransitionDuration);
+ }
+
+ public void Restore()
+ {
+ if (_state.VisualState is ComponentLibraryCollapseVisualState.Expanded)
+ {
+ ApplyExpandedSnapshot();
+ _collapsedChipHost.IsVisible = false;
+ _collapsedChipHost.IsHitTestVisible = false;
+ _collapsedChipHost.Opacity = 0;
+ return;
+ }
+
+ var version = ++_transitionVersion;
+ _state = _state.WithVisualState(ComponentLibraryCollapseVisualState.Restoring, isChipVisible: false);
+
+ PrepareRestoringWindow();
+ HideCollapsedChip(version);
+ Dispatcher.UIThread.Post(
+ () =>
+ {
+ if (version != _transitionVersion)
+ {
+ return;
+ }
+
+ _componentLibraryWindow.Opacity = _state.ExpandedOpacity;
+ _windowTranslate.Y = 0;
+ },
+ DispatcherPriority.Background);
+
+ DispatcherTimer.RunOnce(
+ () =>
+ {
+ if (version != _transitionVersion)
+ {
+ return;
+ }
+
+ _state = _state.WithVisualState(ComponentLibraryCollapseVisualState.Expanded, isChipVisible: false);
+ _componentLibraryWindow.IsVisible = true;
+ _componentLibraryWindow.IsHitTestVisible = true;
+ },
+ TransitionDuration);
+ }
+
+ private void EnsureTransforms()
+ {
+ _componentLibraryWindow.RenderTransform = _windowTranslate;
+ _windowTranslate.Transitions = new Transitions
+ {
+ new DoubleTransition
+ {
+ Property = TranslateTransform.YProperty,
+ Duration = TransitionDuration,
+ Easing = TransitionEasing
+ }
+ };
+
+ _collapsedChipHost.RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
+ _collapsedChipHost.RenderTransform = new TransformGroup
+ {
+ Children =
+ {
+ _chipTranslate,
+ _chipScale
+ }
+ };
+ _chipTranslate.Transitions = new Transitions
+ {
+ new DoubleTransition
+ {
+ Property = TranslateTransform.YProperty,
+ Duration = TransitionDuration,
+ Easing = TransitionEasing
+ }
+ };
+ _chipScale.Transitions = new Transitions
+ {
+ new DoubleTransition
+ {
+ Property = ScaleTransform.ScaleXProperty,
+ Duration = TransitionDuration,
+ Easing = TransitionEasing
+ },
+ new DoubleTransition
+ {
+ Property = ScaleTransform.ScaleYProperty,
+ Duration = TransitionDuration,
+ Easing = TransitionEasing
+ }
+ };
+ }
+
+ private void ApplyExpandedSnapshot(bool applyOpacity = true)
+ {
+ _componentLibraryWindow.Margin = _state.ExpandedMargin;
+ if (applyOpacity)
+ {
+ _componentLibraryWindow.Opacity = _state.ExpandedOpacity;
+ }
+
+ _componentLibraryWindow.IsVisible = true;
+ _componentLibraryWindow.IsHitTestVisible = true;
+ _windowTranslate.Y = 0;
+ }
+
+ private void SetCollapsedWindowTargets()
+ {
+ _componentLibraryWindow.Opacity = 0;
+ _windowTranslate.Y = 28;
+ }
+
+ private void ShowCollapsedChip(int version)
+ {
+ _collapsedChipHost.IsVisible = true;
+ _collapsedChipHost.IsHitTestVisible = false;
+ _collapsedChipTextBlock.IsVisible = true;
+ if (_collapsedChipIcon is not null)
+ {
+ _collapsedChipIcon.IsVisible = true;
+ }
+
+ _collapsedChipHost.Opacity = 0;
+ _chipTranslate.Y = 8;
+ _chipScale.ScaleX = 0.96;
+ _chipScale.ScaleY = 0.96;
+
+ Dispatcher.UIThread.Post(
+ () =>
+ {
+ if (version != _transitionVersion)
+ {
+ return;
+ }
+
+ _collapsedChipHost.Opacity = 1;
+ _chipTranslate.Y = 0;
+ _chipScale.ScaleX = 1;
+ _chipScale.ScaleY = 1;
+ },
+ DispatcherPriority.Background);
+ }
+
+ private void HideCollapsedChip(int version)
+ {
+ _collapsedChipHost.IsVisible = true;
+ _collapsedChipHost.IsHitTestVisible = false;
+ _collapsedChipHost.Opacity = 0;
+ _chipTranslate.Y = 8;
+ _chipScale.ScaleX = 0.96;
+ _chipScale.ScaleY = 0.96;
+
+ DispatcherTimer.RunOnce(
+ () =>
+ {
+ if (version != _transitionVersion)
+ {
+ return;
+ }
+
+ _collapsedChipHost.IsVisible = false;
+ },
+ TransitionDuration);
+ }
+
+ private void PrepareRestoringWindow()
+ {
+ _componentLibraryWindow.IsVisible = true;
+ _componentLibraryWindow.IsHitTestVisible = true;
+ _componentLibraryWindow.Margin = _state.ExpandedMargin;
+ _componentLibraryWindow.Opacity = 0;
+ _windowTranslate.Y = 28;
+ }
+
+ private static bool IsStableExpandedOpacity(double opacity)
+ {
+ return !double.IsNaN(opacity) &&
+ !double.IsInfinity(opacity) &&
+ opacity > StableOpacityThreshold;
+ }
+}
diff --git a/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapseState.cs b/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapseState.cs
new file mode 100644
index 0000000..57ec7f8
--- /dev/null
+++ b/LanMountainDesktop/DesktopEditing/ComponentLibraryCollapseState.cs
@@ -0,0 +1,36 @@
+using Avalonia;
+
+namespace LanMountainDesktop.DesktopEditing;
+
+internal enum ComponentLibraryCollapseVisualState
+{
+ Expanded,
+ Collapsing,
+ Collapsed,
+ Restoring
+}
+
+internal readonly record struct ComponentLibraryCollapseState(
+ ComponentLibraryCollapseVisualState VisualState,
+ Thickness ExpandedMargin,
+ double ExpandedOpacity,
+ bool IsChipVisible)
+{
+ public static ComponentLibraryCollapseState CreateExpanded(Thickness expandedMargin, double expandedOpacity)
+ {
+ return new(
+ ComponentLibraryCollapseVisualState.Expanded,
+ expandedMargin,
+ expandedOpacity,
+ IsChipVisible: false);
+ }
+
+ public ComponentLibraryCollapseState WithVisualState(ComponentLibraryCollapseVisualState visualState, bool isChipVisible)
+ {
+ return this with
+ {
+ VisualState = visualState,
+ IsChipVisible = isChipVisible
+ };
+ }
+}
diff --git a/LanMountainDesktop/DesktopEditing/DesktopEditOverlayPresenter.cs b/LanMountainDesktop/DesktopEditing/DesktopEditOverlayPresenter.cs
index 5b68d91..f10cc88 100644
--- a/LanMountainDesktop/DesktopEditing/DesktopEditOverlayPresenter.cs
+++ b/LanMountainDesktop/DesktopEditing/DesktopEditOverlayPresenter.cs
@@ -9,6 +9,12 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.DesktopEditing;
+internal enum DesktopEditGhostVisualStyle
+{
+ StandardLift = 0,
+ ElevatedFromLibrary
+}
+
internal sealed class DesktopEditOverlayPresenter
{
private static readonly TimeSpan FastDuration = FluttermotionToken.Fast;
@@ -133,14 +139,16 @@ internal sealed class DesktopEditOverlayPresenter
UpdateCandidateAppearance();
}
- public void Show()
+ public void Show(DesktopEditGhostVisualStyle visualStyle = DesktopEditGhostVisualStyle.StandardLift)
{
_dismissVersion++;
_isVisible = true;
_root.IsVisible = true;
_root.Opacity = 0;
_ghostView.Opacity = 0;
- _ghostView.SetRestingScale(0.96);
+ var initialGhostScale = visualStyle == DesktopEditGhostVisualStyle.ElevatedFromLibrary ? 1.02 : 0.985;
+ var targetGhostScale = visualStyle == DesktopEditGhostVisualStyle.ElevatedFromLibrary ? 1.06 : 1;
+ _ghostView.SetRestingScale(initialGhostScale);
_candidateOutline.Opacity = 0;
_candidateScale.ScaleX = 0.96;
_candidateScale.ScaleY = 0.96;
@@ -154,7 +162,7 @@ internal sealed class DesktopEditOverlayPresenter
_root.Opacity = 1;
_ghostView.Opacity = 1;
- _ghostView.SetRestingScale(1);
+ _ghostView.SetRestingScale(targetGhostScale);
if (_candidateRect.HasValue)
{
_candidateOutline.Opacity = 1;
diff --git a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
index 4165354..09a16d5 100644
--- a/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
+++ b/LanMountainDesktop/Views/MainWindow.ComponentSystem.cs
@@ -453,6 +453,7 @@ public partial class MainWindow
_isComponentLibraryOpen = true;
UpdateDesktopComponentHostEditState();
ShowComponentLibraryCategoryView();
+ RestoreComponentLibraryAfterDesktopEdit();
ComponentLibraryWindow.IsVisible = true;
ComponentLibraryWindow.Opacity = 0;
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
@@ -467,6 +468,7 @@ public partial class MainWindow
BuildComponentLibraryCategoryPages();
ComponentLibraryWindow.Opacity = 1;
+ SyncComponentLibraryCollapseExpandedState();
}, DispatcherPriority.Background);
}
@@ -477,6 +479,7 @@ public partial class MainWindow
return;
}
+ RestoreComponentLibraryAfterDesktopEdit();
_isComponentLibraryOpen = false;
CancelDesktopComponentDrag();
CancelDesktopComponentResize(restoreOriginalSpan: true);
@@ -2056,13 +2059,14 @@ public partial class MainWindow
pointerOffset,
GetComponentLibraryBoundsInViewport());
+ CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(placement.ComponentId));
SetDesktopEditSourceHost(sourceHost, 0.22);
EnsureDesktopEditOverlayPresenter();
UpdateDesktopEditOverlayMetadata(placement.ComponentId, widthCells, heightCells, L("component.move", "Move"));
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
_desktopEditOverlayPresenter?.SetInvalid(false);
- _desktopEditOverlayPresenter?.Show();
+ _desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.StandardLift);
UpdateDesktopEditSession(pointerInViewport);
e.Pointer.Capture(this);
@@ -2208,13 +2212,14 @@ public partial class MainWindow
TargetColumn = placement.Column
};
+ CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(placement.ComponentId));
SetDesktopEditSourceHost(sourceHost, 0.22);
EnsureDesktopEditOverlayPresenter();
UpdateDesktopEditOverlayMetadata(placement.ComponentId, startSpan.WidthCells, startSpan.HeightCells, L("component.resize", "Resize"));
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
_desktopEditOverlayPresenter?.SetInvalid(false);
- _desktopEditOverlayPresenter?.Show();
+ _desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.StandardLift);
UpdateDesktopEditSession(pointerInViewport);
e.Pointer.Capture(this);
}
@@ -2883,7 +2888,7 @@ public partial class MainWindow
private void OnComponentLibraryWindowPointerPressed(object? sender, PointerPressedEventArgs e)
{
- if (ComponentLibraryWindow is null || !_isComponentLibraryOpen)
+ if (ComponentLibraryWindow is null || !_isComponentLibraryOpen || IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
{
return;
}
@@ -2904,6 +2909,17 @@ public partial class MainWindow
private void OnComponentLibraryWindowPointerMoved(object? sender, PointerEventArgs e)
{
+ if (IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
+ {
+ if (_isComponentLibraryWindowDragging)
+ {
+ _isComponentLibraryWindowDragging = false;
+ e.Pointer.Capture(null);
+ }
+
+ return;
+ }
+
if (!_isComponentLibraryWindowDragging || ComponentLibraryWindow is null)
{
return;
@@ -2920,11 +2936,23 @@ public partial class MainWindow
);
ComponentLibraryWindow.Margin = newMargin;
+ SyncComponentLibraryCollapseExpandedState();
e.Handled = true;
}
private void OnComponentLibraryWindowPointerReleased(object? sender, PointerReleasedEventArgs e)
{
+ if (IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
+ {
+ if (_isComponentLibraryWindowDragging)
+ {
+ _isComponentLibraryWindowDragging = false;
+ e.Pointer.Capture(null);
+ }
+
+ return;
+ }
+
if (!_isComponentLibraryWindowDragging)
{
return;
@@ -3111,6 +3139,7 @@ public partial class MainWindow
var margin = ComponentLibraryWindow.Margin;
_savedComponentLibraryMargin = margin;
_isComponentLibraryWindowPositionCustomized = true;
+ SyncComponentLibraryCollapseExpandedState();
}
private void RestoreComponentLibraryWindowPosition()
@@ -3121,6 +3150,7 @@ public partial class MainWindow
}
ComponentLibraryWindow.Margin = _savedComponentLibraryMargin;
+ SyncComponentLibraryCollapseExpandedState();
}
private Thickness _savedComponentLibraryMargin = new Thickness(24, 24, 24, 100);
diff --git a/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs b/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
index db89038..c7a7337 100644
--- a/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
+++ b/LanMountainDesktop/Views/MainWindow.DesktopEditing.cs
@@ -32,6 +32,7 @@ public partial class MainWindow
private int _desktopEditOverlayVersion;
private int _desktopEditCommitVersion;
private bool _isDesktopEditCommitPending;
+ private ComponentLibraryCollapsePresenter? _componentLibraryCollapsePresenter;
private bool HasActiveDesktopEditSession => _desktopEditSession.IsActive || _isDesktopEditCommitPending;
@@ -79,6 +80,89 @@ public partial class MainWindow
_desktopEditOverlayPresenter.SetViewportSize(new Size(width, height));
}
+ private void EnsureComponentLibraryCollapsePresenter()
+ {
+ if (_componentLibraryCollapsePresenter is not null || ComponentLibraryWindow is null)
+ {
+ return;
+ }
+
+ var collapsedChipHost = this.FindControl("ComponentLibraryCollapsedChipHost");
+ var collapsedChipTextBlock = this.FindControl("ComponentLibraryCollapsedChipTextBlock");
+ var collapsedChipIcon = this.FindControl("ComponentLibraryCollapsedChipIcon");
+ if (collapsedChipHost is null || collapsedChipTextBlock is null)
+ {
+ return;
+ }
+
+ _componentLibraryCollapsePresenter = new ComponentLibraryCollapsePresenter(
+ ComponentLibraryWindow,
+ collapsedChipHost,
+ collapsedChipTextBlock,
+ collapsedChipIcon);
+ }
+
+ private bool IsComponentLibraryTemporarilyCollapsedForDesktopEdit()
+ {
+ EnsureComponentLibraryCollapsePresenter();
+ return _componentLibraryCollapsePresenter is not null &&
+ _componentLibraryCollapsePresenter.VisualState != ComponentLibraryCollapseVisualState.Expanded;
+ }
+
+ private void SyncComponentLibraryCollapseExpandedState()
+ {
+ if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
+ {
+ return;
+ }
+
+ EnsureComponentLibraryCollapsePresenter();
+ if (_componentLibraryCollapsePresenter is null)
+ {
+ return;
+ }
+
+ _componentLibraryCollapsePresenter.SyncExpandedState(ComponentLibraryWindow.Margin, ComponentLibraryWindow.Opacity);
+ }
+
+ private void CollapseComponentLibraryForDesktopEdit(string? title)
+ {
+ if (!_isComponentLibraryOpen)
+ {
+ return;
+ }
+
+ EnsureComponentLibraryCollapsePresenter();
+ if (_componentLibraryCollapsePresenter is null)
+ {
+ return;
+ }
+
+ SyncComponentLibraryCollapseExpandedState();
+ _componentLibraryCollapsePresenter.Collapse(ResolveComponentLibraryCollapsedChipTitle());
+ }
+
+ private string ResolveComponentLibraryCollapsedChipTitle()
+ {
+ if (!string.IsNullOrWhiteSpace(ComponentLibraryTitleTextBlock?.Text))
+ {
+ return ComponentLibraryTitleTextBlock.Text;
+ }
+
+ return L("button.component_library", "Widgets");
+ }
+
+ private void RestoreComponentLibraryAfterDesktopEdit()
+ {
+ EnsureComponentLibraryCollapsePresenter();
+ if (_componentLibraryCollapsePresenter is null)
+ {
+ return;
+ }
+
+ _componentLibraryCollapsePresenter.Restore();
+ }
+
private bool TryGetCurrentDesktopGridGeometry(out DesktopGridGeometry geometry)
{
geometry = default;
@@ -109,6 +193,7 @@ public partial class MainWindow
private Rect? GetComponentLibraryBoundsInViewport()
{
if (!_isComponentLibraryOpen ||
+ IsComponentLibraryTemporarilyCollapsedForDesktopEdit() ||
ComponentLibraryWindow is null ||
DesktopPagesViewport is null ||
!ComponentLibraryWindow.IsVisible ||
@@ -214,6 +299,8 @@ public partial class MainWindow
private void CancelDesktopEditSession(bool animate)
{
+ RestoreComponentLibraryAfterDesktopEdit();
+
if (_isDesktopEditCommitPending)
{
_desktopEditCommitVersion++;
@@ -273,11 +360,13 @@ public partial class MainWindow
if (!CanCommitDesktopEditAtRect(finalRect))
{
+ RestoreComponentLibraryAfterDesktopEdit();
ResetDesktopEditState();
return;
}
commitAction();
+ RestoreComponentLibraryAfterDesktopEdit();
ResetDesktopEditState();
},
DesktopEditOverlayAnimationDuration);
@@ -319,8 +408,10 @@ public partial class MainWindow
}
_desktopEditSession = _desktopEditSession.PromoteToDraggingNew();
+ CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(_desktopEditSession.ComponentId ?? string.Empty));
+ _desktopEditSession = _desktopEditSession.WithComponentLibraryBounds(GetComponentLibraryBoundsInViewport());
EnsureDesktopEditOverlayPresenter();
- _desktopEditOverlayPresenter?.Show();
+ _desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.ElevatedFromLibrary);
UpdateActiveDesktopDragPreview();
}
diff --git a/LanMountainDesktop/Views/MainWindow.axaml b/LanMountainDesktop/Views/MainWindow.axaml
index e75ca27..ef41412 100644
--- a/LanMountainDesktop/Views/MainWindow.axaml
+++ b/LanMountainDesktop/Views/MainWindow.axaml
@@ -588,6 +588,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+