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 @@ + + + + + + + + + + + + +