diff --git a/src/VirtualStreetSnap/Assets/i18n/en-US.json b/src/VirtualStreetSnap/Assets/i18n/en-US.json index 7af6207..77cbd59 100644 --- a/src/VirtualStreetSnap/Assets/i18n/en-US.json +++ b/src/VirtualStreetSnap/Assets/i18n/en-US.json @@ -76,5 +76,7 @@ "BorderColor": "Border Color", "CommonSettings": "Common Settings", "BorderThickness": "Border Thickness", - "BorderSettings": "Border Settings" + "BorderSettings": "Border Settings", + "Copied": "Copied", + } \ No newline at end of file diff --git a/src/VirtualStreetSnap/Assets/i18n/zh-CN.json b/src/VirtualStreetSnap/Assets/i18n/zh-CN.json index 802252c..40efc48 100644 --- a/src/VirtualStreetSnap/Assets/i18n/zh-CN.json +++ b/src/VirtualStreetSnap/Assets/i18n/zh-CN.json @@ -76,5 +76,6 @@ "BorderColor": "边框颜色", "CommonSettings": "常规设置", "BorderThickness": "边框厚度", - "BorderSettings": "边框设置" + "BorderSettings": "边框设置", + "Copied": "已复制", } \ No newline at end of file diff --git a/src/VirtualStreetSnap/Models/ImageModelBase.cs b/src/VirtualStreetSnap/ViewModels/ImageModelBase.cs similarity index 64% rename from src/VirtualStreetSnap/Models/ImageModelBase.cs rename to src/VirtualStreetSnap/ViewModels/ImageModelBase.cs index 7bdef38..7bfe89a 100644 --- a/src/VirtualStreetSnap/Models/ImageModelBase.cs +++ b/src/VirtualStreetSnap/ViewModels/ImageModelBase.cs @@ -4,19 +4,34 @@ using System.Threading.Tasks; using Avalonia.Media.Imaging; using Avalonia.Platform; +using CommunityToolkit.Mvvm.ComponentModel; using VirtualStreetSnap.Services; -namespace VirtualStreetSnap.Models; +namespace VirtualStreetSnap.ViewModels; -public class ImageModelBase : INotifyPropertyChanged +public partial class ImageModelBase : ViewModelBase { private const string DefaultImagePath = "avares://VirtualStreetSnap/Assets/avalonia-logo.ico"; private const string LoadingImagePath = "avares://VirtualStreetSnap/Assets/Images/LoadingImage.png"; - public string ImgPath { get; set; } = ""; - public string ImgDir { get; set; } = ""; - public string ImgName { get; set; } - public string ImgSize { get; set; } = "0 x 0"; + [ObservableProperty] + private string _imgPath = ""; + + [ObservableProperty] + private string _imgDir = ""; + + [ObservableProperty] + private string _imgName; + + [ObservableProperty] + private string _imgSize = "0 x 0"; + + [ObservableProperty] + private Bitmap? _imageThumb; + + [ObservableProperty] + private Bitmap? _image; + public int ImgThumbSize { get; set; } = 100; public ImageModelBase(string imgPath = "") @@ -28,35 +43,10 @@ public ImageModelBase(string imgPath = "") LoadThumbAsync(); } - private Bitmap? _image; - private Bitmap? _imageThumb; - - public Bitmap Image - { - get => _image; - set - { - _image = value; - ImgSize = $"{value.Size.Width} x {value.Size.Height}"; - OnPropertyChanged(nameof(Image)); - } - } - - public Bitmap? ImageThumb - { - get => _imageThumb; - private set - { - _imageThumb = value; - OnPropertyChanged(nameof(ImageThumb)); - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - protected virtual void OnPropertyChanged(string propertyName) + partial void OnImageChanged(Bitmap value) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + ImgSize = $"{value.Size.Width} x {value.Size.Height}"; } diff --git a/src/VirtualStreetSnap/ViewModels/ImageViewerViewModel.cs b/src/VirtualStreetSnap/ViewModels/ImageViewerViewModel.cs index 961ef83..aff615c 100644 --- a/src/VirtualStreetSnap/ViewModels/ImageViewerViewModel.cs +++ b/src/VirtualStreetSnap/ViewModels/ImageViewerViewModel.cs @@ -16,7 +16,7 @@ public partial class ImageViewerViewModel : ViewModelBase private Bitmap? _image; public ImageViewerViewModel() - { + { ViewImage = new ImageModelBase(); } } \ No newline at end of file diff --git a/src/VirtualStreetSnap/Views/ConfirmDialog.axaml b/src/VirtualStreetSnap/Views/ConfirmDialog.axaml index e26ed1d..aa59966 100644 --- a/src/VirtualStreetSnap/Views/ConfirmDialog.axaml +++ b/src/VirtualStreetSnap/Views/ConfirmDialog.axaml @@ -11,7 +11,7 @@ Height="{Binding Height}" ExtendClientAreaToDecorationsHint="True" ExtendClientAreaTitleBarHeightHint="0" - WindowStartupLocation="CenterScreen"> + WindowStartupLocation="CenterOwner"> diff --git a/src/VirtualStreetSnap/Views/ImageGalleryView.axaml b/src/VirtualStreetSnap/Views/ImageGalleryView.axaml index 91c40f6..db21009 100644 --- a/src/VirtualStreetSnap/Views/ImageGalleryView.axaml +++ b/src/VirtualStreetSnap/Views/ImageGalleryView.axaml @@ -4,7 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:VirtualStreetSnap.ViewModels" xmlns:i18n="clr-namespace:VirtualStreetSnap.Localizer" - xmlns:models="clr-namespace:VirtualStreetSnap.Models" mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="720" x:Class="VirtualStreetSnap.Views.ImageGalleryView" x:DataType="viewModels:ImageGalleryViewModel"> @@ -76,7 +75,7 @@ - + - + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -80,7 +93,7 @@ - + diff --git a/src/VirtualStreetSnap/Views/ImageViewerView.axaml.cs b/src/VirtualStreetSnap/Views/ImageViewerView.axaml.cs index 33eec8c..6c63b39 100644 --- a/src/VirtualStreetSnap/Views/ImageViewerView.axaml.cs +++ b/src/VirtualStreetSnap/Views/ImageViewerView.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -12,50 +13,80 @@ namespace VirtualStreetSnap.Views; public partial class ImageViewerView : UserControl { - private double _currentScale = 1.0; + // zoom + private const double ZoomScaleLogMap = 1.05; private const double InfoDisplayScaleThreshold = 1.2; - private const double ScaleStep = 0.1; private const double MinScale = 0.5; private const double MaxScale = 10.0; + + private double _currentScale = 1.0; + + // zoom animation + private double _zoomVelocity; + private bool _isZooming; + private const double ZoomAnimSpeed = 1.5; + private const double ZoomAnimStopVelocity = 0.05; + private const double ZoomAnimSpeedDecay = 0.85; + private const int ZoomUpdateInterval = 8; //ms + + // pan private Point _lastMovePoint; private Point _lastPanPoint; private bool _isPanning; private readonly ScaleTransform _scaleTransform = new(); private readonly TranslateTransform _translateTransform = new(); + + // color picker private SKBitmap? _pickedColorImage; public ImageViewerView() { InitializeComponent(); - var imageViewbox = ImageViewbox; - imageViewbox.PointerWheelChanged += ImageViewbox_PointerWheelChanged; - var transformGroup = new TransformGroup(); transformGroup.Children.Add(_scaleTransform); transformGroup.Children.Add(_translateTransform); - imageViewbox.RenderTransform = transformGroup; + ViewBoxImage.RenderTransform = transformGroup; } private bool IsPickingColor => DataContext is ImageViewerViewModel { ShowColorPicker: true }; - private void ImageViewbox_PointerWheelChanged(object? sender, PointerWheelEventArgs e) + private void Viewer_PointerWheelChanged(object? sender, PointerWheelEventArgs e) { - if (sender is not Viewbox) return; + if (sender is not Control) return; if (IsPickingColor) return; - _currentScale = e.Delta.Y > 0 - ? Math.Min(_currentScale + ScaleStep, MaxScale) - : Math.Max(_currentScale - ScaleStep, MinScale); - ImageInfoPanel.Opacity = _currentScale > InfoDisplayScaleThreshold? 0 : 1; - _scaleTransform.ScaleX = _currentScale; - _scaleTransform.ScaleY = _currentScale; + var delta = e.Delta.Y > 0 ? 1 : -1; + _zoomVelocity = delta * ZoomAnimSpeed; + if (!_isZooming) + { + _isZooming = true; + StartZoomInertia(); + } + e.Handled = true; } - private void ImageViewbox_PointerPressed(object? sender, PointerPressedEventArgs e) + private async void StartZoomInertia() + { + while (Math.Abs(_zoomVelocity) > ZoomAnimStopVelocity) + { + _currentScale *= Math.Pow(ZoomScaleLogMap, _zoomVelocity); + _currentScale = Math.Clamp(_currentScale, MinScale, MaxScale); + + ImageInfoPanel.Opacity = _currentScale > InfoDisplayScaleThreshold ? 0 : 1; + _scaleTransform.ScaleX = _currentScale; + _scaleTransform.ScaleY = _currentScale; + + _zoomVelocity *= ZoomAnimSpeedDecay; + await Task.Delay(ZoomUpdateInterval); + } + + _isZooming = false; + } + + private void Viewer_PointerPressed(object? sender, PointerPressedEventArgs e) { - if (sender is not Viewbox) return; _lastMovePoint = e.GetPosition(this); if (!e.GetCurrentPoint(this).Properties.IsMiddleButtonPressed && @@ -66,6 +97,7 @@ private void ImageViewbox_PointerPressed(object? sender, PointerPressedEventArgs var viewModel = (ImageViewerViewModel)DataContext!; viewModel.ShowColorPicker = false; _ = PowerShellClipBoard.SetText(ColorPickerTextHex.Text!); + NotifyHelper.Notify(viewModel, Localizer.Localizer.Instance["Copied"], ColorPickerTextHex.Text); return; } @@ -74,21 +106,20 @@ private void ImageViewbox_PointerPressed(object? sender, PointerPressedEventArgs e.Pointer.Capture((IInputElement)sender!); } - private void ImageViewbox_PointerReleased(object? sender, PointerReleasedEventArgs e) + private void Viewer_PointerReleased(object? sender, PointerReleasedEventArgs e) { - if (sender is not Viewbox) return; if (!_isPanning || (e.InitialPressMouseButton != MouseButton.Middle && e.InitialPressMouseButton != MouseButton.Left)) return; _isPanning = false; e.Pointer.Capture(null); } - private void ImageViewbox_PointerMoved(object? sender, PointerEventArgs e) + private void Viewer_PointerMoved(object? sender, PointerEventArgs e) { - if (sender is not Viewbox viewbox) return; + if (sender is not Control viewbox) return; var currentPoint = e.GetPosition(viewbox); - GetColorAndSetText(e, viewbox, currentPoint); + GetColorAndSetText(e, currentPoint); if (!_isPanning) return; var delta = e.GetPosition(this) - _lastPanPoint; @@ -98,19 +129,16 @@ private void ImageViewbox_PointerMoved(object? sender, PointerEventArgs e) _translateTransform.Y += delta.Y; } - private void GetColorAndSetText(PointerEventArgs e, Viewbox viewbox, Point currentPoint) + private void GetColorAndSetText(PointerEventArgs e, Point currentPoint) { - if (!IsPickingColor || viewbox.Child is not Image image) return; - + if (!IsPickingColor || ViewBoxImage is not Image image) return; + var imageBounds = image.Bounds; - var viewboxBounds = viewbox.Bounds; - var scaleX = imageBounds.Width / viewboxBounds.Width; - var scaleY = imageBounds.Height / viewboxBounds.Height; - var imagePoint = new Point(currentPoint.X * scaleX, currentPoint.Y * scaleY); + var imagePoint = new Point(currentPoint.X, currentPoint.Y); if (imagePoint is not { X: >= 0, Y: >= 0 }) return; if (!(imagePoint.X < imageBounds.Width) || !(imagePoint.Y < imageBounds.Height)) return; - + var color = ScreenshotHelper.GetColorFromSKBitmap(_pickedColorImage, imagePoint); var colorHex = color.ToString().Substring(3); color.ToHsv(out var h, out var s, out var v); @@ -147,7 +175,7 @@ private void OnShowColorPicker_Click(object? sender, RoutedEventArgs e) { if (DataContext is not ImageViewerViewModel viewModel) return; viewModel.ShowColorPicker = true; - if (ImageViewbox.Child is not Image image) return; + if (ViewBoxImage is not Image image) return; _pickedColorImage = ScreenshotHelper.CaptureControlSKBitmap(image); Canvas.SetLeft(ColoPickerPanel, _lastPanPoint.X); Canvas.SetTop(ColoPickerPanel, _lastPanPoint.Y); diff --git a/src/VirtualStreetSnap/VirtualStreetSnap.csproj b/src/VirtualStreetSnap/VirtualStreetSnap.csproj index 5f3549d..1cfba42 100644 --- a/src/VirtualStreetSnap/VirtualStreetSnap.csproj +++ b/src/VirtualStreetSnap/VirtualStreetSnap.csproj @@ -15,7 +15,7 @@ true app.manifest true - 0.1.5.2 + 0.1.5.4 Atticus A simple screen shot tool design for VirtualStreetSnap