using System; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Disposables.Fluent; using System.Reactive.Linq; using Artemis.UI.Screens.ProfileEditor.VisualEditor; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.PanAndZoom; using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Threading; using ReactiveUI; using ReactiveUI.Avalonia; namespace Artemis.UI.Screens.ProfileEditor.ConfigurationPanels.Preview; public partial class PreviewView : ReactiveUserControl { private bool _movedByUser; public PreviewView() { InitializeComponent(); ZoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged; ZoomBorder.PointerMoved += ZoomBorderOnPointerMoved; ZoomBorder.PointerWheelChanged += ZoomBorderOnPointerWheelChanged; UpdateZoomBorderBackground(); this.WhenActivated(d => { PreviewViewModel vm = ViewModel!; vm.AutoFitRequested += ViewModelOnAutoFitRequested; Disposable.Create(() => vm.AutoFitRequested -= ViewModelOnAutoFitRequested).DisposeWith(d); }); this.WhenAnyValue(v => v.Bounds).Where(_ => !_movedByUser).Subscribe(_ => AutoFit(true)); } private void ZoomBorderOnPointerWheelChanged(object? sender, PointerWheelEventArgs e) { _movedByUser = true; } private void ZoomBorderOnPointerMoved(object? sender, PointerEventArgs e) { if (e.GetCurrentPoint(ZoomBorder).Properties.IsMiddleButtonPressed) _movedByUser = true; } private void ViewModelOnAutoFitRequested(object? sender, EventArgs e) { Dispatcher.UIThread.Post(() => AutoFit(false)); } private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.Property.Name == nameof(ZoomBorder.Background)) UpdateZoomBorderBackground(); } private void UpdateZoomBorderBackground() { if (ZoomBorder.Background is VisualBrush visualBrush) visualBrush.DestinationRect = new RelativeRect(ZoomBorder.OffsetX, ZoomBorder.OffsetY, 20, 20, RelativeUnit.Absolute); } private void ZoomBorder_OnZoomChanged(object sender, ZoomChangedEventArgs e) { UpdateZoomBorderBackground(); } private void AutoFit(bool skipTransitions) { if (ViewModel == null || !ViewModel.Devices.Any()) return; double left = ViewModel.Devices.Select(d => d.Rectangle.Left).Min(); double top = ViewModel.Devices.Select(d => d.Rectangle.Top).Min(); double bottom = ViewModel.Devices.Select(d => d.Rectangle.Bottom).Max(); double right = ViewModel.Devices.Select(d => d.Rectangle.Right).Max(); // Add a 10 pixel margin around the rect Rect scriptRect = new(new Point(left - 10, top - 10), new Point(right + 10, bottom + 10)); // The scale depends on the available space double scale = Math.Min(3, Math.Min(Bounds.Width / scriptRect.Width, Bounds.Height / scriptRect.Height)); // Pan and zoom to make the script fit ZoomBorder.Zoom(scale, 0, 0, skipTransitions); ZoomBorder.Pan(Bounds.Center.X - scriptRect.Center.X * scale, Bounds.Center.Y - scriptRect.Center.Y * scale, skipTransitions); _movedByUser = false; } }