diff --git a/Aaru.Gui/Controls/DiscSpeedGraph.axaml b/Aaru.Gui/Controls/DiscSpeedGraph.axaml index 2e292d722..44f5215ca 100644 --- a/Aaru.Gui/Controls/DiscSpeedGraph.axaml +++ b/Aaru.Gui/Controls/DiscSpeedGraph.axaml @@ -5,7 +5,9 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Aaru.Gui.Controls.DiscSpeedGraph"> + x:Class="Aaru.Gui.Controls.DiscSpeedGraph" + Focusable="True" + IsTabStop="True"> \ No newline at end of file diff --git a/Aaru.Gui/Controls/DiscSpeedGraph.axaml.cs b/Aaru.Gui/Controls/DiscSpeedGraph.axaml.cs index 97e3421af..825683a19 100644 --- a/Aaru.Gui/Controls/DiscSpeedGraph.axaml.cs +++ b/Aaru.Gui/Controls/DiscSpeedGraph.axaml.cs @@ -44,6 +44,7 @@ using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Shapes; +using Avalonia.Input; using Avalonia.Markup.Xaml; using Avalonia.Media; @@ -55,10 +56,13 @@ namespace Aaru.Gui.Controls; /// public partial class DiscSpeedGraph : UserControl { - const int MARGIN_LEFT = 60; // Space for Y-axis labels (KB/s) - const int MARGIN_RIGHT = 60; // Space for Y-axis labels (speed rating) - const int MARGIN_TOP = 20; // Top margin - const int MARGIN_BOTTOM = 30; // Space for X-axis labels + const int MARGIN_LEFT = 60; // Space for Y-axis labels (KB/s) + const int MARGIN_RIGHT = 60; // Space for Y-axis labels (speed rating) + const int MARGIN_TOP = 20; // Top margin + const int MARGIN_BOTTOM = 30; // Space for X-axis labels + const double MIN_ZOOM = 0.1; // 10% minimum zoom (zoomed out) + const double MAX_ZOOM = 50.0; // 5000% maximum zoom (zoomed in) increased from 10.0 + const double ZOOM_STEP = 0.2; // 20% zoom step per click/scroll public static readonly StyledProperty> SpeedDataProperty = AvaloniaProperty @@ -82,6 +86,8 @@ public partial class DiscSpeedGraph : UserControl int _consecutiveSpikeCount; // Counter for consecutive spike attenuation ObservableCollection<(ulong sector, double speedKbps)> _speedData; + double _yZoomLevel = 1.0; // 1.0 = 100% (no zoom), higher = zoomed in + public DiscSpeedGraph() { InitializeComponent(); @@ -95,6 +101,12 @@ public partial class DiscSpeedGraph : UserControl }; _canvas?.Children.Add(_speedLine); + + // Wire up scroll wheel for zoom + if(_canvas != null) _canvas.PointerWheelChanged += OnPointerWheelChanged; + + // Keyboard shortcuts: + to zoom in, - to zoom out + KeyDown += OnKeyDown; } public ObservableCollection<(ulong sector, double speedKbps)> SpeedData @@ -167,6 +179,57 @@ public partial class DiscSpeedGraph : UserControl } } + void ZoomIn() + { + double newZoom = Math.Min(_yZoomLevel + ZOOM_STEP, MAX_ZOOM); + + if(Math.Abs(newZoom - _yZoomLevel) > 0.001) + { + _yZoomLevel = newZoom; + RedrawAll(); + } + } + + void ZoomOut() + { + double newZoom = Math.Max(_yZoomLevel - ZOOM_STEP, MIN_ZOOM); + + if(Math.Abs(newZoom - _yZoomLevel) > 0.001) + { + _yZoomLevel = newZoom; + RedrawAll(); + } + } + + void OnPointerWheelChanged(object sender, PointerWheelEventArgs e) + { + // Scroll up = zoom in, scroll down = zoom out + if(e.Delta.Y > 0) + ZoomIn(); + else if(e.Delta.Y < 0) ZoomOut(); + + e.Handled = true; + } + + void OnKeyDown(object? sender, KeyEventArgs e) + { + switch(e.Key) + { + case Key.OemPlus: // Usually needs Shift + case Key.Add: // Numpad + + ZoomIn(); + e.Handled = true; + + break; + case Key.OemMinus: + case Key.Subtract: // Numpad - + ZoomOut(); + e.Handled = true; + + break; + } + } + (ulong sector, double speedKbps) ProcessNewDataPoint((ulong sector, double speedKbps) newPoint) { (ulong sector, double speed) = newPoint; @@ -333,7 +396,7 @@ public partial class DiscSpeedGraph : UserControl for(var i = 0; i <= numHorizontalLines; i++) { double y = MARGIN_TOP + graphHeight * (1 - (double)i / numHorizontalLines); - double speed = MaxSpeed * i / numHorizontalLines; + double speed = MaxSpeed / _yZoomLevel * i / numHorizontalLines; // Left side: KB/s var kbpsText = new TextBlock @@ -380,10 +443,12 @@ public partial class DiscSpeedGraph : UserControl _speedLine.Points.Clear(); + double effectiveMaxSpeed = MaxSpeed / _yZoomLevel; + foreach((ulong sector, double speedKbps) in _processedData) { double x = MARGIN_LEFT + graphWidth * sector / MaxSector; - double y = MARGIN_TOP + graphHeight * (1 - speedKbps / MaxSpeed); + double y = MARGIN_TOP + graphHeight * (1 - speedKbps / effectiveMaxSpeed); // Clamp Y to graph bounds y = Math.Max(MARGIN_TOP, Math.Min(MARGIN_TOP + graphHeight, y)); @@ -401,6 +466,8 @@ public partial class DiscSpeedGraph : UserControl if(graphWidth <= 0 || graphHeight <= 0) return; + double effectiveMaxSpeed = MaxSpeed / _yZoomLevel; + // Only add the new point(s) to the polyline int startIndex = _speedLine.Points.Count; @@ -408,7 +475,7 @@ public partial class DiscSpeedGraph : UserControl { (ulong sector, double speedKbps) = _processedData[i]; double x = MARGIN_LEFT + graphWidth * sector / MaxSector; - double y = MARGIN_TOP + graphHeight * (1 - speedKbps / MaxSpeed); + double y = MARGIN_TOP + graphHeight * (1 - speedKbps / effectiveMaxSpeed); // Clamp Y to graph bounds y = Math.Max(MARGIN_TOP, Math.Min(MARGIN_TOP + graphHeight, y));