Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions NINA.Test/View/FramingAssistantViewTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This Source Code Form is subject to the terms of the Mozilla Public

#endregion "copyright"

using CommunityToolkit.Mvvm.Input;
using FluentAssertions;
using NINA.Core.Enum;
using NINA.CustomControlLibrary;
Expand All @@ -23,6 +24,7 @@ This Source Code Form is subject to the terms of the Mozilla Public
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

Expand Down Expand Up @@ -212,6 +214,70 @@ public void ProjectionSelector_TracksStaticOfflineStaticTransitions() {
GC.KeepAlive(host);
}

[Test]
public void ZoomButtons_AdjustFieldOfViewWhenOfflineSkyMapIsLoaded() {
EnsureApplicationResources();
using FramingAssistantTimeContext timeContext = new FramingAssistantTimeContext(() => DateTime.Now, startTimer: false);
SourceContext context = new SourceContext {
FramingAssistantSource = SkySurveySource.SKYATLAS,
SkyMapAnnotator = new SkyMapAnnotatorContext { DynamicFoV = true },
TimeContext = timeContext
};
int zoomInCount = 0;
int zoomOutCount = 0;
context.ZoomInCommand = new RelayCommand(() => zoomInCount++, () => context.SkyMapAnnotator.DynamicFoV);
context.ZoomOutCommand = new RelayCommand(() => zoomOutCount++, () => context.SkyMapAnnotator.DynamicFoV);
FramingAssistantView view = new FramingAssistantView { DataContext = context };
Window host = new Window { Width = 1280, Height = 800, Content = view };
host.Measure(new Size(host.Width, host.Height));
host.Arrange(new Rect(0, 0, host.Width, host.Height));
host.UpdateLayout();
DrainDispatcher();
NINA.WPF.Base.View.ImageView imageView = FindDescendants<NINA.WPF.Base.View.ImageView>(view).Single();
Button zoomIn = FindImageViewButton(imageView, 0)!;
Button zoomOut = FindImageViewButton(imageView, 1)!;
TextBlock scale = (TextBlock)imageView.FindName("PART_TextblockScale");
string initialScale = scale.Text;

zoomIn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
zoomOut.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));

zoomInCount.Should().Be(1);
zoomOutCount.Should().Be(1);
scale.Text.Should().Be(initialScale);
GC.KeepAlive(host);
}

[Test]
public void ZoomButtons_RetainImageScalingWhenOfflineSkyMapIsNotLoaded() {
EnsureApplicationResources();
using FramingAssistantTimeContext timeContext = new FramingAssistantTimeContext(() => DateTime.Now, startTimer: false);
SourceContext context = new SourceContext {
FramingAssistantSource = SkySurveySource.NASA,
SkyMapAnnotator = new SkyMapAnnotatorContext { DynamicFoV = false },
TimeContext = timeContext
};
int commandCount = 0;
context.ZoomInCommand = new RelayCommand(() => commandCount++, () => context.SkyMapAnnotator.DynamicFoV);
context.ZoomOutCommand = new RelayCommand(() => commandCount++, () => context.SkyMapAnnotator.DynamicFoV);
FramingAssistantView view = new FramingAssistantView { DataContext = context };
Window host = new Window { Width = 1280, Height = 800, Content = view };
host.Measure(new Size(host.Width, host.Height));
host.Arrange(new Rect(0, 0, host.Width, host.Height));
host.UpdateLayout();
DrainDispatcher();
NINA.WPF.Base.View.ImageView imageView = FindDescendants<NINA.WPF.Base.View.ImageView>(view).Single();
Button zoomIn = FindImageViewButton(imageView, 0)!;
TextBlock scale = (TextBlock)imageView.FindName("PART_TextblockScale");
string initialScale = scale.Text;

zoomIn.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));

commandCount.Should().Be(0);
scale.Text.Should().NotBe(initialScale);
GC.KeepAlive(host);
}

[TestCase(10, 2, 0, 20, 12)]
[TestCase(10, 0, 0, 20, 8)]
[TestCase(20, 2, 0, 20, 20)]
Expand Down Expand Up @@ -277,6 +343,10 @@ private static System.Collections.Generic.IEnumerable<T> FindDescendants<T>(Depe
return null;
}

private static Button? FindImageViewButton(DependencyObject parent, int column) {
return FindDescendants<Button>(parent).FirstOrDefault(button => Grid.GetColumn(button) == column);
}

private static void ConstructInHost() {
Window host = new Window {
Width = 1280,
Expand Down Expand Up @@ -351,6 +421,8 @@ private sealed class SourceContext {
public SkySurveySource FramingAssistantSource { get; set; }
public SkyMapAnnotatorContext SkyMapAnnotator { get; set; } = new SkyMapAnnotatorContext();
public FramingAssistantTimeContext TimeContext { get; set; } = null!;
public ICommand ZoomInCommand { get; set; } = null!;
public ICommand ZoomOutCommand { get; set; } = null!;
}

private sealed class SkyMapAnnotatorContext : INotifyPropertyChanged {
Expand Down
34 changes: 34 additions & 0 deletions NINA.Test/ViewModel/FramingAssistantFieldOfViewTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#region "copyright"

/*
Copyright © 2016 - 2026 Stefan Berg <isbeorn86+NINA@googlemail.com> and the N.I.N.A. contributors

This file is part of N.I.N.A. - Nighttime Imaging 'N' Astronomy.

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

#endregion "copyright"

using FluentAssertions;
using NINA.ViewModel.FramingAssistant;

namespace NINA.Test.ViewModel {

[TestFixture]
public class FramingAssistantFieldOfViewTest {

[TestCase(1, 1, 1)]
[TestCase(1, -1, 1.5)]
[TestCase(200, 1, 180)]
[TestCase(200, -1, 200)]
public void AdjustFieldOfView_RespectsBothDirectionsAtMinimumAndMaximum(
double fieldOfView,
int delta,
double expected) {
FramingAssistantVM.AdjustFieldOfView(fieldOfView, delta).Should().Be(expected);
}
}
}
2 changes: 2 additions & 0 deletions NINA.WPF.Base/Interfaces/ViewModel/IFramingAssistantVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public interface IFramingAssistantVM {
SkySurveyImage ImageParameter { get; set; }
IAsyncCommand LoadImageCommand { get; }
ICommand MouseWheelCommand { get; }
ICommand ZoomInCommand { get; }
ICommand ZoomOutCommand { get; }
bool NegativeDec { get; set; }
double Opacity { get; set; }
double OverlapPercentage { get; set; }
Expand Down
35 changes: 34 additions & 1 deletion NINA.WPF.Base/View/ImageView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ public ICommand RightMouseButtonMoveCommand {
set => SetValue(RightMouseButtonMoveCommandProperty, value);
}

public static readonly DependencyProperty ZoomInCommandProperty =
DependencyProperty.Register(nameof(ZoomInCommand), typeof(ICommand), typeof(ImageView), new PropertyMetadata(null));

public ICommand ZoomInCommand {
get => (ICommand)GetValue(ZoomInCommandProperty);
set => SetValue(ZoomInCommandProperty, value);
}

public static readonly DependencyProperty ZoomOutCommandProperty =
DependencyProperty.Register(nameof(ZoomOutCommand), typeof(ICommand), typeof(ImageView), new PropertyMetadata(null));

public ICommand ZoomOutCommand {
get => (ICommand)GetValue(ZoomOutCommandProperty);
set => SetValue(ZoomOutCommandProperty, value);
}

public static readonly DependencyProperty ScrollEnabledProperty =
DependencyProperty.Register(nameof(ScrollEnabled), typeof(bool), typeof(ImageView), new PropertyMetadata(true));

Expand Down Expand Up @@ -292,6 +308,10 @@ private void OnsvScrollChanged(object sender, ScrollChangedEventArgs e) {
}

private void ButtonZoomIn_Click(object sender, RoutedEventArgs e) {
if (TryExecuteZoomCommand(ZoomInCommand)) {
return;
}

Zoom(PART_ScaleTransform.ScaleX + PART_ScaleTransform.ScaleX * 0.25);
var centerOfViewport = new Point(PART_ScrollViewer.ViewportWidth / 2,
PART_ScrollViewer.ViewportHeight / 2);
Expand All @@ -300,13 +320,26 @@ private void ButtonZoomIn_Click(object sender, RoutedEventArgs e) {
}

private void ButtonZoomOut_Click(object sender, RoutedEventArgs e) {
if (TryExecuteZoomCommand(ZoomOutCommand)) {
return;
}

Zoom(PART_ScaleTransform.ScaleX - PART_ScaleTransform.ScaleX * 0.25);
var centerOfViewport = new Point(PART_ScrollViewer.ViewportWidth / 2,
PART_ScrollViewer.ViewportHeight / 2);
lastCenterPositionOnTarget =
PART_ScrollViewer.TranslatePoint(centerOfViewport, PART_Canvas);
}

private static bool TryExecuteZoomCommand(ICommand command) {
if (command?.CanExecute(null) != true) {
return false;
}

command.Execute(null);
return true;
}

private void ButtonZoomReset_Click(object sender, RoutedEventArgs e) {
RecalculateScalingFactors();
Zoom(fittingScale * 0.9);
Expand All @@ -328,4 +361,4 @@ private void PART_Canvas_SizeChanged(object sender, SizeChangedEventArgs e) {
}
}
}
}
}
4 changes: 3 additions & 1 deletion NINA/View/FramingAssistantView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,9 @@
ImageRotation="{Binding InverseRectangleRotation}"
RectangleFontSize="{Binding FontSize}"
RectangleOpacity="{Binding Opacity}"
ScrollEnabled="{Binding SkyMapAnnotator.DynamicFoV, Converter={StaticResource InverseBooleanConverter}}">
ScrollEnabled="{Binding SkyMapAnnotator.DynamicFoV, Converter={StaticResource InverseBooleanConverter}}"
ZoomInCommand="{Binding ZoomInCommand}"
ZoomOutCommand="{Binding ZoomOutCommand}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SizeChanged" SourceObject="{Binding RelativeSource={RelativeSource AncestorType=wpfbase:ImageView, Mode=FindAncestor}, Path=PART_ScrollViewerBinding}">
<i:InvokeCommandAction Command="{Binding ScrollViewerSizeChangedCommand}" />
Expand Down
39 changes: 25 additions & 14 deletions NINA/ViewModel/FramingAssistant/FramingAssistantVM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ private void InitializeCommands() {
DeleteCacheEntryCommand = new RelayCommand(DeleteCacheEntry, (object o) => Cache != null);
ResetObservationTimeCommand = new RelayCommand((object o) => TimeContext.ResetToCurrentTime());
MouseWheelCommand = new RelayCommand(MouseWheel);
ZoomInCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(
() => MouseWheel(new MouseWheelResult { Delta = 1 }),
() => SkyMapAnnotator.DynamicFoV);
ZoomOutCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(
() => MouseWheel(new MouseWheelResult { Delta = -1 }),
() => SkyMapAnnotator.DynamicFoV);
GetRotationFromCameraCommand = new AsyncCommand<bool>(GetRotationFromCamera, (object o) => RectangleCalculated && cameraMediator.GetInfo().Connected && cameraMediator.IsFreeToCapture(this));
CancelGetRotationFromCameraCommand = new RelayCommand(o => { try { getRotationTokenSource?.Cancel(); } catch { } });

Expand Down Expand Up @@ -461,33 +467,36 @@ private void InitializeCache() {

private void MouseWheel(object obj) {
var delta = ((MouseWheelResult)obj).Delta;
var adjustedFieldOfView = AdjustFieldOfView(FieldOfView, delta);
if (adjustedFieldOfView != FieldOfView) {
FieldOfView = adjustedFieldOfView;
}

CalculateRectangle(SkyMapAnnotator.ChangeFoV(FieldOfView), updatePlacements: false);
SkyMapAnnotator.UpdateSkyMap();
}

internal static double AdjustFieldOfView(double fieldOfView, int delta) {
double stepSize;
if (FieldOfView < 2) {
if (fieldOfView < 2) {
stepSize = 0.5;
} else if (FieldOfView < 10) {
} else if (fieldOfView < 10) {
stepSize = 1;
} else if (FieldOfView < 30) {
} else if (fieldOfView < 30) {
stepSize = 2;
} else if (FieldOfView < 50) {
} else if (fieldOfView < 50) {
stepSize = 5;
} else if (FieldOfView < 100) {
} else if (fieldOfView < 100) {
stepSize = 10;
} else {
stepSize = 20;
}

if (delta > 0) {
if (FieldOfView > 1) {
FieldOfView = Math.Max(1, FieldOfView - stepSize);
}
} else {
if (FieldOfView < 200) {
FieldOfView = Math.Min(200, FieldOfView + stepSize);
}
return fieldOfView > 1 ? Math.Max(1, fieldOfView - stepSize) : fieldOfView;
}
CalculateRectangle(SkyMapAnnotator.ChangeFoV(FieldOfView), updatePlacements: false);
SkyMapAnnotator.UpdateSkyMap();

return fieldOfView < 200 ? Math.Min(200, fieldOfView + stepSize) : fieldOfView;
}

private async void ResizeTimer_Tick(object sender, EventArgs e) {
Expand Down Expand Up @@ -1724,6 +1733,8 @@ public void Dispose() {
public ICommand ScrollViewerSizeChangedCommand { get; private set; }
public ICommand ResetObservationTimeCommand { get; private set; }
public ICommand MouseWheelCommand { get; private set; }
public ICommand ZoomInCommand { get; private set; }
public ICommand ZoomOutCommand { get; private set; }
public IAsyncCommand GetRotationFromCameraCommand { get; private set; }
public ICommand CancelGetRotationFromCameraCommand { get; private set; }

Expand Down
3 changes: 2 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ This allows you to safely return to a stable release if needed.
- The grid and viewport projection can be switched between Equatorial and Alt/Az coordinates, with clear compass direction indicators along the zero-altitude line that remain visible over the horizon overlay, planetarium handedness and correct image and camera orientation.
- An optional local or custom horizon hides annotations and cached imagery below it, including in wide-field views.
- Offline-only date and time steppers provide a current or fixed observation context for the map and horizon, while the selected date drives the altitude chart.
- The zoom buttons now adjust the offline map's field of view, matching mouse-wheel navigation, while other image sources retain image scaling.
- **Autofocus & Star Measurements**
- The native star detector now measures HFR from a centroid-refined curve of growth instead of using a first-moment approximation
- Local star background estimation now uses a robust sigma-clipped median to reduce bias from nearby stars and outliers
Expand Down Expand Up @@ -1735,4 +1736,4 @@ ___
- Lots and lots of minor bugfixes and improvements
- Launch NINA with a specific ProfileId via cmdline
- Integrated with Windows JumpList feature to launch instance with specific profile loaded
- Enhanced Altitude Check to permit imaging of targets that are below the configured altitude when they are rising in the sky and display a visual cue to distinguish rising versus setting targets
- Enhanced Altitude Check to permit imaging of targets that are below the configured altitude when they are rising in the sky and display a visual cue to distinguish rising versus setting targets
Loading