2026-03-02 20:02:14 +08:00
|
|
|
|
using System;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using Avalonia;
|
|
|
|
|
|
using Avalonia.Controls;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
using Avalonia.Controls.Shapes;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
using Avalonia.Input;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
using Avalonia.Interactivity;
|
2026-03-01 00:34:07 +08:00
|
|
|
|
using Avalonia.Layout;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
using Avalonia.Media;
|
|
|
|
|
|
using Avalonia.Threading;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
using Avalonia.VisualTree;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
using FluentIcons.Avalonia;
|
|
|
|
|
|
using FluentIcons.Common;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
using LanMontainDesktop.ComponentSystem;
|
|
|
|
|
|
using LanMontainDesktop.Models;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
using LanMontainDesktop.Views.Components;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
namespace LanMontainDesktop.Views;
|
|
|
|
|
|
|
|
|
|
|
|
public partial class MainWindow
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private readonly List<DesktopComponentPlacementSnapshot> _desktopComponentPlacements = [];
|
|
|
|
|
|
private readonly Dictionary<int, Grid> _desktopPageComponentGrids = new();
|
|
|
|
|
|
|
|
|
|
|
|
private const string DesktopComponentClass = "desktop-component";
|
|
|
|
|
|
private const string DesktopComponentHostClass = "desktop-component-host";
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private const string DesktopComponentContentHostTag = "desktop-component-content-host";
|
|
|
|
|
|
private const string DesktopComponentResizeHandleTag = "desktop-component-resize-handle";
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
private bool _isDesktopComponentDragActive;
|
|
|
|
|
|
private DesktopComponentDragState? _desktopComponentDrag;
|
|
|
|
|
|
private Border? _desktopComponentDragGhost;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private bool _isDesktopComponentResizeActive;
|
|
|
|
|
|
private DesktopComponentResizeState? _desktopComponentResize;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
private string? _componentLibraryActiveCategoryId;
|
|
|
|
|
|
private int _componentLibraryCategoryIndex;
|
|
|
|
|
|
private int _componentLibraryComponentIndex;
|
|
|
|
|
|
private double _componentLibraryCategoryPageWidth;
|
|
|
|
|
|
private double _componentLibraryComponentPageWidth;
|
|
|
|
|
|
private TranslateTransform? _componentLibraryCategoryHostTransform;
|
|
|
|
|
|
private TranslateTransform? _componentLibraryComponentHostTransform;
|
|
|
|
|
|
private IReadOnlyList<ComponentLibraryCategory> _componentLibraryCategories = Array.Empty<ComponentLibraryCategory>();
|
|
|
|
|
|
private IReadOnlyList<DesktopComponentDefinition> _componentLibraryActiveComponents = Array.Empty<DesktopComponentDefinition>();
|
|
|
|
|
|
private bool _isComponentLibraryCategoryGestureActive;
|
|
|
|
|
|
private bool _isComponentLibraryComponentGestureActive;
|
|
|
|
|
|
private Point _componentLibraryCategoryGestureStartPoint;
|
|
|
|
|
|
private Point _componentLibraryCategoryGestureCurrentPoint;
|
|
|
|
|
|
private double _componentLibraryCategoryGestureBaseOffset;
|
|
|
|
|
|
private Point _componentLibraryComponentGestureStartPoint;
|
|
|
|
|
|
private Point _componentLibraryComponentGestureCurrentPoint;
|
|
|
|
|
|
private double _componentLibraryComponentGestureBaseOffset;
|
|
|
|
|
|
|
|
|
|
|
|
private enum DesktopComponentDragKind
|
|
|
|
|
|
{
|
|
|
|
|
|
None,
|
|
|
|
|
|
NewFromLibrary,
|
|
|
|
|
|
MoveExisting
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private sealed class DesktopComponentDragState
|
|
|
|
|
|
{
|
|
|
|
|
|
public DesktopComponentDragKind Kind { get; init; }
|
|
|
|
|
|
public string ComponentId { get; init; } = string.Empty;
|
|
|
|
|
|
public string PlacementId { get; init; } = string.Empty;
|
|
|
|
|
|
public int PageIndex { get; init; }
|
|
|
|
|
|
public int WidthCells { get; init; }
|
|
|
|
|
|
public int HeightCells { get; init; }
|
|
|
|
|
|
public Point PointerOffset { get; init; }
|
|
|
|
|
|
public Border? SourceHost { get; init; }
|
|
|
|
|
|
public int TargetRow { get; set; }
|
|
|
|
|
|
public int TargetColumn { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private sealed class DesktopComponentResizeState
|
|
|
|
|
|
{
|
|
|
|
|
|
public string PlacementId { get; init; } = string.Empty;
|
|
|
|
|
|
public string ComponentId { get; init; } = string.Empty;
|
|
|
|
|
|
public Border SourceHost { get; init; } = null!;
|
|
|
|
|
|
public int StartWidthCells { get; init; }
|
|
|
|
|
|
public int StartHeightCells { get; init; }
|
|
|
|
|
|
public int MinWidthCells { get; init; }
|
|
|
|
|
|
public int MinHeightCells { get; init; }
|
|
|
|
|
|
public int MaxWidthCells { get; init; }
|
|
|
|
|
|
public int MaxHeightCells { get; init; }
|
|
|
|
|
|
public Point StartPointerInViewport { get; init; }
|
|
|
|
|
|
public int CurrentWidthCells { get; set; }
|
|
|
|
|
|
public int CurrentHeightCells { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private sealed record ComponentLibraryCategory(
|
|
|
|
|
|
string Id,
|
|
|
|
|
|
Symbol Icon,
|
|
|
|
|
|
string Title,
|
|
|
|
|
|
IReadOnlyList<DesktopComponentDefinition> Components);
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
// "Desktop edit" toggle. While editing, show the component library window.
|
2026-02-28 12:30:16 +08:00
|
|
|
|
if (_isComponentLibraryOpen)
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
CloseComponentLibraryWindow(reopenSettings: false);
|
2026-02-28 12:30:16 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_reopenSettingsAfterComponentLibraryClose = _isSettingsOpen;
|
|
|
|
|
|
if (_isSettingsOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
CloseSettingsPage(immediate: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
OpenComponentLibraryWindow();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
CloseComponentLibraryWindow(reopenSettings: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void OnCloseComponentSettingsClick(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
CloseComponentSettingsWindow();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
private void OnStatusBarClockChecked(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_suppressStatusBarToggleEvents)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_topStatusComponentIds.Add(BuiltInComponentIds.Clock);
|
|
|
|
|
|
ApplyTopStatusComponentVisibility();
|
|
|
|
|
|
UpdateWallpaperPreviewLayout();
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnStatusBarClockUnchecked(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_suppressStatusBarToggleEvents)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_topStatusComponentIds.Remove(BuiltInComponentIds.Clock);
|
|
|
|
|
|
ApplyTopStatusComponentVisibility();
|
|
|
|
|
|
UpdateWallpaperPreviewLayout();
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyTaskbarSettings(AppSettingsSnapshot snapshot)
|
|
|
|
|
|
{
|
|
|
|
|
|
_topStatusComponentIds.Clear();
|
|
|
|
|
|
if (snapshot.TopStatusComponentIds is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var componentId in snapshot.TopStatusComponentIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(componentId))
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var normalizedId = componentId.Trim();
|
|
|
|
|
|
if (_componentRegistry.IsKnownComponent(normalizedId) &&
|
|
|
|
|
|
_componentRegistry.AllowsStatusBarPlacement(normalizedId))
|
|
|
|
|
|
{
|
|
|
|
|
|
_topStatusComponentIds.Add(normalizedId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_pinnedTaskbarActions.Clear();
|
|
|
|
|
|
if (snapshot.PinnedTaskbarActions is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var actionText in snapshot.PinnedTaskbarActions)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Enum.TryParse<TaskbarActionId>(actionText, ignoreCase: true, out var action))
|
|
|
|
|
|
{
|
|
|
|
|
|
_pinnedTaskbarActions.Add(action);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_pinnedTaskbarActions.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var action in DefaultPinnedTaskbarActions)
|
|
|
|
|
|
{
|
|
|
|
|
|
_pinnedTaskbarActions.Add(action);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_enableDynamicTaskbarActions = snapshot.EnableDynamicTaskbarActions;
|
|
|
|
|
|
_taskbarLayoutMode = string.IsNullOrWhiteSpace(snapshot.TaskbarLayoutMode)
|
|
|
|
|
|
? TaskbarLayoutBottomFullRowMacStyle
|
|
|
|
|
|
: snapshot.TaskbarLayoutMode;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
_clockDisplayFormat = snapshot.ClockDisplayFormat == "HourMinute"
|
|
|
|
|
|
? ClockDisplayFormat.HourMinute
|
|
|
|
|
|
: ClockDisplayFormat.HourMinuteSecond;
|
|
|
|
|
|
|
|
|
|
|
|
if (ClockWidget is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClockWidget.SetDisplayFormat(_clockDisplayFormat);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_clockDisplayFormat == ClockDisplayFormat.HourMinute)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ClockFormatHMRadio is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClockFormatHMRadio.IsChecked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ClockFormatHMSSRadio is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClockFormatHMSSRadio.IsChecked = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyTopStatusComponentVisibility()
|
|
|
|
|
|
{
|
|
|
|
|
|
var showClock = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
|
|
|
|
|
|
|
|
|
|
|
|
if (ClockWidget is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClockWidget.IsVisible = showClock;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (showClock)
|
|
|
|
|
|
{
|
|
|
|
|
|
ClockWidget.SetDisplayFormat(_clockDisplayFormat);
|
|
|
|
|
|
var columnSpan = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? 2 : 3;
|
|
|
|
|
|
Grid.SetColumnSpan(ClockWidget, columnSpan);
|
|
|
|
|
|
}
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (WallpaperPreviewClockWidget is not null)
|
2026-02-28 12:30:16 +08:00
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
WallpaperPreviewClockWidget.IsVisible = showClock;
|
|
|
|
|
|
if (showClock)
|
|
|
|
|
|
{
|
|
|
|
|
|
WallpaperPreviewClockWidget.SetDisplayFormat(_clockDisplayFormat);
|
|
|
|
|
|
}
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private TaskbarContext GetCurrentTaskbarContext()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isSettingsOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return TaskbarContext.Desktop;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return SettingsNavListBox?.SelectedIndex switch
|
|
|
|
|
|
{
|
|
|
|
|
|
0 => TaskbarContext.SettingsWallpaper,
|
|
|
|
|
|
1 => TaskbarContext.SettingsGrid,
|
|
|
|
|
|
2 => TaskbarContext.SettingsColor,
|
|
|
|
|
|
3 => TaskbarContext.SettingsStatusBar,
|
|
|
|
|
|
4 => TaskbarContext.SettingsRegion,
|
|
|
|
|
|
_ => TaskbarContext.Desktop
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyTaskbarActionVisibility(TaskbarContext context)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (BackToWindowsButton is null ||
|
|
|
|
|
|
OpenComponentLibraryButton is null ||
|
|
|
|
|
|
OpenSettingsButton is null ||
|
|
|
|
|
|
WallpaperPreviewBackButtonVisual is null ||
|
|
|
|
|
|
WallpaperPreviewComponentLibraryVisual is null ||
|
|
|
|
|
|
WallpaperPreviewSettingsButtonIcon is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
|
|
|
|
|
|
var showSettings = _pinnedTaskbarActions.Contains(TaskbarActionId.OpenSettings);
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var showDesktopEdit = _isSettingsOpen;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
BackToWindowsButton.IsVisible = showMinimize;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
OpenComponentLibraryButton.IsVisible = showDesktopEdit;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
OpenSettingsButton.IsVisible = showSettings;
|
|
|
|
|
|
WallpaperPreviewBackButtonVisual.IsVisible = showMinimize;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
WallpaperPreviewComponentLibraryVisual.IsVisible = showDesktopEdit;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
WallpaperPreviewSettingsButtonIcon.IsVisible = showSettings;
|
|
|
|
|
|
|
|
|
|
|
|
if (TaskbarFixedActionsHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
TaskbarFixedActionsHost.IsVisible = showMinimize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (TaskbarSettingsActionHost is not null)
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
TaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (WallpaperPreviewTaskbarFixedActionsHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
WallpaperPreviewTaskbarFixedActionsHost.IsVisible = showMinimize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (WallpaperPreviewTaskbarSettingsActionHost is not null)
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
WallpaperPreviewTaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var dynamicActions = ResolveDynamicTaskbarActions(context)
|
|
|
|
|
|
.Where(action => action.IsVisible)
|
|
|
|
|
|
.ToList();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
var hasDynamicActions = dynamicActions.Count > 0;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
BuildDynamicTaskbarVisuals(dynamicActions, _currentDesktopCellSize);
|
2026-02-28 12:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
if (TaskbarDynamicActionsHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
TaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
WallpaperPreviewTaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateOpenSettingsActionVisualState();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateOpenSettingsActionVisualState()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (OpenSettingsButtonTextBlock is null || OpenSettingsButton is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var showBackToDesktop = _isSettingsOpen;
|
|
|
|
|
|
OpenSettingsButtonTextBlock.IsVisible = showBackToDesktop;
|
|
|
|
|
|
OpenSettingsButtonTextBlock.Text = L("settings.back_to_desktop", "Back to Desktop");
|
|
|
|
|
|
ToolTip.SetTip(
|
|
|
|
|
|
OpenSettingsButton,
|
|
|
|
|
|
showBackToDesktop
|
|
|
|
|
|
? L("settings.back_to_desktop", "Back to Desktop")
|
|
|
|
|
|
: L("tooltip.open_settings", "Settings"));
|
|
|
|
|
|
|
|
|
|
|
|
var effectiveCellSize = _currentDesktopCellSize > 0
|
|
|
|
|
|
? _currentDesktopCellSize
|
|
|
|
|
|
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
|
|
|
|
|
|
ApplyWidgetSizing(effectiveCellSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OpenComponentLibraryWindow()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryOpen = true;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
UpdateDesktopComponentHostEditState();
|
|
|
|
|
|
ShowComponentLibraryCategoryView();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
ComponentLibraryWindow.IsVisible = true;
|
|
|
|
|
|
ComponentLibraryWindow.Opacity = 0;
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
2026-03-02 20:02:14 +08:00
|
|
|
|
RestoreComponentLibraryWindowPosition();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
BuildComponentLibraryCategoryPages();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
ComponentLibraryWindow.Opacity = 1;
|
|
|
|
|
|
}, DispatcherPriority.Background);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CloseComponentLibraryWindow(bool reopenSettings)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryOpen = false;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
CancelDesktopComponentDrag();
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CancelDesktopComponentResize(restoreOriginalSpan: true);
|
|
|
|
|
|
ClearDesktopComponentSelection();
|
2026-03-01 16:50:06 +08:00
|
|
|
|
UpdateDesktopComponentHostEditState();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
ComponentLibraryWindow.Opacity = 0;
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
|
|
|
|
|
|
|
|
|
|
DispatcherTimer.RunOnce(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_isComponentLibraryOpen || ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryWindow.IsVisible = false;
|
|
|
|
|
|
|
|
|
|
|
|
var shouldReopenSettings = reopenSettings && _reopenSettingsAfterComponentLibraryClose;
|
|
|
|
|
|
_reopenSettingsAfterComponentLibraryClose = false;
|
|
|
|
|
|
if (shouldReopenSettings)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenSettingsPage();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, TimeSpan.FromMilliseconds(200));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void InitializeDesktopComponentDragHandlers()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Global handlers: we capture the pointer during drag, then track move/release anywhere.
|
|
|
|
|
|
AddHandler(PointerMovedEvent, OnDesktopComponentDragPointerMoved, RoutingStrategies.Tunnel);
|
|
|
|
|
|
AddHandler(PointerReleasedEvent, OnDesktopComponentDragPointerReleased, RoutingStrategies.Tunnel);
|
|
|
|
|
|
AddHandler(PointerCaptureLostEvent, OnDesktopComponentDragPointerCaptureLost, RoutingStrategies.Tunnel);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
private IReadOnlyList<TaskbarActionItem> ResolveDynamicTaskbarActions(TaskbarContext context)
|
|
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
if (context == TaskbarContext.Desktop && _isComponentLibraryOpen)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var actions = new List<TaskbarActionItem>();
|
|
|
|
|
|
if (_selectedDesktopComponentHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
actions.Add(new TaskbarActionItem(
|
|
|
|
|
|
TaskbarActionId.DeleteComponent,
|
|
|
|
|
|
L("component.delete", "Delete"),
|
|
|
|
|
|
"Delete",
|
|
|
|
|
|
IsVisible: true,
|
|
|
|
|
|
CommandKey: "component.delete"));
|
|
|
|
|
|
|
|
|
|
|
|
actions.Add(new TaskbarActionItem(
|
|
|
|
|
|
TaskbarActionId.EditComponent,
|
|
|
|
|
|
L("component.edit", "Edit"),
|
|
|
|
|
|
"Edit",
|
|
|
|
|
|
IsVisible: true,
|
|
|
|
|
|
CommandKey: "component.edit"));
|
|
|
|
|
|
|
|
|
|
|
|
return actions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var canAddPage = _desktopPageCount < MaxDesktopPageCount;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var canDeletePage = _desktopPageCount > MinDesktopPageCount;
|
|
|
|
|
|
|
|
|
|
|
|
if (canAddPage)
|
|
|
|
|
|
{
|
|
|
|
|
|
actions.Add(new TaskbarActionItem(
|
2026-03-01 16:50:06 +08:00
|
|
|
|
TaskbarActionId.AddDesktopPage,
|
|
|
|
|
|
L("desktop.add_page", "Add page"),
|
|
|
|
|
|
"Add",
|
2026-03-02 20:02:14 +08:00
|
|
|
|
IsVisible: true,
|
|
|
|
|
|
CommandKey: "desktop.add_page"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (canDeletePage)
|
|
|
|
|
|
{
|
|
|
|
|
|
actions.Add(new TaskbarActionItem(
|
|
|
|
|
|
TaskbarActionId.DeleteDesktopPage,
|
|
|
|
|
|
L("desktop.delete_page", "Delete page"),
|
|
|
|
|
|
"Delete",
|
|
|
|
|
|
IsVisible: true,
|
|
|
|
|
|
CommandKey: "desktop.delete_page"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return actions;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
if (!_enableDynamicTaskbarActions)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Array.Empty<TaskbarActionItem>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_ = context;
|
|
|
|
|
|
return Array.Empty<TaskbarActionItem>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void BuildDynamicTaskbarVisuals(IReadOnlyList<TaskbarActionItem> actions, double cellSize)
|
2026-02-28 12:30:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (TaskbarDynamicActionsPanel is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
TaskbarDynamicActionsPanel.Children.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 00:34:07 +08:00
|
|
|
|
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
|
2026-02-28 12:30:16 +08:00
|
|
|
|
{
|
2026-03-01 00:34:07 +08:00
|
|
|
|
WallpaperPreviewTaskbarDynamicActionsHost.Children.Clear();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (actions.Count == 0 ||
|
|
|
|
|
|
TaskbarDynamicActionsPanel is null ||
|
2026-03-01 00:34:07 +08:00
|
|
|
|
WallpaperPreviewTaskbarDynamicActionsHost is null)
|
2026-02-28 12:30:16 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
// Match taskbar typographic scale to the current grid cell size.
|
|
|
|
|
|
var taskbarCellHeight = Math.Clamp(cellSize * 0.76, 36, 76);
|
|
|
|
|
|
var fontSize = Math.Clamp(taskbarCellHeight * 0.36, 11, 22);
|
|
|
|
|
|
var iconSize = Math.Clamp(taskbarCellHeight * 0.44, 12, 26);
|
|
|
|
|
|
var padding = Math.Clamp(taskbarCellHeight * 0.20, 6, 14);
|
|
|
|
|
|
var cornerRadius = Math.Clamp(taskbarCellHeight * 0.32, 8, 16);
|
|
|
|
|
|
var spacing = Math.Clamp(taskbarCellHeight * 0.18, 4, 10);
|
|
|
|
|
|
|
|
|
|
|
|
var pageCountText = $"{_currentDesktopSurfaceIndex + 1}/{_desktopPageCount}";
|
|
|
|
|
|
var pageCountBlock = new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = pageCountText,
|
|
|
|
|
|
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush"),
|
|
|
|
|
|
FontSize = fontSize,
|
|
|
|
|
|
FontWeight = FontWeight.SemiBold,
|
|
|
|
|
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
|
|
|
|
|
|
Margin = new Thickness(0, 0, spacing, 0)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var pageCountContainer = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Background = GetThemeBrush("AdaptiveButtonBackgroundBrush"),
|
|
|
|
|
|
CornerRadius = new CornerRadius(cornerRadius),
|
|
|
|
|
|
Padding = new Thickness(padding),
|
|
|
|
|
|
Child = pageCountBlock,
|
|
|
|
|
|
Margin = new Thickness(0, 0, spacing, 0)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
TaskbarDynamicActionsPanel.Children.Add(pageCountContainer);
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
foreach (var action in actions)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!action.IsVisible)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var isDeleteAction = action.Id == TaskbarActionId.DeleteDesktopPage ||
|
|
|
|
|
|
action.Id == TaskbarActionId.DeleteComponent;
|
|
|
|
|
|
var isEditAction = action.Id == TaskbarActionId.EditComponent;
|
|
|
|
|
|
|
|
|
|
|
|
Symbol iconSymbol;
|
|
|
|
|
|
if (isDeleteAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
iconSymbol = Symbol.Delete;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (isEditAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
iconSymbol = Symbol.Edit;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
iconSymbol = Symbol.Add;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Control icon = new SymbolIcon
|
|
|
|
|
|
{
|
|
|
|
|
|
Symbol = iconSymbol,
|
|
|
|
|
|
IconVariant = IconVariant.Regular,
|
|
|
|
|
|
FontSize = iconSize
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var buttonContent = new StackPanel
|
|
|
|
|
|
{
|
|
|
|
|
|
Orientation = Orientation.Horizontal,
|
|
|
|
|
|
Spacing = spacing * 0.6,
|
|
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
icon,
|
|
|
|
|
|
new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = action.Title,
|
|
|
|
|
|
FontSize = fontSize,
|
|
|
|
|
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
var button = new Button
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Content = buttonContent,
|
2026-02-28 12:30:16 +08:00
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
BorderThickness = new Thickness(0),
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Padding = new Thickness(padding),
|
|
|
|
|
|
Foreground = isDeleteAction
|
|
|
|
|
|
? new SolidColorBrush(Color.Parse("#FFFF6B6B"))
|
|
|
|
|
|
: Foreground,
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Tag = action.CommandKey
|
2026-02-28 12:30:16 +08:00
|
|
|
|
};
|
2026-03-01 16:50:06 +08:00
|
|
|
|
button.Click += OnDynamicTaskbarActionClick;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
|
|
|
|
|
|
TaskbarDynamicActionsPanel.Children.Add(button);
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Control previewIcon = new SymbolIcon
|
|
|
|
|
|
{
|
|
|
|
|
|
Symbol = iconSymbol,
|
|
|
|
|
|
IconVariant = IconVariant.Regular,
|
|
|
|
|
|
FontSize = iconSize * 0.85
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
var previewText = new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = action.Title,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
FontSize = fontSize * 0.85,
|
|
|
|
|
|
Foreground = isDeleteAction
|
|
|
|
|
|
? new SolidColorBrush(Color.Parse("#FFFF6B6B"))
|
|
|
|
|
|
: Foreground,
|
2026-02-28 12:30:16 +08:00
|
|
|
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
|
|
|
|
|
};
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
var previewContent = new StackPanel
|
|
|
|
|
|
{
|
|
|
|
|
|
Orientation = Orientation.Horizontal,
|
|
|
|
|
|
Spacing = spacing * 0.5,
|
|
|
|
|
|
Children = { previewIcon, previewText }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-28 12:30:16 +08:00
|
|
|
|
var previewBorder = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
BorderThickness = new Thickness(0),
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Child = previewContent
|
2026-02-28 12:30:16 +08:00
|
|
|
|
};
|
2026-03-01 00:34:07 +08:00
|
|
|
|
WallpaperPreviewTaskbarDynamicActionsHost.Children.Add(previewBorder);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void OnDynamicTaskbarActionClick(object? sender, RoutedEventArgs e)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
if (sender is not Button button || button.Tag is not string commandKey)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
switch (commandKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
case "desktop.add_page":
|
|
|
|
|
|
AddDesktopPage();
|
|
|
|
|
|
break;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
case "desktop.delete_page":
|
|
|
|
|
|
DeleteCurrentDesktopPage();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "component.delete":
|
|
|
|
|
|
DeleteSelectedComponent();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "component.edit":
|
|
|
|
|
|
OpenComponentSettings();
|
|
|
|
|
|
break;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void DeleteSelectedComponent()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_selectedDesktopComponentHost is null || _selectedDesktopComponentHost.Tag is not string placementId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, placementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 娴犲海缍夐弽闂磋厬缁夊娅庣紒鍕
|
|
|
|
|
|
if (_desktopPageComponentGrids.TryGetValue(placement.PageIndex, out var pageGrid))
|
|
|
|
|
|
{
|
|
|
|
|
|
pageGrid.Children.Remove(_selectedDesktopComponentHost);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Remove from persisted placement list as well.
|
|
|
|
|
|
_desktopComponentPlacements.Remove(placement);
|
|
|
|
|
|
|
|
|
|
|
|
ClearDesktopComponentSelection();
|
|
|
|
|
|
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
|
|
|
|
|
|
|
|
|
|
// 娣囨繂鐡ㄧ拋鍓х枂
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OpenComponentSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_selectedDesktopComponentHost is null || _selectedDesktopComponentHost.Tag is not string placementId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, placementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (placement.ComponentId == BuiltInComponentIds.Date)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenDateComponentSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OpenDateComponentSettings()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentSettingsWindow is null || ComponentSettingsContentHost is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var settingsContent = new DateWidgetSettingsWindow();
|
|
|
|
|
|
ComponentSettingsContentHost.Content = settingsContent;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentSettingsWindow.IsVisible = true;
|
|
|
|
|
|
ComponentSettingsWindow.Opacity = 0;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentSettingsWindow.Opacity = 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CloseComponentSettingsWindow()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentSettingsWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ComponentSettingsWindow.Opacity = 0;
|
|
|
|
|
|
|
|
|
|
|
|
DispatcherTimer.RunOnce(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentSettingsWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentSettingsWindow.IsVisible = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ComponentSettingsContentHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentSettingsContentHost.Content = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}, TimeSpan.FromMilliseconds(200));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void AddDesktopPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopPageCount >= MaxDesktopPageCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_desktopPageCount = Math.Clamp(_desktopPageCount + 1, MinDesktopPageCount, MaxDesktopPageCount);
|
|
|
|
|
|
_currentDesktopSurfaceIndex = Math.Clamp(_desktopPageCount - 1, 0, LauncherSurfaceIndex);
|
|
|
|
|
|
RebuildDesktopGrid();
|
|
|
|
|
|
PersistSettings();
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
// 閺囧瓨鏌婇崝銊︹偓浣锋崲閸斺剝鐖弰鍓с仛
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void DeleteCurrentDesktopPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopPageCount <= MinDesktopPageCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placementsToRemove = _desktopComponentPlacements
|
|
|
|
|
|
.Where(p => p.PageIndex == _currentDesktopSurfaceIndex)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var placement in placementsToRemove)
|
|
|
|
|
|
{
|
|
|
|
|
|
_desktopComponentPlacements.Remove(placement);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_desktopPageCount = Math.Clamp(_desktopPageCount - 1, MinDesktopPageCount, MaxDesktopPageCount);
|
|
|
|
|
|
|
|
|
|
|
|
// 鐠嬪啯鏆hぐ鎾冲妞ょ敻娼扮槐銏犵穿
|
|
|
|
|
|
_currentDesktopSurfaceIndex = Math.Clamp(_currentDesktopSurfaceIndex, 0, _desktopPageCount - 1);
|
|
|
|
|
|
|
|
|
|
|
|
// Update remaining page indices after deletion.
|
|
|
|
|
|
foreach (var placement in _desktopComponentPlacements)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (placement.PageIndex > _currentDesktopSurfaceIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
placement.PageIndex--;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RebuildDesktopGrid();
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
|
|
|
|
|
|
// 閺囧瓨鏌婇崝銊︹偓浣锋崲閸斺剝鐖弰鍓с仛
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void InitializeDesktopComponentPlacements(AppSettingsSnapshot snapshot)
|
|
|
|
|
|
{
|
|
|
|
|
|
_desktopComponentPlacements.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
if (snapshot.DesktopComponentPlacements is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
foreach (var placement in snapshot.DesktopComponentPlacements)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
if (placement is null || string.IsNullOrWhiteSpace(placement.ComponentId))
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var placementId = string.IsNullOrWhiteSpace(placement.PlacementId)
|
|
|
|
|
|
? Guid.NewGuid().ToString("N")
|
|
|
|
|
|
: placement.PlacementId.Trim();
|
|
|
|
|
|
var componentId = placement.ComponentId.Trim();
|
|
|
|
|
|
if (!_componentRegistry.TryGetDefinition(componentId, out var definition) || !definition.AllowDesktopPlacement)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var (widthCells, heightCells) = NormalizeComponentCellSpan(
|
|
|
|
|
|
componentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
placement.WidthCells,
|
|
|
|
|
|
placement.HeightCells));
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
_desktopComponentPlacements.Add(new DesktopComponentPlacementSnapshot
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
PlacementId = placementId,
|
|
|
|
|
|
PageIndex = Math.Max(0, placement.PageIndex),
|
|
|
|
|
|
ComponentId = componentId,
|
|
|
|
|
|
Row = Math.Max(0, placement.Row),
|
|
|
|
|
|
Column = Math.Max(0, placement.Column),
|
|
|
|
|
|
WidthCells = widthCells,
|
|
|
|
|
|
HeightCells = heightCells
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RestoreDesktopPageComponents(int pageIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_desktopPageComponentGrids.TryGetValue(pageIndex, out var pageGrid))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pageGrid.Children.Clear();
|
|
|
|
|
|
|
|
|
|
|
|
var maxColumns = pageGrid.ColumnDefinitions.Count;
|
|
|
|
|
|
var maxRows = pageGrid.RowDefinitions.Count;
|
|
|
|
|
|
if (maxColumns <= 0 || maxRows <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
foreach (var placement in _desktopComponentPlacements.Where(p => p.PageIndex == pageIndex))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_componentRegistry.TryGetDefinition(placement.ComponentId, out var definition) || !definition.AllowDesktopPlacement)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var (widthCells, heightCells) = NormalizeComponentCellSpan(
|
|
|
|
|
|
placement.ComponentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
placement.WidthCells,
|
|
|
|
|
|
placement.HeightCells));
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
var clampedColumn = Math.Clamp(placement.Column, 0, Math.Max(0, maxColumns - widthCells));
|
|
|
|
|
|
var clampedRow = Math.Clamp(placement.Row, 0, Math.Max(0, maxRows - heightCells));
|
|
|
|
|
|
|
|
|
|
|
|
var host = CreateDesktopComponentHost(placement);
|
|
|
|
|
|
if (host is null)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
placement.Column = clampedColumn;
|
|
|
|
|
|
placement.Row = clampedRow;
|
|
|
|
|
|
placement.WidthCells = widthCells;
|
|
|
|
|
|
placement.HeightCells = heightCells;
|
|
|
|
|
|
|
|
|
|
|
|
Grid.SetColumn(host, clampedColumn);
|
|
|
|
|
|
Grid.SetRow(host, clampedRow);
|
|
|
|
|
|
Grid.SetColumnSpan(host, widthCells);
|
|
|
|
|
|
Grid.SetRowSpan(host, heightCells);
|
|
|
|
|
|
pageGrid.Children.Add(host);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void PlaceDesktopComponentOnPage(string componentId, int pageIndex, int row, int column)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_desktopPageComponentGrids.TryGetValue(pageIndex, out var pageGrid))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!_componentRegistry.TryGetDefinition(componentId, out var definition) || !definition.AllowDesktopPlacement)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var (widthCells, heightCells) = NormalizeComponentCellSpan(
|
|
|
|
|
|
componentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
definition.MinWidthCells,
|
|
|
|
|
|
definition.MinHeightCells));
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
var maxColumns = pageGrid.ColumnDefinitions.Count;
|
|
|
|
|
|
var maxRows = pageGrid.RowDefinitions.Count;
|
|
|
|
|
|
if (maxColumns <= 0 || maxRows <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
column = Math.Clamp(column, 0, Math.Max(0, maxColumns - widthCells));
|
|
|
|
|
|
row = Math.Clamp(row, 0, Math.Max(0, maxRows - heightCells));
|
|
|
|
|
|
|
|
|
|
|
|
var placementId = Guid.NewGuid().ToString("N");
|
|
|
|
|
|
var placement = new DesktopComponentPlacementSnapshot
|
|
|
|
|
|
{
|
|
|
|
|
|
PlacementId = placementId,
|
|
|
|
|
|
PageIndex = pageIndex,
|
|
|
|
|
|
ComponentId = componentId,
|
|
|
|
|
|
Row = row,
|
|
|
|
|
|
Column = column,
|
|
|
|
|
|
WidthCells = widthCells,
|
|
|
|
|
|
HeightCells = heightCells
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var host = CreateDesktopComponentHost(placement);
|
|
|
|
|
|
if (host is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Grid.SetColumn(host, column);
|
|
|
|
|
|
Grid.SetRow(host, row);
|
|
|
|
|
|
Grid.SetColumnSpan(host, widthCells);
|
|
|
|
|
|
Grid.SetRowSpan(host, heightCells);
|
|
|
|
|
|
pageGrid.Children.Add(host);
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentPlacements.Add(placement);
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Border? CreateDesktopComponentHost(DesktopComponentPlacementSnapshot placement)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(placement.PlacementId))
|
|
|
|
|
|
{
|
|
|
|
|
|
placement.PlacementId = Guid.NewGuid().ToString("N");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var component = CreateDesktopComponentControl(placement.ComponentId);
|
|
|
|
|
|
if (component is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var componentCornerRadius = GetComponentCornerRadius(placement.ComponentId);
|
|
|
|
|
|
|
|
|
|
|
|
var visualInset = GetDesktopComponentVisualInset(
|
|
|
|
|
|
Math.Max(1, placement.WidthCells),
|
|
|
|
|
|
Math.Max(1, placement.HeightCells));
|
|
|
|
|
|
|
|
|
|
|
|
var contentHost = new Border
|
2026-03-01 16:50:06 +08:00
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Tag = DesktopComponentContentHostTag,
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Background = Brushes.Transparent,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CornerRadius = new CornerRadius(componentCornerRadius),
|
2026-03-01 16:50:06 +08:00
|
|
|
|
ClipToBounds = true,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Padding = visualInset,
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Child = component
|
|
|
|
|
|
};
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
// Separate visual arc size from hit target size for better touch usability.
|
|
|
|
|
|
var handleTouchSize = Math.Clamp(_currentDesktopCellSize * 0.72, 30, 54);
|
|
|
|
|
|
var handleVisualSize = Math.Clamp(_currentDesktopCellSize * 0.56, 20, 40);
|
|
|
|
|
|
var handlePadding = Math.Max(2, (handleTouchSize - handleVisualSize) / 2);
|
|
|
|
|
|
var arcThickness = Math.Clamp(_currentDesktopCellSize * 0.17, 7, 14);
|
|
|
|
|
|
var arcData = Geometry.Parse("M 24,6 A 18,18 0 0 1 6,24");
|
|
|
|
|
|
|
|
|
|
|
|
var resizeHandleVisual = new Grid
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = handleVisualSize,
|
|
|
|
|
|
Height = handleVisualSize,
|
|
|
|
|
|
IsHitTestVisible = false
|
|
|
|
|
|
};
|
|
|
|
|
|
resizeHandleVisual.Children.Add(new Path
|
|
|
|
|
|
{
|
|
|
|
|
|
Data = arcData,
|
|
|
|
|
|
Stretch = Stretch.Fill,
|
|
|
|
|
|
Stroke = GetThemeBrush("AdaptiveTextAccentBrush"),
|
|
|
|
|
|
StrokeThickness = arcThickness + 3,
|
|
|
|
|
|
StrokeLineCap = PenLineCap.Round
|
|
|
|
|
|
});
|
|
|
|
|
|
resizeHandleVisual.Children.Add(new Path
|
|
|
|
|
|
{
|
|
|
|
|
|
Data = arcData,
|
|
|
|
|
|
Stretch = Stretch.Fill,
|
|
|
|
|
|
Stroke = GetThemeBrush("AdaptiveAccentBrush"),
|
|
|
|
|
|
StrokeThickness = arcThickness,
|
|
|
|
|
|
StrokeLineCap = PenLineCap.Round
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var resizeHandle = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Tag = DesktopComponentResizeHandleTag,
|
|
|
|
|
|
Width = handleTouchSize,
|
|
|
|
|
|
Height = handleTouchSize,
|
|
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
BorderBrush = Brushes.Transparent,
|
|
|
|
|
|
BorderThickness = new Thickness(0),
|
|
|
|
|
|
CornerRadius = new CornerRadius(handleTouchSize * 0.5),
|
|
|
|
|
|
Padding = new Thickness(handlePadding),
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Bottom,
|
|
|
|
|
|
Margin = new Thickness(
|
|
|
|
|
|
0,
|
|
|
|
|
|
0,
|
|
|
|
|
|
-Math.Clamp(handleTouchSize * 0.42, 10, 24),
|
|
|
|
|
|
-Math.Clamp(handleTouchSize * 0.42, 10, 24)),
|
|
|
|
|
|
Child = resizeHandleVisual,
|
|
|
|
|
|
Opacity = 1,
|
|
|
|
|
|
IsVisible = false,
|
|
|
|
|
|
IsHitTestVisible = false
|
|
|
|
|
|
};
|
|
|
|
|
|
resizeHandle.PointerPressed += OnDesktopComponentResizeHandlePointerPressed;
|
|
|
|
|
|
|
|
|
|
|
|
var hostChrome = new Grid
|
|
|
|
|
|
{
|
|
|
|
|
|
ClipToBounds = false
|
|
|
|
|
|
};
|
|
|
|
|
|
hostChrome.Children.Add(contentHost);
|
|
|
|
|
|
hostChrome.Children.Add(resizeHandle);
|
|
|
|
|
|
|
|
|
|
|
|
var host = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Tag = placement.PlacementId,
|
|
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
ClipToBounds = false,
|
|
|
|
|
|
CornerRadius = new CornerRadius(componentCornerRadius),
|
|
|
|
|
|
Child = hostChrome
|
|
|
|
|
|
};
|
2026-03-01 16:50:06 +08:00
|
|
|
|
host.Classes.Add(DesktopComponentHostClass);
|
|
|
|
|
|
ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen);
|
|
|
|
|
|
host.PointerPressed += OnDesktopComponentHostPointerPressed;
|
|
|
|
|
|
return host;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private static (int WidthCells, int HeightCells) NormalizeComponentCellSpan(
|
|
|
|
|
|
string componentId,
|
|
|
|
|
|
(int WidthCells, int HeightCells) span)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.Equals(componentId, BuiltInComponentIds.Date, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (Math.Max(4, span.WidthCells), Math.Max(2, span.HeightCells));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.Equals(componentId, BuiltInComponentIds.MonthCalendar, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (Math.Max(2, span.WidthCells), Math.Max(2, span.HeightCells));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.Equals(componentId, BuiltInComponentIds.LunarCalendar, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (Math.Max(2, span.WidthCells), Math.Max(2, span.HeightCells));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (Math.Max(1, span.WidthCells), Math.Max(1, span.HeightCells));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private double GetComponentCornerRadius(string componentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return componentId switch
|
|
|
|
|
|
{
|
|
|
|
|
|
BuiltInComponentIds.Date => 16,
|
|
|
|
|
|
BuiltInComponentIds.MonthCalendar => Math.Clamp(_currentDesktopCellSize * 0.26, 10, 22),
|
|
|
|
|
|
BuiltInComponentIds.LunarCalendar => Math.Clamp(_currentDesktopCellSize * 0.30, 12, 26),
|
|
|
|
|
|
_ => Math.Clamp(_currentDesktopCellSize * 0.22, 8, 18)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Thickness GetDesktopComponentVisualInset(int widthCells, int heightCells)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Keep the drop/selection bounds on grid cells while reducing visual footprint.
|
|
|
|
|
|
var baseInset = Math.Clamp(_currentDesktopCellSize * 0.08, 2, 10);
|
|
|
|
|
|
var horizontal = Math.Clamp(baseInset + Math.Max(0, widthCells - 1) * 0.25, 2, 12);
|
|
|
|
|
|
var vertical = Math.Clamp(baseInset * 0.85 + Math.Max(0, heightCells - 1) * 0.2, 2, 10);
|
|
|
|
|
|
return new Thickness(horizontal, vertical, horizontal, vertical);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Border? FindDesktopComponentHost(Visual? visual)
|
|
|
|
|
|
{
|
|
|
|
|
|
var current = visual;
|
|
|
|
|
|
while (current is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (current is Border border && border.Classes.Contains(DesktopComponentHostClass))
|
|
|
|
|
|
{
|
|
|
|
|
|
return border;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
current = current.GetVisualParent();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Border? TryGetContentHost(Border host)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (host.Child is Grid hostChrome)
|
|
|
|
|
|
{
|
|
|
|
|
|
return hostChrome.Children
|
|
|
|
|
|
.OfType<Border>()
|
|
|
|
|
|
.FirstOrDefault(child =>
|
|
|
|
|
|
string.Equals(child.Tag?.ToString(), DesktopComponentContentHostTag, StringComparison.Ordinal));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Border? TryGetResizeHandle(Border host)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (host.Child is Grid hostChrome)
|
|
|
|
|
|
{
|
|
|
|
|
|
return hostChrome.Children
|
|
|
|
|
|
.OfType<Border>()
|
|
|
|
|
|
.FirstOrDefault(child =>
|
|
|
|
|
|
string.Equals(child.Tag?.ToString(), DesktopComponentResizeHandleTag, StringComparison.Ordinal));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool IsPointerOnSelectedFrameBorder(Border host, Point pointerInHost)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (host != _selectedDesktopComponentHost || !_isComponentLibraryOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var width = host.Bounds.Width;
|
|
|
|
|
|
var height = host.Bounds.Height;
|
|
|
|
|
|
if (width <= 1 || height <= 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var borderBand = Math.Clamp(_currentDesktopCellSize * 0.15, 8, 22);
|
|
|
|
|
|
var onLeft = pointerInHost.X <= borderBand;
|
|
|
|
|
|
var onRight = pointerInHost.X >= width - borderBand;
|
|
|
|
|
|
var onTop = pointerInHost.Y <= borderBand;
|
|
|
|
|
|
var onBottom = pointerInHost.Y >= height - borderBand;
|
|
|
|
|
|
return onLeft || onRight || onTop || onBottom;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private Control? CreateDesktopComponentControl(string componentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (componentId == BuiltInComponentIds.Date)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new DateWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(_currentDesktopCellSize);
|
|
|
|
|
|
widget.Classes.Add(DesktopComponentClass);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (componentId == BuiltInComponentIds.MonthCalendar)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new MonthCalendarWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(_currentDesktopCellSize);
|
|
|
|
|
|
widget.Classes.Add(DesktopComponentClass);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (componentId == BuiltInComponentIds.LunarCalendar)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new LunarCalendarWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(_currentDesktopCellSize);
|
|
|
|
|
|
widget.Classes.Add(DesktopComponentClass);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CollapseComponentLibraryPanel()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Animate component library panel collapsing downward
|
|
|
|
|
|
if (ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryWindow.Height = 0;
|
|
|
|
|
|
ComponentLibraryWindow.IsVisible = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryOpen = false;
|
|
|
|
|
|
CancelDesktopComponentDrag();
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CancelDesktopComponentResize(restoreOriginalSpan: true);
|
|
|
|
|
|
ClearDesktopComponentSelection();
|
2026-03-01 16:50:06 +08:00
|
|
|
|
UpdateDesktopComponentHostEditState();
|
|
|
|
|
|
UpdateComponentLibraryLayout(_currentDesktopCellSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateDesktopComponentHostEditState()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var pageGrid in _desktopPageComponentGrids.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var child in pageGrid.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (child is Border host && host.Classes.Contains(DesktopComponentHostClass))
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplyDesktopEditStateToHost(host, _isComponentLibraryOpen);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyDesktopEditStateToHost(Border host, bool isEditMode)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
host.IsHitTestVisible = true;
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (TryGetContentHost(host) is Border contentHost)
|
2026-03-01 16:50:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
// In edit mode, prefer drag interactions over component interactions.
|
2026-03-02 20:02:14 +08:00
|
|
|
|
contentHost.IsHitTestVisible = !isEditMode;
|
|
|
|
|
|
if (contentHost.Child is Control componentControl)
|
|
|
|
|
|
{
|
|
|
|
|
|
componentControl.IsHitTestVisible = !isEditMode;
|
|
|
|
|
|
}
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|
|
|
|
|
|
var isSelected = host == _selectedDesktopComponentHost;
|
|
|
|
|
|
ApplySelectionStateToHost(host, isSelected);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDesktopComponentHostPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (!_isComponentLibraryOpen || _isDesktopComponentDragActive || _isDesktopComponentResizeActive)
|
2026-03-01 16:50:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (DesktopPagesViewport is null ||
|
|
|
|
|
|
sender is not Border host ||
|
|
|
|
|
|
host.Tag is not string placementId ||
|
|
|
|
|
|
!e.GetCurrentPoint(host).Properties.IsLeftButtonPressed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, placementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var wasSelected = host == _selectedDesktopComponentHost;
|
|
|
|
|
|
SetSelectedDesktopComponent(host);
|
|
|
|
|
|
if (!wasSelected)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pointerInHost = e.GetPosition(host);
|
|
|
|
|
|
if (IsPointerOnSelectedFrameBorder(host, pointerInHost))
|
|
|
|
|
|
{
|
|
|
|
|
|
BeginDesktopComponentResizeDrag(host, placement, e);
|
|
|
|
|
|
if (_isDesktopComponentResizeActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
BeginDesktopComponentMoveDrag(host, placement, e);
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void SetSelectedDesktopComponent(Border? host)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Clear previous selection
|
|
|
|
|
|
if (_selectedDesktopComponentHost is not null && _selectedDesktopComponentHost != host)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplySelectionStateToHost(_selectedDesktopComponentHost, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set new selection
|
|
|
|
|
|
_selectedDesktopComponentHost = host;
|
|
|
|
|
|
if (host is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplySelectionStateToHost(host, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Refresh taskbar actions to show delete/edit buttons
|
|
|
|
|
|
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplySelectionStateToHost(Border host, bool isSelected)
|
|
|
|
|
|
{
|
|
|
|
|
|
var showSelection = isSelected && _isComponentLibraryOpen;
|
|
|
|
|
|
host.BorderThickness = showSelection
|
|
|
|
|
|
? new Thickness(Math.Clamp(_currentDesktopCellSize * 0.04, 1, 3))
|
|
|
|
|
|
: new Thickness(0);
|
|
|
|
|
|
host.BorderBrush = showSelection ? GetThemeBrush("AdaptiveAccentBrush") : null;
|
|
|
|
|
|
|
|
|
|
|
|
if (TryGetResizeHandle(host) is Border resizeHandle)
|
|
|
|
|
|
{
|
|
|
|
|
|
resizeHandle.IsVisible = showSelection;
|
|
|
|
|
|
resizeHandle.IsHitTestVisible = showSelection;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ClearDesktopComponentSelection()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_selectedDesktopComponentHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplySelectionStateToHost(_selectedDesktopComponentHost, false);
|
|
|
|
|
|
_selectedDesktopComponentHost = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void BeginDesktopComponentMoveDrag(Border sourceHost, DesktopComponentPlacementSnapshot placement, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (_isDesktopComponentResizeActive ||
|
|
|
|
|
|
DesktopEditDragLayer is null ||
|
2026-03-01 16:50:06 +08:00
|
|
|
|
DesktopPagesViewport is null ||
|
|
|
|
|
|
_currentDesktopCellSize <= 0 ||
|
|
|
|
|
|
!_componentRegistry.TryGetDefinition(placement.ComponentId, out var definition))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var (widthCells, heightCells) = NormalizeComponentCellSpan(
|
|
|
|
|
|
placement.ComponentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
placement.WidthCells,
|
|
|
|
|
|
placement.HeightCells));
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
var pointerInViewport = e.GetPosition(DesktopPagesViewport);
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var pitch = CurrentDesktopPitch;
|
|
|
|
|
|
var topLeft = new Point(placement.Column * pitch, placement.Row * pitch);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var pointerOffset = pointerInViewport - topLeft;
|
|
|
|
|
|
|
|
|
|
|
|
sourceHost.Opacity = 0.35;
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentDrag = new DesktopComponentDragState
|
|
|
|
|
|
{
|
|
|
|
|
|
Kind = DesktopComponentDragKind.MoveExisting,
|
|
|
|
|
|
ComponentId = placement.ComponentId,
|
|
|
|
|
|
PlacementId = placement.PlacementId,
|
|
|
|
|
|
PageIndex = placement.PageIndex,
|
|
|
|
|
|
WidthCells = widthCells,
|
|
|
|
|
|
HeightCells = heightCells,
|
|
|
|
|
|
PointerOffset = pointerOffset,
|
|
|
|
|
|
SourceHost = sourceHost
|
|
|
|
|
|
};
|
|
|
|
|
|
_isDesktopComponentDragActive = true;
|
|
|
|
|
|
|
|
|
|
|
|
EnsureDesktopComponentDragGhost(placement.ComponentId, widthCells, heightCells);
|
|
|
|
|
|
UpdateDesktopComponentDragVisual(pointerInViewport);
|
|
|
|
|
|
|
|
|
|
|
|
e.Pointer.Capture(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BeginDesktopComponentNewDrag(string componentId, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen ||
|
|
|
|
|
|
_isDesktopComponentDragActive ||
|
2026-03-02 20:02:14 +08:00
|
|
|
|
_isDesktopComponentResizeActive ||
|
2026-03-01 16:50:06 +08:00
|
|
|
|
DesktopEditDragLayer is null ||
|
|
|
|
|
|
DesktopPagesViewport is null ||
|
|
|
|
|
|
_currentDesktopCellSize <= 0 ||
|
|
|
|
|
|
!_componentRegistry.TryGetDefinition(componentId, out var definition) ||
|
|
|
|
|
|
!definition.AllowDesktopPlacement)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var (widthCells, heightCells) = NormalizeComponentCellSpan(
|
|
|
|
|
|
componentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
definition.MinWidthCells,
|
|
|
|
|
|
definition.MinHeightCells));
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
// Center the component under the pointer while dragging from the library.
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var ghostWidth = Math.Max(1, widthCells * _currentDesktopCellSize + Math.Max(0, widthCells - 1) * _currentDesktopCellGap);
|
|
|
|
|
|
var ghostHeight = Math.Max(1, heightCells * _currentDesktopCellSize + Math.Max(0, heightCells - 1) * _currentDesktopCellGap);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var pointerOffset = new Point(
|
2026-03-02 20:02:14 +08:00
|
|
|
|
ghostWidth * 0.5,
|
|
|
|
|
|
ghostHeight * 0.5);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
_desktopComponentDrag = new DesktopComponentDragState
|
|
|
|
|
|
{
|
|
|
|
|
|
Kind = DesktopComponentDragKind.NewFromLibrary,
|
|
|
|
|
|
ComponentId = componentId,
|
|
|
|
|
|
PageIndex = _currentDesktopSurfaceIndex,
|
|
|
|
|
|
WidthCells = widthCells,
|
|
|
|
|
|
HeightCells = heightCells,
|
|
|
|
|
|
PointerOffset = pointerOffset
|
|
|
|
|
|
};
|
|
|
|
|
|
_isDesktopComponentDragActive = true;
|
|
|
|
|
|
|
|
|
|
|
|
EnsureDesktopComponentDragGhost(componentId, widthCells, heightCells);
|
|
|
|
|
|
var pointerInViewport = e.GetPosition(DesktopPagesViewport);
|
|
|
|
|
|
UpdateDesktopComponentDragVisual(pointerInViewport);
|
2026-03-01 00:34:07 +08:00
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
e.Pointer.Capture(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void EnsureDesktopComponentDragGhost(string componentId, int widthCells, int heightCells)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DesktopEditDragLayer is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DesktopEditDragLayer.Children.Clear();
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var ghostWidth = Math.Max(1, widthCells * _currentDesktopCellSize + Math.Max(0, widthCells - 1) * _currentDesktopCellGap);
|
|
|
|
|
|
var ghostHeight = Math.Max(1, heightCells * _currentDesktopCellSize + Math.Max(0, heightCells - 1) * _currentDesktopCellGap);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
var ghostContent = CreateDesktopComponentControl(componentId);
|
|
|
|
|
|
if (ghostContent is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ghostContent.IsHitTestVisible = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var visualInset = GetDesktopComponentVisualInset(widthCells, heightCells);
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
_desktopComponentDragGhost = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = ghostWidth,
|
|
|
|
|
|
Height = ghostHeight,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CornerRadius = new CornerRadius(Math.Clamp(_currentDesktopCellSize * 0.45, 16, 36)),
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Background = new SolidColorBrush(Color.Parse("#331E40AF")),
|
|
|
|
|
|
BorderBrush = GetThemeBrush("AdaptiveAccentBrush"),
|
|
|
|
|
|
BorderThickness = new Thickness(Math.Clamp(_currentDesktopCellSize * 0.04, 1, 3)),
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Padding = visualInset,
|
|
|
|
|
|
ClipToBounds = true,
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Child = ghostContent,
|
|
|
|
|
|
Opacity = 0.92,
|
|
|
|
|
|
IsHitTestVisible = false
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DesktopEditDragLayer.Children.Add(_desktopComponentDragGhost);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void OnDesktopComponentResizeHandlePointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen ||
|
|
|
|
|
|
_isDesktopComponentDragActive ||
|
|
|
|
|
|
_isDesktopComponentResizeActive ||
|
|
|
|
|
|
DesktopPagesViewport is null ||
|
|
|
|
|
|
sender is not Border handle ||
|
|
|
|
|
|
!e.GetCurrentPoint(handle).Properties.IsLeftButtonPressed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var host = FindDesktopComponentHost(handle);
|
|
|
|
|
|
if (host?.Tag is not string placementId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, placementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SetSelectedDesktopComponent(host);
|
|
|
|
|
|
BeginDesktopComponentResizeDrag(host, placement, e);
|
|
|
|
|
|
if (_isDesktopComponentResizeActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BeginDesktopComponentResizeDrag(
|
|
|
|
|
|
Border sourceHost,
|
|
|
|
|
|
DesktopComponentPlacementSnapshot placement,
|
|
|
|
|
|
PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DesktopPagesViewport is null ||
|
|
|
|
|
|
_currentDesktopCellSize <= 0 ||
|
|
|
|
|
|
!_componentRegistry.TryGetDefinition(placement.ComponentId, out var definition) ||
|
|
|
|
|
|
!_desktopPageComponentGrids.TryGetValue(placement.PageIndex, out var pageGrid))
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var startSpan = NormalizeComponentCellSpan(
|
|
|
|
|
|
placement.ComponentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
placement.WidthCells,
|
|
|
|
|
|
placement.HeightCells));
|
|
|
|
|
|
|
|
|
|
|
|
var minSpan = NormalizeComponentCellSpan(
|
|
|
|
|
|
placement.ComponentId,
|
|
|
|
|
|
ComponentPlacementRules.EnsureMinimumSize(
|
|
|
|
|
|
definition,
|
|
|
|
|
|
definition.MinWidthCells,
|
|
|
|
|
|
definition.MinHeightCells));
|
|
|
|
|
|
|
|
|
|
|
|
var maxWidthCells = Math.Max(startSpan.WidthCells, pageGrid.ColumnDefinitions.Count - placement.Column);
|
|
|
|
|
|
var maxHeightCells = Math.Max(startSpan.HeightCells, pageGrid.RowDefinitions.Count - placement.Row);
|
|
|
|
|
|
if (maxWidthCells <= 0 || maxHeightCells <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pointerInViewport = e.GetPosition(DesktopPagesViewport);
|
|
|
|
|
|
_desktopComponentResize = new DesktopComponentResizeState
|
|
|
|
|
|
{
|
|
|
|
|
|
PlacementId = placement.PlacementId,
|
|
|
|
|
|
ComponentId = placement.ComponentId,
|
|
|
|
|
|
SourceHost = sourceHost,
|
|
|
|
|
|
StartWidthCells = startSpan.WidthCells,
|
|
|
|
|
|
StartHeightCells = startSpan.HeightCells,
|
|
|
|
|
|
MinWidthCells = Math.Max(1, Math.Min(minSpan.WidthCells, maxWidthCells)),
|
|
|
|
|
|
MinHeightCells = Math.Max(1, Math.Min(minSpan.HeightCells, maxHeightCells)),
|
|
|
|
|
|
MaxWidthCells = maxWidthCells,
|
|
|
|
|
|
MaxHeightCells = maxHeightCells,
|
|
|
|
|
|
StartPointerInViewport = pointerInViewport,
|
|
|
|
|
|
CurrentWidthCells = startSpan.WidthCells,
|
|
|
|
|
|
CurrentHeightCells = startSpan.HeightCells
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_isDesktopComponentResizeActive = true;
|
|
|
|
|
|
sourceHost.Opacity = 0.96;
|
|
|
|
|
|
e.Pointer.Capture(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateDesktopComponentResizeVisual(Point pointerInViewport)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopComponentResize is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pitch = CurrentDesktopPitch;
|
|
|
|
|
|
if (pitch <= 0 ||
|
|
|
|
|
|
_desktopComponentResize.StartWidthCells <= 0 ||
|
|
|
|
|
|
_desktopComponentResize.StartHeightCells <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var deltaX = pointerInViewport.X - _desktopComponentResize.StartPointerInViewport.X;
|
|
|
|
|
|
var deltaY = pointerInViewport.Y - _desktopComponentResize.StartPointerInViewport.Y;
|
|
|
|
|
|
var widthScale = (_desktopComponentResize.StartWidthCells + deltaX / pitch) / _desktopComponentResize.StartWidthCells;
|
|
|
|
|
|
var heightScale = (_desktopComponentResize.StartHeightCells + deltaY / pitch) / _desktopComponentResize.StartHeightCells;
|
|
|
|
|
|
|
|
|
|
|
|
var proposedScale = Math.Max(widthScale, heightScale);
|
|
|
|
|
|
var minScale = Math.Max(
|
|
|
|
|
|
(double)_desktopComponentResize.MinWidthCells / _desktopComponentResize.StartWidthCells,
|
|
|
|
|
|
(double)_desktopComponentResize.MinHeightCells / _desktopComponentResize.StartHeightCells);
|
|
|
|
|
|
var maxScale = Math.Min(
|
|
|
|
|
|
(double)_desktopComponentResize.MaxWidthCells / _desktopComponentResize.StartWidthCells,
|
|
|
|
|
|
(double)_desktopComponentResize.MaxHeightCells / _desktopComponentResize.StartHeightCells);
|
|
|
|
|
|
|
|
|
|
|
|
if (double.IsNaN(proposedScale) || double.IsInfinity(proposedScale))
|
|
|
|
|
|
{
|
|
|
|
|
|
proposedScale = minScale;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (maxScale < minScale)
|
|
|
|
|
|
{
|
|
|
|
|
|
maxScale = minScale;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var scale = Math.Clamp(proposedScale, minScale, maxScale);
|
|
|
|
|
|
var widthCells = Math.Clamp(
|
|
|
|
|
|
(int)Math.Round(_desktopComponentResize.StartWidthCells * scale),
|
|
|
|
|
|
_desktopComponentResize.MinWidthCells,
|
|
|
|
|
|
_desktopComponentResize.MaxWidthCells);
|
|
|
|
|
|
var heightCells = Math.Clamp(
|
|
|
|
|
|
(int)Math.Round(_desktopComponentResize.StartHeightCells * scale),
|
|
|
|
|
|
_desktopComponentResize.MinHeightCells,
|
|
|
|
|
|
_desktopComponentResize.MaxHeightCells);
|
|
|
|
|
|
|
|
|
|
|
|
var normalized = NormalizeComponentCellSpan(_desktopComponentResize.ComponentId, (widthCells, heightCells));
|
|
|
|
|
|
widthCells = Math.Clamp(normalized.WidthCells, _desktopComponentResize.MinWidthCells, _desktopComponentResize.MaxWidthCells);
|
|
|
|
|
|
heightCells = Math.Clamp(normalized.HeightCells, _desktopComponentResize.MinHeightCells, _desktopComponentResize.MaxHeightCells);
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentResize.CurrentWidthCells = widthCells;
|
|
|
|
|
|
_desktopComponentResize.CurrentHeightCells = heightCells;
|
|
|
|
|
|
Grid.SetColumnSpan(_desktopComponentResize.SourceHost, widthCells);
|
|
|
|
|
|
Grid.SetRowSpan(_desktopComponentResize.SourceHost, heightCells);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool TryCompleteDesktopComponentResize(Point pointerInViewport)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopComponentResize is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateDesktopComponentResizeVisual(pointerInViewport);
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, _desktopComponentResize.PlacementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var widthCells = Math.Max(1, _desktopComponentResize.CurrentWidthCells);
|
|
|
|
|
|
var heightCells = Math.Max(1, _desktopComponentResize.CurrentHeightCells);
|
|
|
|
|
|
var changed = placement.WidthCells != widthCells || placement.HeightCells != heightCells;
|
|
|
|
|
|
placement.WidthCells = widthCells;
|
|
|
|
|
|
placement.HeightCells = heightCells;
|
|
|
|
|
|
|
|
|
|
|
|
ApplyDesktopEditStateToHost(_desktopComponentResize.SourceHost, _isComponentLibraryOpen);
|
|
|
|
|
|
if (changed)
|
|
|
|
|
|
{
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CancelDesktopComponentResize(bool restoreOriginalSpan)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isDesktopComponentResizeActive || _desktopComponentResize is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (restoreOriginalSpan)
|
|
|
|
|
|
{
|
|
|
|
|
|
Grid.SetColumnSpan(_desktopComponentResize.SourceHost, _desktopComponentResize.StartWidthCells);
|
|
|
|
|
|
Grid.SetRowSpan(_desktopComponentResize.SourceHost, _desktopComponentResize.StartHeightCells);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentResize.SourceHost.Opacity = 1;
|
|
|
|
|
|
ApplyDesktopEditStateToHost(_desktopComponentResize.SourceHost, _isComponentLibraryOpen);
|
|
|
|
|
|
_desktopComponentResize = null;
|
|
|
|
|
|
_isDesktopComponentResizeActive = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void OnDesktopComponentDragPointerMoved(object? sender, PointerEventArgs e)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (DesktopPagesViewport is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_isDesktopComponentResizeActive && _desktopComponentResize is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
UpdateDesktopComponentResizeVisual(e.GetPosition(DesktopPagesViewport));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!_isDesktopComponentDragActive || _desktopComponentDrag is null)
|
2026-03-01 16:50:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UpdateDesktopComponentDragVisual(e.GetPosition(DesktopPagesViewport));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDesktopComponentDragPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (DesktopPagesViewport is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_isDesktopComponentResizeActive && _desktopComponentResize is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var resizePointerInViewport = e.GetPosition(DesktopPagesViewport);
|
|
|
|
|
|
var resizeSuccess = TryCompleteDesktopComponentResize(resizePointerInViewport);
|
|
|
|
|
|
CancelDesktopComponentResize(restoreOriginalSpan: !resizeSuccess);
|
|
|
|
|
|
e.Pointer.Capture(null);
|
|
|
|
|
|
if (resizeSuccess)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!_isDesktopComponentDragActive || _desktopComponentDrag is null)
|
2026-03-01 16:50:06 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pointerInViewport = e.GetPosition(DesktopPagesViewport);
|
|
|
|
|
|
var success = TryCompleteDesktopComponentDrag(pointerInViewport);
|
|
|
|
|
|
CancelDesktopComponentDrag();
|
|
|
|
|
|
e.Pointer.Capture(null);
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDesktopComponentDragPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (_isDesktopComponentResizeActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
CancelDesktopComponentResize(restoreOriginalSpan: true);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
if (!_isDesktopComponentDragActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CancelDesktopComponentDrag();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void UpdateDesktopComponentDragVisual(Point pointerInViewport)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopComponentDragGhost is null || _desktopComponentDrag is null || DesktopPagesViewport is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var withinViewport =
|
|
|
|
|
|
pointerInViewport.X >= 0 &&
|
|
|
|
|
|
pointerInViewport.Y >= 0 &&
|
|
|
|
|
|
pointerInViewport.X <= DesktopPagesViewport.Bounds.Width &&
|
|
|
|
|
|
pointerInViewport.Y <= DesktopPagesViewport.Bounds.Height;
|
|
|
|
|
|
|
|
|
|
|
|
if (!withinViewport ||
|
|
|
|
|
|
!TryGetDesktopComponentDropCell(pointerInViewport, _desktopComponentDrag, out var row, out var column))
|
|
|
|
|
|
{
|
|
|
|
|
|
_desktopComponentDragGhost.IsVisible = false;
|
|
|
|
|
|
return;
|
2026-03-01 00:34:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
_desktopComponentDragGhost.IsVisible = true;
|
|
|
|
|
|
_desktopComponentDrag.TargetRow = row;
|
|
|
|
|
|
_desktopComponentDrag.TargetColumn = column;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var pitch = CurrentDesktopPitch;
|
|
|
|
|
|
Canvas.SetLeft(_desktopComponentDragGhost, column * pitch);
|
|
|
|
|
|
Canvas.SetTop(_desktopComponentDragGhost, row * pitch);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool TryGetDesktopComponentDropCell(
|
|
|
|
|
|
Point pointerInViewport,
|
|
|
|
|
|
DesktopComponentDragState state,
|
|
|
|
|
|
out int row,
|
|
|
|
|
|
out int column)
|
|
|
|
|
|
{
|
|
|
|
|
|
row = 0;
|
|
|
|
|
|
column = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (_currentDesktopCellSize <= 0 ||
|
|
|
|
|
|
_currentDesktopSurfaceIndex < 0 ||
|
|
|
|
|
|
_currentDesktopSurfaceIndex >= _desktopPageCount ||
|
|
|
|
|
|
!_desktopPageComponentGrids.TryGetValue(_currentDesktopSurfaceIndex, out var pageGrid))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var maxColumns = pageGrid.ColumnDefinitions.Count;
|
|
|
|
|
|
var maxRows = pageGrid.RowDefinitions.Count;
|
|
|
|
|
|
if (maxColumns <= 0 || maxRows <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var pitch = CurrentDesktopPitch;
|
|
|
|
|
|
if (pitch <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var x = pointerInViewport.X - state.PointerOffset.X;
|
|
|
|
|
|
var y = pointerInViewport.Y - state.PointerOffset.Y;
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
column = (int)Math.Floor(x / pitch);
|
|
|
|
|
|
row = (int)Math.Floor(y / pitch);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
column = Math.Clamp(column, 0, Math.Max(0, maxColumns - state.WidthCells));
|
|
|
|
|
|
row = Math.Clamp(row, 0, Math.Max(0, maxRows - state.HeightCells));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool TryCompleteDesktopComponentDrag(Point pointerInViewport)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_desktopComponentDrag is null ||
|
|
|
|
|
|
_currentDesktopCellSize <= 0 ||
|
|
|
|
|
|
_currentDesktopSurfaceIndex < 0 ||
|
|
|
|
|
|
_currentDesktopSurfaceIndex >= _desktopPageCount)
|
2026-03-01 00:34:07 +08:00
|
|
|
|
{
|
2026-03-01 16:50:06 +08:00
|
|
|
|
return false;
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
|
|
|
|
|
if (!TryGetDesktopComponentDropCell(pointerInViewport, _desktopComponentDrag, out var row, out var column))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (_desktopComponentDrag.Kind)
|
|
|
|
|
|
{
|
|
|
|
|
|
case DesktopComponentDragKind.NewFromLibrary:
|
|
|
|
|
|
PlaceDesktopComponentOnPage(_desktopComponentDrag.ComponentId, _currentDesktopSurfaceIndex, row, column);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
case DesktopComponentDragKind.MoveExisting:
|
|
|
|
|
|
return TryMoveExistingDesktopComponent(_desktopComponentDrag.PlacementId, row, column);
|
|
|
|
|
|
default:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool TryMoveExistingDesktopComponent(string placementId, int row, int column)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(placementId) ||
|
|
|
|
|
|
_desktopComponentDrag?.SourceHost is null ||
|
|
|
|
|
|
_desktopComponentDrag.Kind != DesktopComponentDragKind.MoveExisting)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var placement = _desktopComponentPlacements.FirstOrDefault(p =>
|
|
|
|
|
|
string.Equals(p.PlacementId, placementId, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
|
if (placement is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
placement.Row = Math.Max(0, row);
|
|
|
|
|
|
placement.Column = Math.Max(0, column);
|
|
|
|
|
|
|
|
|
|
|
|
Grid.SetRow(_desktopComponentDrag.SourceHost, placement.Row);
|
|
|
|
|
|
Grid.SetColumn(_desktopComponentDrag.SourceHost, placement.Column);
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentDrag.SourceHost.Opacity = 1;
|
|
|
|
|
|
ApplyDesktopEditStateToHost(_desktopComponentDrag.SourceHost, _isComponentLibraryOpen);
|
|
|
|
|
|
PersistSettings();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CancelDesktopComponentDrag()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isDesktopComponentDragActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_desktopComponentDrag?.SourceHost is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_desktopComponentDrag.SourceHost.Opacity = 1;
|
|
|
|
|
|
ApplyDesktopEditStateToHost(_desktopComponentDrag.SourceHost, _isComponentLibraryOpen);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentDrag = null;
|
|
|
|
|
|
_isDesktopComponentDragActive = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (DesktopEditDragLayer is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
DesktopEditDragLayer.Children.Clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_desktopComponentDragGhost = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowComponentLibraryCategoryView()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryCategoriesView is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryCategoriesView.IsVisible = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ComponentLibraryComponentsView is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryComponentsView.IsVisible = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ShowComponentLibraryComponentsView()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryCategoriesView is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryCategoriesView.IsVisible = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ComponentLibraryComponentsView is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryComponentsView.IsVisible = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildComponentLibraryCategoryPages()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryCategoryViewport is null ||
|
|
|
|
|
|
ComponentLibraryCategoryPagesHost is null ||
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer is null ||
|
|
|
|
|
|
ComponentLibraryEmptyTextBlock is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategories = GetComponentLibraryCategories();
|
|
|
|
|
|
var categoryCount = _componentLibraryCategories.Count;
|
|
|
|
|
|
ComponentLibraryEmptyTextBlock.IsVisible = categoryCount == 0;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.Children.Clear();
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.RowDefinitions.Clear();
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.ColumnDefinitions.Clear();
|
|
|
|
|
|
if (categoryCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryCategoryIndex = 0;
|
|
|
|
|
|
_componentLibraryActiveCategoryId = null;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var viewportWidth = ComponentLibraryCategoryViewport.Bounds.Width;
|
|
|
|
|
|
if (viewportWidth <= 1 && ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
viewportWidth = Math.Max(1, ComponentLibraryWindow.Bounds.Width - 48);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var viewportHeight = ComponentLibraryCategoryViewport.Bounds.Height;
|
|
|
|
|
|
if (viewportHeight <= 1 && ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
viewportHeight = Math.Max(1, ComponentLibraryWindow.Bounds.Height - 120);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategoryPageWidth = Math.Max(1, viewportWidth);
|
|
|
|
|
|
ComponentLibraryCategoryPagesHost.Width = _componentLibraryCategoryPageWidth * categoryCount;
|
|
|
|
|
|
ComponentLibraryCategoryPagesHost.Height = viewportHeight;
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.Width = ComponentLibraryCategoryPagesHost.Width;
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.Height = viewportHeight;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.RowDefinitions.Add(new RowDefinition(new GridLength(viewportHeight, GridUnitType.Pixel)));
|
|
|
|
|
|
for (var i = 0; i < categoryCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.ColumnDefinitions.Add(
|
|
|
|
|
|
new ColumnDefinition(new GridLength(_componentLibraryCategoryPageWidth, GridUnitType.Pixel)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(_componentLibraryActiveCategoryId))
|
|
|
|
|
|
{
|
|
|
|
|
|
var activeIndex = _componentLibraryCategories
|
|
|
|
|
|
.Select((category, index) => (category, index))
|
|
|
|
|
|
.FirstOrDefault(tuple =>
|
|
|
|
|
|
string.Equals(tuple.category.Id, _componentLibraryActiveCategoryId, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
.index;
|
|
|
|
|
|
_componentLibraryCategoryIndex = Math.Clamp(activeIndex, 0, Math.Max(0, categoryCount - 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryCategoryIndex = Math.Clamp(_componentLibraryCategoryIndex, 0, Math.Max(0, categoryCount - 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryActiveCategoryId = _componentLibraryCategories[_componentLibraryCategoryIndex].Id;
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < categoryCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var category = _componentLibraryCategories[i];
|
|
|
|
|
|
var page = new Grid
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = _componentLibraryCategoryPageWidth,
|
|
|
|
|
|
Height = viewportHeight,
|
|
|
|
|
|
Background = Brushes.Transparent
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var cardWidth = Math.Clamp(_componentLibraryCategoryPageWidth * 0.64, 160, 260);
|
|
|
|
|
|
var cardHeight = Math.Clamp(viewportHeight * 0.70, 140, 220);
|
|
|
|
|
|
|
|
|
|
|
|
var iconSize = Math.Clamp(cardHeight * 0.34, 30, 56);
|
|
|
|
|
|
|
|
|
|
|
|
var card = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Classes = { "glass-panel" },
|
|
|
|
|
|
Width = cardWidth,
|
|
|
|
|
|
Height = cardHeight,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CornerRadius = new CornerRadius(36),
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Padding = new Thickness(18),
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
|
Child = new StackPanel
|
|
|
|
|
|
{
|
|
|
|
|
|
Spacing = 12,
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
new SymbolIcon
|
|
|
|
|
|
{
|
|
|
|
|
|
Symbol = category.Icon,
|
|
|
|
|
|
IconVariant = IconVariant.Regular,
|
|
|
|
|
|
FontSize = iconSize,
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center
|
|
|
|
|
|
},
|
|
|
|
|
|
new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = category.Title,
|
|
|
|
|
|
FontSize = Math.Clamp(cardHeight * 0.14, 12, 18),
|
|
|
|
|
|
FontWeight = FontWeight.SemiBold,
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
|
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
page.Children.Add(card);
|
|
|
|
|
|
|
|
|
|
|
|
Grid.SetRow(page, 0);
|
|
|
|
|
|
Grid.SetColumn(page, i);
|
|
|
|
|
|
ComponentLibraryCategoryPagesContainer.Children.Add(page);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategoryHostTransform = ComponentLibraryCategoryPagesHost.RenderTransform as TranslateTransform;
|
|
|
|
|
|
if (_componentLibraryCategoryHostTransform is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryCategoryHostTransform = new TranslateTransform();
|
|
|
|
|
|
ComponentLibraryCategoryPagesHost.RenderTransform = _componentLibraryCategoryHostTransform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ApplyComponentLibraryCategoryOffset();
|
|
|
|
|
|
|
|
|
|
|
|
if (ComponentLibraryBackTextBlock is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryBackTextBlock.Text = L("common.back", "Back");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IReadOnlyList<ComponentLibraryCategory> GetComponentLibraryCategories()
|
|
|
|
|
|
{
|
|
|
|
|
|
var definitions = _componentRegistry
|
|
|
|
|
|
.GetAll()
|
|
|
|
|
|
.Where(definition => definition.AllowDesktopPlacement)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (definitions.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Array.Empty<ComponentLibraryCategory>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return definitions
|
|
|
|
|
|
.GroupBy(definition => definition.Category, StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
.OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
.Select(group =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var categoryId = string.IsNullOrWhiteSpace(group.Key) ? "Other" : group.Key.Trim();
|
|
|
|
|
|
var components = group
|
|
|
|
|
|
.OrderBy(definition => definition.DisplayName, StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
return new ComponentLibraryCategory(
|
|
|
|
|
|
categoryId,
|
|
|
|
|
|
ResolveComponentLibraryCategoryIcon(categoryId),
|
|
|
|
|
|
GetLocalizedComponentLibraryCategoryTitle(categoryId),
|
|
|
|
|
|
components);
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Symbol ResolveComponentLibraryCategoryIcon(string categoryId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.Equals(categoryId, "Date", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return Symbol.CalendarDate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Symbol.Apps;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetLocalizedComponentLibraryCategoryTitle(string categoryId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.Equals(categoryId, "Date", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
2026-03-02 20:02:14 +08:00
|
|
|
|
return L("component_category.date", "Calendar");
|
2026-03-01 16:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return categoryId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyComponentLibraryCategoryOffset()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_componentLibraryCategoryHostTransform is null || _componentLibraryCategoryPageWidth <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategoryHostTransform.X = -_componentLibraryCategoryIndex * _componentLibraryCategoryPageWidth;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ApplyComponentLibraryComponentOffset()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_componentLibraryComponentHostTransform is null || _componentLibraryComponentPageWidth <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryComponentHostTransform.X = -_componentLibraryComponentIndex * _componentLibraryComponentPageWidth;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OpenComponentLibraryCurrentCategory()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_componentLibraryCategories.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategoryIndex = Math.Clamp(_componentLibraryCategoryIndex, 0, Math.Max(0, _componentLibraryCategories.Count - 1));
|
|
|
|
|
|
var category = _componentLibraryCategories[_componentLibraryCategoryIndex];
|
|
|
|
|
|
_componentLibraryActiveCategoryId = category.Id;
|
|
|
|
|
|
_componentLibraryComponentIndex = 0;
|
|
|
|
|
|
BuildComponentLibraryComponentPages(category);
|
|
|
|
|
|
ShowComponentLibraryComponentsView();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildComponentLibraryComponentPages(ComponentLibraryCategory category)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryComponentViewport is null ||
|
|
|
|
|
|
ComponentLibraryComponentPagesHost is null ||
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryActiveComponents = category.Components;
|
|
|
|
|
|
var componentCount = _componentLibraryActiveComponents.Count;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.Children.Clear();
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.RowDefinitions.Clear();
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.ColumnDefinitions.Clear();
|
|
|
|
|
|
if (componentCount == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryComponentIndex = 0;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var viewportWidth = ComponentLibraryComponentViewport.Bounds.Width;
|
|
|
|
|
|
if (viewportWidth <= 1 && ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
viewportWidth = Math.Max(1, ComponentLibraryWindow.Bounds.Width - 48);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var viewportHeight = ComponentLibraryComponentViewport.Bounds.Height;
|
|
|
|
|
|
if (viewportHeight <= 1 && ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
viewportHeight = Math.Max(1, ComponentLibraryWindow.Bounds.Height - 160);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryComponentPageWidth = Math.Max(1, viewportWidth);
|
|
|
|
|
|
ComponentLibraryComponentPagesHost.Width = _componentLibraryComponentPageWidth * componentCount;
|
|
|
|
|
|
ComponentLibraryComponentPagesHost.Height = viewportHeight;
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.Width = ComponentLibraryComponentPagesHost.Width;
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.Height = viewportHeight;
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.RowDefinitions.Add(new RowDefinition(new GridLength(viewportHeight, GridUnitType.Pixel)));
|
|
|
|
|
|
for (var i = 0; i < componentCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.ColumnDefinitions.Add(
|
|
|
|
|
|
new ColumnDefinition(new GridLength(_componentLibraryComponentPageWidth, GridUnitType.Pixel)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryComponentIndex = Math.Clamp(_componentLibraryComponentIndex, 0, Math.Max(0, componentCount - 1));
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < componentCount; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var definition = _componentLibraryActiveComponents[i];
|
|
|
|
|
|
if (!_componentRegistry.TryGetDefinition(definition.Id, out var resolved) || !resolved.AllowDesktopPlacement)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var page = new Grid
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = _componentLibraryComponentPageWidth,
|
|
|
|
|
|
Height = viewportHeight,
|
|
|
|
|
|
Background = Brushes.Transparent
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Fit the preview to the page while preserving component cell span proportions.
|
|
|
|
|
|
var previewMaxWidth = _componentLibraryComponentPageWidth * 0.86;
|
|
|
|
|
|
var previewMaxHeight = viewportHeight * 0.72;
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var previewSpan = NormalizeComponentCellSpan(
|
|
|
|
|
|
resolved.Id,
|
|
|
|
|
|
(resolved.MinWidthCells, resolved.MinHeightCells));
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var previewCellSize = Math.Min(
|
2026-03-02 20:02:14 +08:00
|
|
|
|
previewMaxWidth / Math.Max(1, previewSpan.WidthCells),
|
|
|
|
|
|
previewMaxHeight / Math.Max(1, previewSpan.HeightCells));
|
|
|
|
|
|
previewCellSize = Math.Clamp(previewCellSize, 20, 72);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var previewWidth = previewSpan.WidthCells * previewCellSize;
|
|
|
|
|
|
var previewHeight = previewSpan.HeightCells * previewCellSize;
|
|
|
|
|
|
var renderCellSize = Math.Clamp(previewCellSize * 1.35, 28, 82);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var previewControl = CreateComponentLibraryPreviewControl(resolved.Id, renderCellSize);
|
2026-03-01 16:50:06 +08:00
|
|
|
|
if (previewControl is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
var previewSurface = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = previewSpan.WidthCells * renderCellSize,
|
|
|
|
|
|
Height = previewSpan.HeightCells * renderCellSize,
|
|
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
Child = previewControl
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var previewViewbox = new Viewbox
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = previewWidth,
|
|
|
|
|
|
Height = previewHeight,
|
|
|
|
|
|
Stretch = Stretch.Uniform,
|
|
|
|
|
|
Child = previewSurface
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
var previewBorder = new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Width = previewWidth,
|
|
|
|
|
|
Height = previewHeight,
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CornerRadius = new CornerRadius(20),
|
2026-03-01 16:50:06 +08:00
|
|
|
|
ClipToBounds = true,
|
|
|
|
|
|
Background = Brushes.Transparent,
|
|
|
|
|
|
BorderThickness = new Thickness(0),
|
2026-03-02 20:02:14 +08:00
|
|
|
|
Child = previewViewbox,
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Tag = resolved.Id
|
|
|
|
|
|
};
|
|
|
|
|
|
previewBorder.PointerPressed += OnComponentLibraryComponentPreviewPointerPressed;
|
|
|
|
|
|
|
|
|
|
|
|
var label = new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = GetLocalizedComponentDisplayName(resolved),
|
|
|
|
|
|
FontSize = 14,
|
|
|
|
|
|
FontWeight = FontWeight.SemiBold,
|
|
|
|
|
|
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush"),
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var hint = new TextBlock
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = L("component_library.drag_hint", "Drag to place"),
|
|
|
|
|
|
FontSize = 12,
|
|
|
|
|
|
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush"),
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var stack = new StackPanel
|
|
|
|
|
|
{
|
|
|
|
|
|
Spacing = 10,
|
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
|
|
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
|
|
|
|
Children =
|
|
|
|
|
|
{
|
|
|
|
|
|
new Border
|
|
|
|
|
|
{
|
|
|
|
|
|
Classes = { "glass-panel" },
|
2026-03-02 20:02:14 +08:00
|
|
|
|
CornerRadius = new CornerRadius(28),
|
2026-03-01 16:50:06 +08:00
|
|
|
|
Padding = new Thickness(12),
|
|
|
|
|
|
Child = previewBorder
|
|
|
|
|
|
},
|
|
|
|
|
|
label,
|
|
|
|
|
|
hint
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
page.Children.Add(stack);
|
|
|
|
|
|
|
|
|
|
|
|
Grid.SetRow(page, 0);
|
|
|
|
|
|
Grid.SetColumn(page, i);
|
|
|
|
|
|
ComponentLibraryComponentPagesContainer.Children.Add(page);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryComponentHostTransform = ComponentLibraryComponentPagesHost.RenderTransform as TranslateTransform;
|
|
|
|
|
|
if (_componentLibraryComponentHostTransform is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryComponentHostTransform = new TranslateTransform();
|
|
|
|
|
|
ComponentLibraryComponentPagesHost.RenderTransform = _componentLibraryComponentHostTransform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ApplyComponentLibraryComponentOffset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Control? CreateComponentLibraryPreviewControl(string componentId, double cellSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (componentId == BuiltInComponentIds.Date)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new DateWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(cellSize);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (componentId == BuiltInComponentIds.MonthCalendar)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new MonthCalendarWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(cellSize);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (componentId == BuiltInComponentIds.LunarCalendar)
|
|
|
|
|
|
{
|
|
|
|
|
|
var widget = new LunarCalendarWidget();
|
|
|
|
|
|
widget.SetTimeZoneService(_timeZoneService);
|
|
|
|
|
|
widget.ApplyCellSize(cellSize);
|
|
|
|
|
|
return widget;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetLocalizedComponentDisplayName(DesktopComponentDefinition definition)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.Equals(definition.Id, BuiltInComponentIds.Date, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return L("component.date", definition.DisplayName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
if (string.Equals(definition.Id, BuiltInComponentIds.MonthCalendar, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return L("component.month_calendar", definition.DisplayName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.Equals(definition.Id, BuiltInComponentIds.LunarCalendar, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return L("component.lunar_calendar", definition.DisplayName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
return definition.DisplayName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryComponentPreviewPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (sender is not Border border ||
|
|
|
|
|
|
border.Tag is not string componentId ||
|
|
|
|
|
|
!e.GetCurrentPoint(border).Properties.IsLeftButtonPressed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BeginDesktopComponentNewDrag(componentId, e);
|
|
|
|
|
|
if (_isDesktopComponentDragActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private bool _isComponentLibraryWindowDragging;
|
|
|
|
|
|
private Point _componentLibraryWindowDragStartPoint;
|
|
|
|
|
|
private Thickness _componentLibraryWindowOriginalMargin;
|
|
|
|
|
|
private bool _isComponentLibraryWindowPositionCustomized;
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryWindowPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryWindow is null || !_isComponentLibraryOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var point = e.GetPosition(ComponentLibraryWindow);
|
|
|
|
|
|
if (point.Y > 40) // 閺嶅洭顣介弽蹇涚彯鎼达妇瀹虫稉?0px
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryWindowDragging = true;
|
|
|
|
|
|
_componentLibraryWindowDragStartPoint = e.GetPosition(this);
|
|
|
|
|
|
_componentLibraryWindowOriginalMargin = ComponentLibraryWindow.Margin;
|
|
|
|
|
|
|
|
|
|
|
|
e.Pointer.Capture(ComponentLibraryWindow);
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryWindowPointerMoved(object? sender, PointerEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryWindowDragging || ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var currentPoint = e.GetPosition(this);
|
|
|
|
|
|
var delta = currentPoint - _componentLibraryWindowDragStartPoint;
|
|
|
|
|
|
|
|
|
|
|
|
var newMargin = new Thickness(
|
|
|
|
|
|
Math.Max(10, _componentLibraryWindowOriginalMargin.Left + delta.X),
|
|
|
|
|
|
Math.Max(10, _componentLibraryWindowOriginalMargin.Top + delta.Y),
|
|
|
|
|
|
Math.Max(10, _componentLibraryWindowOriginalMargin.Right - delta.X),
|
|
|
|
|
|
Math.Max(10, _componentLibraryWindowOriginalMargin.Bottom - delta.Y)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryWindow.Margin = newMargin;
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryWindowPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryWindowDragging)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryWindowDragging = false;
|
|
|
|
|
|
e.Pointer.Capture(null);
|
|
|
|
|
|
|
|
|
|
|
|
if (ComponentLibraryWindow is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
SaveComponentLibraryWindowPosition();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void OnComponentLibraryBackClick(object? sender, RoutedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
ShowComponentLibraryCategoryView();
|
|
|
|
|
|
BuildComponentLibraryCategoryPages();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryCategoryViewportPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen ||
|
|
|
|
|
|
_componentLibraryCategories.Count == 0 ||
|
|
|
|
|
|
ComponentLibraryCategoryViewport is null ||
|
|
|
|
|
|
_componentLibraryCategoryHostTransform is null ||
|
|
|
|
|
|
!e.GetCurrentPoint(ComponentLibraryCategoryViewport).Properties.IsLeftButtonPressed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryCategoryGestureActive = true;
|
|
|
|
|
|
_componentLibraryCategoryGestureStartPoint = e.GetPosition(ComponentLibraryCategoryViewport);
|
|
|
|
|
|
_componentLibraryCategoryGestureCurrentPoint = _componentLibraryCategoryGestureStartPoint;
|
|
|
|
|
|
_componentLibraryCategoryGestureBaseOffset = -_componentLibraryCategoryIndex * _componentLibraryCategoryPageWidth;
|
|
|
|
|
|
e.Pointer.Capture(ComponentLibraryCategoryViewport);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryCategoryViewportPointerMoved(object? sender, PointerEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryCategoryGestureActive ||
|
|
|
|
|
|
ComponentLibraryCategoryViewport is null ||
|
|
|
|
|
|
_componentLibraryCategoryHostTransform is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryCategoryGestureCurrentPoint = e.GetPosition(ComponentLibraryCategoryViewport);
|
|
|
|
|
|
var deltaX = _componentLibraryCategoryGestureCurrentPoint.X - _componentLibraryCategoryGestureStartPoint.X;
|
|
|
|
|
|
var minOffset = -Math.Max(0, _componentLibraryCategories.Count - 1) * _componentLibraryCategoryPageWidth;
|
|
|
|
|
|
var tentative = _componentLibraryCategoryGestureBaseOffset + deltaX;
|
|
|
|
|
|
_componentLibraryCategoryHostTransform.X = Math.Clamp(tentative, minOffset, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryCategoryViewportPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryCategoryGestureActive ||
|
|
|
|
|
|
ComponentLibraryCategoryViewport is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryCategoryGestureActive = false;
|
|
|
|
|
|
e.Pointer.Capture(null);
|
|
|
|
|
|
|
|
|
|
|
|
var endPoint = e.GetPosition(ComponentLibraryCategoryViewport);
|
|
|
|
|
|
var deltaX = endPoint.X - _componentLibraryCategoryGestureStartPoint.X;
|
|
|
|
|
|
var deltaY = endPoint.Y - _componentLibraryCategoryGestureStartPoint.Y;
|
|
|
|
|
|
|
|
|
|
|
|
var tapThreshold = 6;
|
|
|
|
|
|
if (Math.Abs(deltaX) <= tapThreshold && Math.Abs(deltaY) <= tapThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenComponentLibraryCurrentCategory();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var swipeThreshold = Math.Max(40, _componentLibraryCategoryPageWidth * 0.18);
|
|
|
|
|
|
if (deltaX <= -swipeThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryCategoryIndex = Math.Min(_componentLibraryCategoryIndex + 1, Math.Max(0, _componentLibraryCategories.Count - 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (deltaX >= swipeThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryCategoryIndex = Math.Max(_componentLibraryCategoryIndex - 1, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryActiveCategoryId = _componentLibraryCategories.Count > 0
|
|
|
|
|
|
? _componentLibraryCategories[_componentLibraryCategoryIndex].Id
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
ApplyComponentLibraryCategoryOffset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryCategoryViewportPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryCategoryGestureActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryCategoryGestureActive = false;
|
|
|
|
|
|
ApplyComponentLibraryCategoryOffset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryComponentViewportPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryOpen ||
|
|
|
|
|
|
_componentLibraryActiveComponents.Count <= 1 ||
|
|
|
|
|
|
ComponentLibraryComponentViewport is null ||
|
|
|
|
|
|
_componentLibraryComponentHostTransform is null ||
|
|
|
|
|
|
!e.GetCurrentPoint(ComponentLibraryComponentViewport).Properties.IsLeftButtonPressed)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryComponentGestureActive = true;
|
|
|
|
|
|
_componentLibraryComponentGestureStartPoint = e.GetPosition(ComponentLibraryComponentViewport);
|
|
|
|
|
|
_componentLibraryComponentGestureCurrentPoint = _componentLibraryComponentGestureStartPoint;
|
|
|
|
|
|
_componentLibraryComponentGestureBaseOffset = -_componentLibraryComponentIndex * _componentLibraryComponentPageWidth;
|
|
|
|
|
|
e.Pointer.Capture(ComponentLibraryComponentViewport);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryComponentViewportPointerMoved(object? sender, PointerEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryComponentGestureActive ||
|
|
|
|
|
|
ComponentLibraryComponentViewport is null ||
|
|
|
|
|
|
_componentLibraryComponentHostTransform is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_componentLibraryComponentGestureCurrentPoint = e.GetPosition(ComponentLibraryComponentViewport);
|
|
|
|
|
|
var deltaX = _componentLibraryComponentGestureCurrentPoint.X - _componentLibraryComponentGestureStartPoint.X;
|
|
|
|
|
|
var minOffset = -Math.Max(0, _componentLibraryActiveComponents.Count - 1) * _componentLibraryComponentPageWidth;
|
|
|
|
|
|
var tentative = _componentLibraryComponentGestureBaseOffset + deltaX;
|
|
|
|
|
|
_componentLibraryComponentHostTransform.X = Math.Clamp(tentative, minOffset, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-02 20:02:14 +08:00
|
|
|
|
private void SaveComponentLibraryWindowPosition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var margin = ComponentLibraryWindow.Margin;
|
|
|
|
|
|
_savedComponentLibraryMargin = margin;
|
|
|
|
|
|
_isComponentLibraryWindowPositionCustomized = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void RestoreComponentLibraryWindowPosition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ComponentLibraryWindow is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ComponentLibraryWindow.Margin = _savedComponentLibraryMargin;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Thickness _savedComponentLibraryMargin = new Thickness(24, 24, 24, 100);
|
|
|
|
|
|
|
2026-03-01 16:50:06 +08:00
|
|
|
|
private void OnComponentLibraryComponentViewportPointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryComponentGestureActive ||
|
|
|
|
|
|
ComponentLibraryComponentViewport is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryComponentGestureActive = false;
|
|
|
|
|
|
e.Pointer.Capture(null);
|
|
|
|
|
|
|
|
|
|
|
|
var endPoint = e.GetPosition(ComponentLibraryComponentViewport);
|
|
|
|
|
|
var deltaX = endPoint.X - _componentLibraryComponentGestureStartPoint.X;
|
|
|
|
|
|
|
|
|
|
|
|
var swipeThreshold = Math.Max(40, _componentLibraryComponentPageWidth * 0.18);
|
|
|
|
|
|
if (deltaX <= -swipeThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryComponentIndex = Math.Min(_componentLibraryComponentIndex + 1, Math.Max(0, _componentLibraryActiveComponents.Count - 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (deltaX >= swipeThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
_componentLibraryComponentIndex = Math.Max(_componentLibraryComponentIndex - 1, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ApplyComponentLibraryComponentOffset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnComponentLibraryComponentViewportPointerCaptureLost(object? sender, PointerCaptureLostEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_isComponentLibraryComponentGestureActive)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_isComponentLibraryComponentGestureActive = false;
|
|
|
|
|
|
ApplyComponentLibraryComponentOffset();
|
2026-02-28 12:30:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 20:02:14 +08:00
|
|
|
|
|