mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 01:44: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.False(DesktopPlacementMath.CanCommitPlacement(placementRect, occludingLibraryBounds));
|
||||||
Assert.True(DesktopPlacementMath.CanCommitPlacement(placementRect, distantLibraryBounds));
|
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;
|
namespace LanMountainDesktop.DesktopEditing;
|
||||||
|
|
||||||
|
internal enum DesktopEditGhostVisualStyle
|
||||||
|
{
|
||||||
|
StandardLift = 0,
|
||||||
|
ElevatedFromLibrary
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed class DesktopEditOverlayPresenter
|
internal sealed class DesktopEditOverlayPresenter
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan FastDuration = FluttermotionToken.Fast;
|
private static readonly TimeSpan FastDuration = FluttermotionToken.Fast;
|
||||||
@@ -133,14 +139,16 @@ internal sealed class DesktopEditOverlayPresenter
|
|||||||
UpdateCandidateAppearance();
|
UpdateCandidateAppearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Show()
|
public void Show(DesktopEditGhostVisualStyle visualStyle = DesktopEditGhostVisualStyle.StandardLift)
|
||||||
{
|
{
|
||||||
_dismissVersion++;
|
_dismissVersion++;
|
||||||
_isVisible = true;
|
_isVisible = true;
|
||||||
_root.IsVisible = true;
|
_root.IsVisible = true;
|
||||||
_root.Opacity = 0;
|
_root.Opacity = 0;
|
||||||
_ghostView.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;
|
_candidateOutline.Opacity = 0;
|
||||||
_candidateScale.ScaleX = 0.96;
|
_candidateScale.ScaleX = 0.96;
|
||||||
_candidateScale.ScaleY = 0.96;
|
_candidateScale.ScaleY = 0.96;
|
||||||
@@ -154,7 +162,7 @@ internal sealed class DesktopEditOverlayPresenter
|
|||||||
|
|
||||||
_root.Opacity = 1;
|
_root.Opacity = 1;
|
||||||
_ghostView.Opacity = 1;
|
_ghostView.Opacity = 1;
|
||||||
_ghostView.SetRestingScale(1);
|
_ghostView.SetRestingScale(targetGhostScale);
|
||||||
if (_candidateRect.HasValue)
|
if (_candidateRect.HasValue)
|
||||||
{
|
{
|
||||||
_candidateOutline.Opacity = 1;
|
_candidateOutline.Opacity = 1;
|
||||||
|
|||||||
@@ -453,6 +453,7 @@ public partial class MainWindow
|
|||||||
_isComponentLibraryOpen = true;
|
_isComponentLibraryOpen = true;
|
||||||
UpdateDesktopComponentHostEditState();
|
UpdateDesktopComponentHostEditState();
|
||||||
ShowComponentLibraryCategoryView();
|
ShowComponentLibraryCategoryView();
|
||||||
|
RestoreComponentLibraryAfterDesktopEdit();
|
||||||
ComponentLibraryWindow.IsVisible = true;
|
ComponentLibraryWindow.IsVisible = true;
|
||||||
ComponentLibraryWindow.Opacity = 0;
|
ComponentLibraryWindow.Opacity = 0;
|
||||||
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
||||||
@@ -467,6 +468,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
BuildComponentLibraryCategoryPages();
|
BuildComponentLibraryCategoryPages();
|
||||||
ComponentLibraryWindow.Opacity = 1;
|
ComponentLibraryWindow.Opacity = 1;
|
||||||
|
SyncComponentLibraryCollapseExpandedState();
|
||||||
}, DispatcherPriority.Background);
|
}, DispatcherPriority.Background);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,6 +479,7 @@ public partial class MainWindow
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RestoreComponentLibraryAfterDesktopEdit();
|
||||||
_isComponentLibraryOpen = false;
|
_isComponentLibraryOpen = false;
|
||||||
CancelDesktopComponentDrag();
|
CancelDesktopComponentDrag();
|
||||||
CancelDesktopComponentResize(restoreOriginalSpan: true);
|
CancelDesktopComponentResize(restoreOriginalSpan: true);
|
||||||
@@ -2056,13 +2059,14 @@ public partial class MainWindow
|
|||||||
pointerOffset,
|
pointerOffset,
|
||||||
GetComponentLibraryBoundsInViewport());
|
GetComponentLibraryBoundsInViewport());
|
||||||
|
|
||||||
|
CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(placement.ComponentId));
|
||||||
SetDesktopEditSourceHost(sourceHost, 0.22);
|
SetDesktopEditSourceHost(sourceHost, 0.22);
|
||||||
EnsureDesktopEditOverlayPresenter();
|
EnsureDesktopEditOverlayPresenter();
|
||||||
UpdateDesktopEditOverlayMetadata(placement.ComponentId, widthCells, heightCells, L("component.move", "Move"));
|
UpdateDesktopEditOverlayMetadata(placement.ComponentId, widthCells, heightCells, L("component.move", "Move"));
|
||||||
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
|
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
|
||||||
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
|
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
|
||||||
_desktopEditOverlayPresenter?.SetInvalid(false);
|
_desktopEditOverlayPresenter?.SetInvalid(false);
|
||||||
_desktopEditOverlayPresenter?.Show();
|
_desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.StandardLift);
|
||||||
UpdateDesktopEditSession(pointerInViewport);
|
UpdateDesktopEditSession(pointerInViewport);
|
||||||
|
|
||||||
e.Pointer.Capture(this);
|
e.Pointer.Capture(this);
|
||||||
@@ -2208,13 +2212,14 @@ public partial class MainWindow
|
|||||||
TargetColumn = placement.Column
|
TargetColumn = placement.Column
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(placement.ComponentId));
|
||||||
SetDesktopEditSourceHost(sourceHost, 0.22);
|
SetDesktopEditSourceHost(sourceHost, 0.22);
|
||||||
EnsureDesktopEditOverlayPresenter();
|
EnsureDesktopEditOverlayPresenter();
|
||||||
UpdateDesktopEditOverlayMetadata(placement.ComponentId, startSpan.WidthCells, startSpan.HeightCells, L("component.resize", "Resize"));
|
UpdateDesktopEditOverlayMetadata(placement.ComponentId, startSpan.WidthCells, startSpan.HeightCells, L("component.resize", "Resize"));
|
||||||
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
|
_desktopEditOverlayPresenter?.SetPreviewRect(_desktopEditOriginalRect);
|
||||||
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
|
_desktopEditOverlayPresenter?.SetCandidateRect(_desktopEditOriginalRect);
|
||||||
_desktopEditOverlayPresenter?.SetInvalid(false);
|
_desktopEditOverlayPresenter?.SetInvalid(false);
|
||||||
_desktopEditOverlayPresenter?.Show();
|
_desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.StandardLift);
|
||||||
UpdateDesktopEditSession(pointerInViewport);
|
UpdateDesktopEditSession(pointerInViewport);
|
||||||
e.Pointer.Capture(this);
|
e.Pointer.Capture(this);
|
||||||
}
|
}
|
||||||
@@ -2883,7 +2888,7 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void OnComponentLibraryWindowPointerPressed(object? sender, PointerPressedEventArgs e)
|
private void OnComponentLibraryWindowPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (ComponentLibraryWindow is null || !_isComponentLibraryOpen)
|
if (ComponentLibraryWindow is null || !_isComponentLibraryOpen || IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2904,6 +2909,17 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void OnComponentLibraryWindowPointerMoved(object? sender, PointerEventArgs e)
|
private void OnComponentLibraryWindowPointerMoved(object? sender, PointerEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
|
||||||
|
{
|
||||||
|
if (_isComponentLibraryWindowDragging)
|
||||||
|
{
|
||||||
|
_isComponentLibraryWindowDragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_isComponentLibraryWindowDragging || ComponentLibraryWindow is null)
|
if (!_isComponentLibraryWindowDragging || ComponentLibraryWindow is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -2920,11 +2936,23 @@ public partial class MainWindow
|
|||||||
);
|
);
|
||||||
|
|
||||||
ComponentLibraryWindow.Margin = newMargin;
|
ComponentLibraryWindow.Margin = newMargin;
|
||||||
|
SyncComponentLibraryCollapseExpandedState();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnComponentLibraryWindowPointerReleased(object? sender, PointerReleasedEventArgs e)
|
private void OnComponentLibraryWindowPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (IsComponentLibraryTemporarilyCollapsedForDesktopEdit())
|
||||||
|
{
|
||||||
|
if (_isComponentLibraryWindowDragging)
|
||||||
|
{
|
||||||
|
_isComponentLibraryWindowDragging = false;
|
||||||
|
e.Pointer.Capture(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_isComponentLibraryWindowDragging)
|
if (!_isComponentLibraryWindowDragging)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -3111,6 +3139,7 @@ public partial class MainWindow
|
|||||||
var margin = ComponentLibraryWindow.Margin;
|
var margin = ComponentLibraryWindow.Margin;
|
||||||
_savedComponentLibraryMargin = margin;
|
_savedComponentLibraryMargin = margin;
|
||||||
_isComponentLibraryWindowPositionCustomized = true;
|
_isComponentLibraryWindowPositionCustomized = true;
|
||||||
|
SyncComponentLibraryCollapseExpandedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestoreComponentLibraryWindowPosition()
|
private void RestoreComponentLibraryWindowPosition()
|
||||||
@@ -3121,6 +3150,7 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
ComponentLibraryWindow.Margin = _savedComponentLibraryMargin;
|
ComponentLibraryWindow.Margin = _savedComponentLibraryMargin;
|
||||||
|
SyncComponentLibraryCollapseExpandedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Thickness _savedComponentLibraryMargin = new Thickness(24, 24, 24, 100);
|
private Thickness _savedComponentLibraryMargin = new Thickness(24, 24, 24, 100);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public partial class MainWindow
|
|||||||
private int _desktopEditOverlayVersion;
|
private int _desktopEditOverlayVersion;
|
||||||
private int _desktopEditCommitVersion;
|
private int _desktopEditCommitVersion;
|
||||||
private bool _isDesktopEditCommitPending;
|
private bool _isDesktopEditCommitPending;
|
||||||
|
private ComponentLibraryCollapsePresenter? _componentLibraryCollapsePresenter;
|
||||||
|
|
||||||
private bool HasActiveDesktopEditSession => _desktopEditSession.IsActive || _isDesktopEditCommitPending;
|
private bool HasActiveDesktopEditSession => _desktopEditSession.IsActive || _isDesktopEditCommitPending;
|
||||||
|
|
||||||
@@ -79,6 +80,89 @@ public partial class MainWindow
|
|||||||
_desktopEditOverlayPresenter.SetViewportSize(new Size(width, height));
|
_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)
|
private bool TryGetCurrentDesktopGridGeometry(out DesktopGridGeometry geometry)
|
||||||
{
|
{
|
||||||
geometry = default;
|
geometry = default;
|
||||||
@@ -109,6 +193,7 @@ public partial class MainWindow
|
|||||||
private Rect? GetComponentLibraryBoundsInViewport()
|
private Rect? GetComponentLibraryBoundsInViewport()
|
||||||
{
|
{
|
||||||
if (!_isComponentLibraryOpen ||
|
if (!_isComponentLibraryOpen ||
|
||||||
|
IsComponentLibraryTemporarilyCollapsedForDesktopEdit() ||
|
||||||
ComponentLibraryWindow is null ||
|
ComponentLibraryWindow is null ||
|
||||||
DesktopPagesViewport is null ||
|
DesktopPagesViewport is null ||
|
||||||
!ComponentLibraryWindow.IsVisible ||
|
!ComponentLibraryWindow.IsVisible ||
|
||||||
@@ -214,6 +299,8 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private void CancelDesktopEditSession(bool animate)
|
private void CancelDesktopEditSession(bool animate)
|
||||||
{
|
{
|
||||||
|
RestoreComponentLibraryAfterDesktopEdit();
|
||||||
|
|
||||||
if (_isDesktopEditCommitPending)
|
if (_isDesktopEditCommitPending)
|
||||||
{
|
{
|
||||||
_desktopEditCommitVersion++;
|
_desktopEditCommitVersion++;
|
||||||
@@ -273,11 +360,13 @@ public partial class MainWindow
|
|||||||
|
|
||||||
if (!CanCommitDesktopEditAtRect(finalRect))
|
if (!CanCommitDesktopEditAtRect(finalRect))
|
||||||
{
|
{
|
||||||
|
RestoreComponentLibraryAfterDesktopEdit();
|
||||||
ResetDesktopEditState();
|
ResetDesktopEditState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
commitAction();
|
commitAction();
|
||||||
|
RestoreComponentLibraryAfterDesktopEdit();
|
||||||
ResetDesktopEditState();
|
ResetDesktopEditState();
|
||||||
},
|
},
|
||||||
DesktopEditOverlayAnimationDuration);
|
DesktopEditOverlayAnimationDuration);
|
||||||
@@ -319,8 +408,10 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
_desktopEditSession = _desktopEditSession.PromoteToDraggingNew();
|
_desktopEditSession = _desktopEditSession.PromoteToDraggingNew();
|
||||||
|
CollapseComponentLibraryForDesktopEdit(ResolveDesktopEditTitle(_desktopEditSession.ComponentId ?? string.Empty));
|
||||||
|
_desktopEditSession = _desktopEditSession.WithComponentLibraryBounds(GetComponentLibraryBoundsInViewport());
|
||||||
EnsureDesktopEditOverlayPresenter();
|
EnsureDesktopEditOverlayPresenter();
|
||||||
_desktopEditOverlayPresenter?.Show();
|
_desktopEditOverlayPresenter?.Show(DesktopEditGhostVisualStyle.ElevatedFromLibrary);
|
||||||
UpdateActiveDesktopDragPreview();
|
UpdateActiveDesktopDragPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -588,6 +588,44 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</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>
|
</Grid>
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
Reference in New Issue
Block a user