mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.7.5
顺滑的组件放置与调整
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Border>("ComponentLibraryCollapsedChipHost");
|
||||
var collapsedChipTextBlock = this.FindControl<TextBlock>("ComponentLibraryCollapsedChipTextBlock");
|
||||
var collapsedChipIcon = this.FindControl<Control>("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();
|
||||
}
|
||||
|
||||
|
||||
@@ -588,6 +588,44 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="ComponentLibraryCollapsedChipHost"
|
||||
IsVisible="False"
|
||||
IsHitTestVisible="False"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0,0,0,88"
|
||||
Padding="14,10"
|
||||
CornerRadius="999"
|
||||
Classes="surface-translucent-strong"
|
||||
BorderBrush="{DynamicResource AdaptiveDockGlassBorderBrush}"
|
||||
BorderThickness="1"
|
||||
Opacity="0">
|
||||
<Border.Transitions>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="{StaticResource FluttermotionToken.Duration.Fast}" />
|
||||
</Transitions>
|
||||
</Border.Transitions>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*"
|
||||
ColumnSpacing="10">
|
||||
<fi:FluentIcon x:Name="ComponentLibraryCollapsedChipIcon"
|
||||
Grid.Column="0"
|
||||
Icon="Apps"
|
||||
IconVariant="Regular"
|
||||
Width="18"
|
||||
Height="18"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
|
||||
<TextBlock x:Name="ComponentLibraryCollapsedChipTextBlock"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
|
||||
Text="Widgets" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user