mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
359 lines
12 KiB
C#
359 lines
12 KiB
C#
using System;
|
|
using Avalonia;
|
|
using Avalonia.Animation;
|
|
using Avalonia.Animation.Easings;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Layout;
|
|
using Avalonia.Media;
|
|
|
|
namespace LanMountainDesktop.DesktopEditing;
|
|
|
|
internal sealed class DesktopEditGhostView : Border
|
|
{
|
|
private static readonly TimeSpan FastDuration = TimeSpan.FromMilliseconds(120);
|
|
private static readonly Easing StandardEasing = new CubicEaseOut();
|
|
|
|
private readonly Image _previewImage;
|
|
private readonly Border _previewOverlay;
|
|
private readonly Border _fallbackCard;
|
|
private readonly Border _accentDot;
|
|
private readonly TextBlock _titleTextBlock;
|
|
private readonly TextBlock _detailTextBlock;
|
|
private readonly Border _badgeBorder;
|
|
private readonly TextBlock _badgeTextBlock;
|
|
private readonly ScaleTransform _scaleTransform = new(1, 1);
|
|
|
|
private readonly SolidColorBrush _normalBackgroundBrush = new(Color.Parse("#F11B2430"));
|
|
private readonly SolidColorBrush _normalBorderBrush = new(Color.Parse("#4D8AA3C1"));
|
|
private readonly SolidColorBrush _normalAccentBrush = new(Color.Parse("#FF4F8EF7"));
|
|
private readonly SolidColorBrush _normalTextBrush = new(Color.Parse("#FFF5F7FA"));
|
|
private readonly SolidColorBrush _normalMutedTextBrush = new(Color.Parse("#BDE2E8F0"));
|
|
private readonly SolidColorBrush _normalBadgeBackgroundBrush = new(Color.Parse("#245E86D6"));
|
|
private readonly SolidColorBrush _normalBadgeBorderBrush = new(Color.Parse("#557EA7E6"));
|
|
private readonly SolidColorBrush _invalidBackgroundBrush = new(Color.Parse("#F01B1022"));
|
|
private readonly SolidColorBrush _invalidBorderBrush = new(Color.Parse("#FFE25555"));
|
|
private readonly SolidColorBrush _invalidAccentBrush = new(Color.Parse("#FFFF6B6B"));
|
|
private readonly SolidColorBrush _invalidBadgeBackgroundBrush = new(Color.Parse("#33FF4D4D"));
|
|
private readonly SolidColorBrush _invalidBadgeBorderBrush = new(Color.Parse("#88FF7676"));
|
|
|
|
private bool _hasPreviewImage;
|
|
private bool _isInvalid;
|
|
|
|
public DesktopEditGhostView()
|
|
{
|
|
HorizontalAlignment = HorizontalAlignment.Stretch;
|
|
VerticalAlignment = VerticalAlignment.Stretch;
|
|
Padding = new Thickness(14);
|
|
Background = _normalBackgroundBrush;
|
|
BorderBrush = _normalBorderBrush;
|
|
BorderThickness = new Thickness(1);
|
|
CornerRadius = new CornerRadius(22);
|
|
ClipToBounds = true;
|
|
RenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
|
|
RenderTransform = _scaleTransform;
|
|
Transitions = new Transitions
|
|
{
|
|
CreateOpacityTransition(FastDuration)
|
|
};
|
|
_scaleTransform.Transitions = new Transitions
|
|
{
|
|
CreateScaleTransition(ScaleTransform.ScaleXProperty, FastDuration),
|
|
CreateScaleTransition(ScaleTransform.ScaleYProperty, FastDuration)
|
|
};
|
|
|
|
_accentDot = new Border
|
|
{
|
|
Width = 10,
|
|
Height = 10,
|
|
CornerRadius = new CornerRadius(999),
|
|
Background = _normalAccentBrush,
|
|
BorderThickness = new Thickness(0),
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
|
|
_titleTextBlock = new TextBlock
|
|
{
|
|
Foreground = _normalTextBrush,
|
|
FontWeight = FontWeight.SemiBold,
|
|
TextTrimming = TextTrimming.CharacterEllipsis,
|
|
TextWrapping = TextWrapping.NoWrap,
|
|
MaxLines = 1
|
|
};
|
|
|
|
_detailTextBlock = new TextBlock
|
|
{
|
|
Foreground = _normalMutedTextBrush,
|
|
TextTrimming = TextTrimming.CharacterEllipsis,
|
|
TextWrapping = TextWrapping.NoWrap,
|
|
MaxLines = 1
|
|
};
|
|
|
|
_badgeTextBlock = new TextBlock
|
|
{
|
|
Foreground = _normalTextBrush,
|
|
HorizontalAlignment = HorizontalAlignment.Center,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
TextTrimming = TextTrimming.CharacterEllipsis,
|
|
TextWrapping = TextWrapping.NoWrap,
|
|
MaxLines = 1
|
|
};
|
|
|
|
_badgeBorder = new Border
|
|
{
|
|
HorizontalAlignment = HorizontalAlignment.Left,
|
|
VerticalAlignment = VerticalAlignment.Top,
|
|
Padding = new Thickness(9, 4),
|
|
CornerRadius = new CornerRadius(999),
|
|
Background = _normalBadgeBackgroundBrush,
|
|
BorderBrush = _normalBadgeBorderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
Child = _badgeTextBlock
|
|
};
|
|
|
|
_previewImage = new Image
|
|
{
|
|
Stretch = Stretch.UniformToFill,
|
|
IsVisible = false
|
|
};
|
|
|
|
_previewOverlay = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.Parse("#1A000000")),
|
|
IsVisible = false
|
|
};
|
|
|
|
var headerPanel = new StackPanel
|
|
{
|
|
Orientation = Orientation.Horizontal,
|
|
Spacing = 8,
|
|
Children =
|
|
{
|
|
_accentDot,
|
|
_titleTextBlock
|
|
}
|
|
};
|
|
|
|
var contentPanel = new StackPanel
|
|
{
|
|
Spacing = 6,
|
|
Children =
|
|
{
|
|
headerPanel,
|
|
_detailTextBlock
|
|
}
|
|
};
|
|
|
|
var fallbackGrid = new Grid
|
|
{
|
|
RowDefinitions = new RowDefinitions
|
|
{
|
|
new RowDefinition(GridLength.Auto),
|
|
new RowDefinition(GridLength.Auto)
|
|
},
|
|
RowSpacing = 8
|
|
};
|
|
fallbackGrid.Children.Add(contentPanel);
|
|
fallbackGrid.Children.Add(_badgeBorder);
|
|
Grid.SetRow(contentPanel, 0);
|
|
Grid.SetRow(_badgeBorder, 1);
|
|
_badgeBorder.Margin = new Thickness(0, 2, 0, 0);
|
|
|
|
_fallbackCard = new Border
|
|
{
|
|
Background = Brushes.Transparent,
|
|
Child = fallbackGrid
|
|
};
|
|
|
|
Child = new Grid
|
|
{
|
|
Children =
|
|
{
|
|
_previewImage,
|
|
_previewOverlay,
|
|
_fallbackCard
|
|
}
|
|
};
|
|
|
|
UpdatePreviewMetrics(180, 120);
|
|
UpdateContent(null, null, null);
|
|
ApplyShellChrome();
|
|
}
|
|
|
|
public void UpdateContent(string? title, string? detail, string? badgeText)
|
|
{
|
|
_titleTextBlock.Text = string.IsNullOrWhiteSpace(title) ? "Component" : title;
|
|
_detailTextBlock.Text = string.IsNullOrWhiteSpace(detail) ? string.Empty : detail;
|
|
_detailTextBlock.IsVisible = !string.IsNullOrWhiteSpace(detail);
|
|
_badgeTextBlock.Text = string.IsNullOrWhiteSpace(badgeText) ? string.Empty : badgeText;
|
|
_badgeBorder.IsVisible = !string.IsNullOrWhiteSpace(badgeText);
|
|
}
|
|
|
|
public void SetPreviewImage(IImage? image)
|
|
{
|
|
_previewImage.Source = image;
|
|
_hasPreviewImage = image is not null;
|
|
_previewImage.IsVisible = _hasPreviewImage;
|
|
_previewOverlay.IsVisible = false;
|
|
_fallbackCard.IsVisible = !_hasPreviewImage;
|
|
ApplyShellChrome();
|
|
}
|
|
|
|
public void UpdatePreviewMetrics(double width, double height)
|
|
{
|
|
var normalizedWidth = Math.Max(1, width);
|
|
var normalizedHeight = Math.Max(1, height);
|
|
var minSide = Math.Max(1, Math.Min(normalizedWidth, normalizedHeight));
|
|
|
|
CornerRadius = _hasPreviewImage
|
|
? new CornerRadius(Math.Clamp(minSide * 0.14, 14, 24))
|
|
: new CornerRadius(Math.Clamp(minSide * 0.16, 16, 28));
|
|
Padding = _hasPreviewImage
|
|
? new Thickness(
|
|
Math.Clamp(minSide * 0.02, 1, 4),
|
|
Math.Clamp(minSide * 0.02, 1, 4),
|
|
Math.Clamp(minSide * 0.02, 1, 4),
|
|
Math.Clamp(minSide * 0.02, 1, 4))
|
|
: new Thickness(
|
|
Math.Clamp(minSide * 0.10, 10, 18),
|
|
Math.Clamp(minSide * 0.10, 10, 18),
|
|
Math.Clamp(minSide * 0.10, 10, 18),
|
|
Math.Clamp(minSide * 0.09, 10, 16));
|
|
|
|
var titleFontSize = Math.Clamp(minSide * 0.12, 12, 18);
|
|
var detailFontSize = Math.Clamp(minSide * 0.085, 10, 13);
|
|
var badgeFontSize = Math.Clamp(minSide * 0.08, 9, 12);
|
|
var dotSize = Math.Clamp(minSide * 0.07, 8, 12);
|
|
var badgeHorizontalPadding = Math.Clamp(minSide * 0.07, 8, 14);
|
|
var badgeVerticalPadding = Math.Clamp(minSide * 0.035, 3, 6);
|
|
|
|
_accentDot.Width = dotSize;
|
|
_accentDot.Height = dotSize;
|
|
_titleTextBlock.FontSize = titleFontSize;
|
|
_detailTextBlock.FontSize = detailFontSize;
|
|
_badgeTextBlock.FontSize = badgeFontSize;
|
|
_badgeBorder.Padding = new Thickness(badgeHorizontalPadding, badgeVerticalPadding);
|
|
}
|
|
|
|
public void SetInvalid(bool isInvalid)
|
|
{
|
|
_isInvalid = isInvalid;
|
|
|
|
if (isInvalid)
|
|
{
|
|
_accentDot.Background = _invalidAccentBrush;
|
|
_badgeBorder.Background = _invalidBadgeBackgroundBrush;
|
|
_badgeBorder.BorderBrush = _invalidBadgeBorderBrush;
|
|
_titleTextBlock.Foreground = _invalidBorderBrush;
|
|
_detailTextBlock.Foreground = _invalidBorderBrush;
|
|
_badgeTextBlock.Foreground = _invalidBorderBrush;
|
|
if (!_hasPreviewImage)
|
|
{
|
|
Background = _invalidBackgroundBrush;
|
|
BorderBrush = _invalidBorderBrush;
|
|
BorderThickness = new Thickness(1);
|
|
Opacity = 0.9;
|
|
}
|
|
else
|
|
{
|
|
ApplyShellChrome();
|
|
}
|
|
return;
|
|
}
|
|
|
|
_accentDot.Background = _normalAccentBrush;
|
|
_badgeBorder.Background = _normalBadgeBackgroundBrush;
|
|
_badgeBorder.BorderBrush = _normalBadgeBorderBrush;
|
|
_titleTextBlock.Foreground = _normalTextBrush;
|
|
_detailTextBlock.Foreground = _normalMutedTextBrush;
|
|
_badgeTextBlock.Foreground = _normalTextBrush;
|
|
if (!_hasPreviewImage)
|
|
{
|
|
Background = _normalBackgroundBrush;
|
|
BorderBrush = _normalBorderBrush;
|
|
BorderThickness = new Thickness(1);
|
|
Opacity = 1.0;
|
|
}
|
|
else
|
|
{
|
|
ApplyShellChrome();
|
|
}
|
|
}
|
|
|
|
public void SetRestingScale(double scale)
|
|
{
|
|
var clampedScale = Math.Clamp(scale, 0.85, 1.12);
|
|
_scaleTransform.ScaleX = clampedScale;
|
|
_scaleTransform.ScaleY = clampedScale;
|
|
}
|
|
|
|
public void AnimateToScale(double scale)
|
|
{
|
|
var clampedScale = Math.Clamp(scale, 0.85, 1.12);
|
|
_scaleTransform.ScaleX = clampedScale;
|
|
_scaleTransform.ScaleY = clampedScale;
|
|
}
|
|
|
|
internal bool HasPreviewImage => _hasPreviewImage;
|
|
|
|
internal void SetScaleTransitionDuration(TimeSpan duration)
|
|
{
|
|
_scaleTransform.Transitions = new Transitions
|
|
{
|
|
CreateScaleTransition(ScaleTransform.ScaleXProperty, duration),
|
|
CreateScaleTransition(ScaleTransform.ScaleYProperty, duration)
|
|
};
|
|
}
|
|
|
|
internal void SetOpacityTransitionDuration(TimeSpan duration)
|
|
{
|
|
Transitions = new Transitions
|
|
{
|
|
CreateOpacityTransition(duration)
|
|
};
|
|
}
|
|
|
|
private void ApplyShellChrome()
|
|
{
|
|
if (_hasPreviewImage)
|
|
{
|
|
Background = Brushes.Transparent;
|
|
BorderBrush = Brushes.Transparent;
|
|
BorderThickness = new Thickness(0);
|
|
BoxShadow = BoxShadows.Parse("0 14 32 #1A000000");
|
|
Opacity = 1.0;
|
|
return;
|
|
}
|
|
|
|
BoxShadow = default;
|
|
if (_isInvalid)
|
|
{
|
|
Background = _invalidBackgroundBrush;
|
|
BorderBrush = _invalidBorderBrush;
|
|
BorderThickness = new Thickness(1);
|
|
Opacity = 0.9;
|
|
return;
|
|
}
|
|
|
|
Background = _normalBackgroundBrush;
|
|
BorderBrush = _normalBorderBrush;
|
|
BorderThickness = new Thickness(1);
|
|
Opacity = 1.0;
|
|
}
|
|
|
|
private static DoubleTransition CreateScaleTransition(AvaloniaProperty property, TimeSpan duration) =>
|
|
new()
|
|
{
|
|
Property = property,
|
|
Duration = duration,
|
|
Easing = StandardEasing
|
|
};
|
|
|
|
private static DoubleTransition CreateOpacityTransition(TimeSpan duration) =>
|
|
new()
|
|
{
|
|
Property = Visual.OpacityProperty,
|
|
Duration = duration,
|
|
Easing = StandardEasing
|
|
};
|
|
}
|