diff --git a/source/_magnifier/commands.py b/source/_magnifier/commands.py index 95821d24df9..f3939475934 100644 --- a/source/_magnifier/commands.py +++ b/source/_magnifier/commands.py @@ -153,9 +153,7 @@ def zoom(direction: Direction) -> None: return magnifier._zoom(direction) ui.message( - ZoomLevel.ZOOM_MESSAGE.format( - zoomLevel=f"{magnifier.zoomLevel:.1f}", - ), + ZoomLevel.zoomMessage(magnifier.zoomLevel), ) diff --git a/source/_magnifier/config.py b/source/_magnifier/config.py index e4cbecbc314..f2e2e3271b8 100644 --- a/source/_magnifier/config.py +++ b/source/_magnifier/config.py @@ -36,45 +36,27 @@ class ZoomLevel: Constants and utilities for zoom level management. """ - MAX_ZOOM: float = 10.0 - MIN_ZOOM: float = 1.0 - STEP_FACTOR: float = 0.5 - ZOOM_MESSAGE = pgettext( - "magnifier", - # Translators: Message announced when zooming in with {zoomLevel} being the target zoom level. - "{zoomLevel}x", - ) - - @classmethod - def zoom_range(cls) -> list[float]: - """ - Return the list of available zoom levels. - """ - start = round(cls.MIN_ZOOM / cls.STEP_FACTOR) - end = round(cls.MAX_ZOOM / cls.STEP_FACTOR) - - return [i * cls.STEP_FACTOR for i in range(start, end + 1)] - - @classmethod - def zoom_strings(cls) -> list[str]: - """ - Return localized zoom level strings. - """ - return [ - cls.ZOOM_MESSAGE.format( - zoomLevel=f"{value:.1f}", - ) - for value in cls.zoom_range() - ] - - -def getZoomLevel() -> float: + MAX_ZOOM: int = 5000 + MIN_ZOOM: int = 100 + STEP_FACTOR: int = 50 + + @staticmethod + def zoomMessage(zoomLevel: int) -> str: + zoomLevel = zoomLevel / 100.0 + return pgettext( + "magnifier", + # Translators: Message announced when zooming in with {zoomLevel} being the target zoom level. + "{zoomLevel}x", + ).format(zoomLevel=f"{zoomLevel:.1f}") + + +def getZoomLevel() -> int: """ Get zoom level from config. - :return: The zoom level. + :return: The zoom level (percentage). """ - zoomLevel = config.conf["magnifier"]["zoomLevel"] + zoomLevel = config.conf["magnifier"]["zoom"] return zoomLevel @@ -85,22 +67,22 @@ def getZoomLevelString() -> str: :return: Formatted zoom level string. """ zoomLevel = getZoomLevel() - zoomValues = ZoomLevel.zoom_range() - zoomStrings = ZoomLevel.zoom_strings() - closestIndex = min( - range(len(zoomValues)), - key=lambda i: abs(zoomValues[i] - zoomLevel), - ) - return zoomStrings[closestIndex] + return ZoomLevel.zoomMessage(zoomLevel) -def setZoomLevel(zoomLevel: float) -> None: +def setZoomLevel(zoomLevel: int) -> None: """ Set zoom level from settings. :param zoomLevel: The zoom level to set. """ - config.conf["magnifier"]["zoomLevel"] = zoomLevel + if not isinstance(zoomLevel, int): + raise ValueError("Zoom level must be an integer percentage") + if not (ZoomLevel.MIN_ZOOM <= zoomLevel <= ZoomLevel.MAX_ZOOM): + raise ValueError(f"Zoom level must be between {ZoomLevel.MIN_ZOOM} and {ZoomLevel.MAX_ZOOM}") + if zoomLevel % ZoomLevel.STEP_FACTOR != 0: + raise ValueError(f"Zoom level must be a multiple of {ZoomLevel.STEP_FACTOR}") + config.conf["magnifier"]["zoom"] = zoomLevel def getPanStep() -> int: diff --git a/source/_magnifier/fullscreenMagnifier.py b/source/_magnifier/fullscreenMagnifier.py index 7b77fa61808..94caad051cc 100644 --- a/source/_magnifier/fullscreenMagnifier.py +++ b/source/_magnifier/fullscreenMagnifier.py @@ -230,7 +230,7 @@ def _fullscreenMagnifier(self, coordinates: Coordinates) -> None: """ params = self._getMagnifierParameters(coordinates) magnification.MagSetFullscreenTransform( - self.zoomLevel, + self.zoomLevelRatio, params.coordinates.x, params.coordinates.y, ) @@ -340,7 +340,7 @@ def _relativePos( :return: The (x, y) coordinates of the magnifier center """ - zoom = self.zoomLevel + zoom = self.zoomLevelRatio mouseX, mouseY = coordinates magnifierWidth = self._displayOrientation.width / zoom magnifierHeight = self._displayOrientation.height / zoom @@ -388,8 +388,8 @@ def _getMagnifierParameters(self, coordinates: Coordinates) -> MagnifierParamete """ x, y = coordinates # Calculate the size of the capture area at the current zoom level - magnifierWidth = self._displayOrientation.width / self.zoomLevel - magnifierHeight = self._displayOrientation.height / self.zoomLevel + magnifierWidth = self._displayOrientation.width / self.zoomLevelRatio + magnifierHeight = self._displayOrientation.height / self.zoomLevelRatio # Compute the top-left corner so that (x, y) is at the center left = int(x - (magnifierWidth / 2)) diff --git a/source/_magnifier/magnifier.py b/source/_magnifier/magnifier.py index afe0548d300..a44e024e3af 100644 --- a/source/_magnifier/magnifier.py +++ b/source/_magnifier/magnifier.py @@ -69,25 +69,31 @@ def filterType(self) -> Filter: def filterType(self, value: Filter) -> None: self._filterType = value + @property + def zoomLevelRatio(self) -> float: + """Get the zoom level as a float (e.g., 2.0 for 200% zoom)""" + return self._zoomLevel / 100.0 + @property def zoomLevel(self) -> float: + """Get the zoom level as a percentage (e.g., 200 for 200% zoom)""" return self._zoomLevel @zoomLevel.setter - def zoomLevel(self, value: float) -> None: + def zoomLevel(self, value: int) -> None: """ Set zoom level, ensuring it's a valid value in the zoom range. :param value: The zoom level to set :raises ValueError: If the value is not in the valid zoom range """ - validZoomValues = ZoomLevel.zoom_range() - if value not in validZoomValues: - # Find the closest valid zoom value - closestZoom = min(validZoomValues, key=lambda x: abs(x - value)) - log.warning(f"Invalid zoom level {value}, using closest valid value {closestZoom}") - value = closestZoom - self._zoomLevel = value + if not isinstance(value, int): + raise ValueError("Zoom level must be an integer percentage") + if not (ZoomLevel.MIN_ZOOM <= value <= ZoomLevel.MAX_ZOOM): + raise ValueError(f"Zoom level must be between {ZoomLevel.MIN_ZOOM} and {ZoomLevel.MAX_ZOOM}") + if value % ZoomLevel.STEP_FACTOR != 0: + raise ValueError(f"Zoom level must be a multiple of {ZoomLevel.STEP_FACTOR}") + self._zoomLevel = float(value) @property def currentCoordinates(self) -> Coordinates: @@ -119,8 +125,8 @@ def _getScreenLimits(self) -> tuple[int, int, int, int]: return (0, 0, self._displayOrientation.width, self._displayOrientation.height) else: # In normal mode: calculate limits to keep view within screen - visibleWidth = self._displayOrientation.width / self.zoomLevel - visibleHeight = self._displayOrientation.height / self.zoomLevel + visibleWidth = self._displayOrientation.width / self.zoomLevelRatio + visibleHeight = self._displayOrientation.height / self.zoomLevelRatio minX = int(visibleWidth / 2) minY = int(visibleHeight / 2) maxX = int(self._displayOrientation.width - (visibleWidth / 2)) @@ -148,6 +154,7 @@ def _setZoomRawValue(self, value: float) -> None: :param value: The zoom level to set (can be any intermediate value) """ + value = max(ZoomLevel.MIN_ZOOM, min(value, ZoomLevel.MAX_ZOOM)) self._zoomLevel = value def _onDisplayChanged(self, orientationState: OrientationState) -> None: @@ -293,11 +300,11 @@ def _zoom(self, direction: Direction) -> None: :param direction: Direction.IN to zoom in, Direction.OUT to zoom out """ if direction == Direction.IN: - newZoom = self.zoomLevel + ZoomLevel.STEP_FACTOR + newZoom = int(self.zoomLevel + ZoomLevel.STEP_FACTOR) if newZoom <= ZoomLevel.MAX_ZOOM: self.zoomLevel = newZoom elif direction == Direction.OUT: - newZoom = self.zoomLevel - ZoomLevel.STEP_FACTOR + newZoom = int(self.zoomLevel - ZoomLevel.STEP_FACTOR) if newZoom >= ZoomLevel.MIN_ZOOM: self.zoomLevel = newZoom @@ -313,7 +320,7 @@ def _pan(self, action: MagnifierAction) -> bool: minX, minY, maxX, maxY = self._getScreenLimits() - panPixels = int((self._displayOrientation.width / self.zoomLevel) * self._panStep / 100) + panPixels = int((self._displayOrientation.width / self.zoomLevelRatio) * self._panStep / 100) match action: case MagnifierAction.PAN_LEFT: diff --git a/source/_magnifier/utils/spotlightManager.py b/source/_magnifier/utils/spotlightManager.py index a7b6112b601..6d62467a4dd 100644 --- a/source/_magnifier/utils/spotlightManager.py +++ b/source/_magnifier/utils/spotlightManager.py @@ -30,7 +30,7 @@ def __init__( self._animationSteps: int = 40 self._animationStepDelay: int = 12 self._currentCoordinates: Coordinates = fullscreenMagnifier._focusManager.getCurrentFocusCoordinates() - self._originalZoomLevel: float = 0.0 + self._originalZoomLevel: int = 0 self._currentZoomLevel: float = 0.0 self._originalMode: FullScreenMode | None = None @@ -92,8 +92,8 @@ def _animateZoom( ) self._animationStepsList = self._computeAnimationSteps( - self._currentZoomLevel, - target.zoomLevel, + round(self._currentZoomLevel), + round(target.zoomLevel), self._currentCoordinates, target.coordinates, ) @@ -175,8 +175,8 @@ def zoomBack(self) -> None: def _computeAnimationSteps( self, - zoomStart: float, - zoomEnd: float, + zoomStart: int, + zoomEnd: int, coordinateStart: Coordinates, coordinateEnd: Coordinates, ) -> list[ZoomHistory]: @@ -196,7 +196,7 @@ def _computeAnimationSteps( startX, startY = coordinateStart endX, endY = coordinateEnd - animationSteps = [] + animationSteps: list[ZoomHistory] = [] zoomDelta = (zoomEnd - zoomStart) / self._animationSteps coordDeltaX = (endX - startX) / self._animationSteps diff --git a/source/config/configSpec.py b/source/config/configSpec.py index b0451e2ab02..0f7b9f37fcb 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -118,7 +118,7 @@ [magnifier] enabled = boolean(default=false) magnifiedView = string(default="fullscreen") - zoomLevel = float(min=1.0, max=10.0, default=2.0) + zoom = integer(min=100, max=5000, default=200) isTrueCentered = boolean(default=False) filter = string(default="normal") followMouse = boolean(default=True) diff --git a/source/config/profileUpgradeSteps.py b/source/config/profileUpgradeSteps.py index c9cde845726..b822b54c649 100644 --- a/source/config/profileUpgradeSteps.py +++ b/source/config/profileUpgradeSteps.py @@ -1,5 +1,5 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2016-2025 NV Access Limited, Bill Dengler, Cyrille Bougot, Łukasz Golonka, Leonard de Ruijter, Cary-rowen +# Copyright (C) 2016-2026 NV Access Limited, Bill Dengler, Cyrille Bougot, Łukasz Golonka, Leonard de Ruijter, Cary-rowen # This file is covered by the GNU General Public License. # See the file COPYING for more details. diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 594155ca6f0..96458ea386f 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -10,7 +10,6 @@ # This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt -import bisect import copy import logging import math @@ -6064,32 +6063,23 @@ def makeSettings( # ZOOM SETTINGS # Translators: The label for a setting in magnifier settings to select the zoom level. - zoomLabelText = _("&Zoom level:") + zoomLabelText = _("&Zoom (%):") - zoomValues = magnifierConfig.ZoomLevel.zoom_range() - zoomChoices = magnifierConfig.ZoomLevel.zoom_strings() - - self.zoomList = sHelper.addLabeledControl( + self.zoomCtrl = sHelper.addLabeledControl( zoomLabelText, - wx.Choice, - choices=zoomChoices, + wx.SpinCtrl, + min=magnifierConfig.ZoomLevel.MIN_ZOOM, + max=magnifierConfig.ZoomLevel.MAX_ZOOM, ) + self.zoomCtrl.SetIncrement(magnifierConfig.ZoomLevel.STEP_FACTOR) self.bindHelpEvent( "MagnifierZoom", - self.zoomList, + self.zoomCtrl, ) # Set value from config zoomLevel = magnifierConfig.getZoomLevel() - zoomIndex = bisect.bisect_left(zoomValues, zoomLevel) - # Find the closest value - if zoomIndex == 0: - closestIndex = 0 - elif zoomIndex >= len(zoomValues): - closestIndex = len(zoomValues) - 1 - else: - closestIndex = min(zoomIndex - 1, zoomIndex, key=lambda i: abs(zoomValues[i] - zoomLevel)) - self.zoomList.SetSelection(closestIndex) + self.zoomCtrl.SetValue(zoomLevel) # PAN SETTINGS # Translators: The label for a setting in magnifier settings to select the pan step size (in percentage). @@ -6102,7 +6092,7 @@ def makeSettings( max=100, ) self.bindHelpEvent( - "magnifierPanStep", + "MagnifierPanningStepSize", self.panSpinCtrl, ) @@ -6195,8 +6185,8 @@ def onSave(self): """Save the current selections to config.""" magnifierConfig.setEnabled(self.enableMagnifierCheckBox.GetValue()) - selectedZoom = self.zoomList.GetSelection() - magnifierConfig.setZoomLevel(magnifierConfig.ZoomLevel.zoom_range()[selectedZoom]) + selectedZoom = self.zoomCtrl.GetValue() + magnifierConfig.setZoomLevel(selectedZoom) magnifierConfig.setPanStep(self.panSpinCtrl.GetValue()) diff --git a/tests/unit/test_magnifier/test_fullscreenMagnifier.py b/tests/unit/test_magnifier/test_fullscreenMagnifier.py index 3c918ee26a7..493344e7287 100644 --- a/tests/unit/test_magnifier/test_fullscreenMagnifier.py +++ b/tests/unit/test_magnifier/test_fullscreenMagnifier.py @@ -1,13 +1,14 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2025 NV Access Limited, Antoine Haffreingue +# Copyright (C) 2025-2026 NV Access Limited, Antoine Haffreingue # This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt from unittest.mock import MagicMock, patch +from _magnifier.config import ZoomLevel +from _magnifier.magnifier import Magnifier from _magnifier.utils.types import Filter, FullScreenMode, MagnifiedView, Direction, Coordinates from _magnifier.fullscreenMagnifier import FullScreenMagnifier from tests.unit.test_magnifier.test_magnifier import _TestMagnifier -from _magnifier.magnifier import Magnifier from winAPI._displayTracking import getPrimaryDisplayOrientation @@ -19,7 +20,7 @@ def testMagnifierCreation(self): magnifier = FullScreenMagnifier() magnifier._startMagnifier() - self.assertEqual(magnifier.zoomLevel, 2.0) + self.assertEqual(magnifier.zoomLevel, 200) self.assertEqual(magnifier.filterType, Filter.NORMAL) self.assertEqual(magnifier._fullscreenMode, FullScreenMode.CENTER) self.assertEqual(magnifier._MAGNIFIED_VIEW, MagnifiedView.FULLSCREEN) @@ -32,17 +33,17 @@ def testMagnifierZoom(self): magnifier = FullScreenMagnifier() magnifier._startMagnifier() - # Set initial zoom to 1.0 for predictable testing - magnifier.zoomLevel = 1.0 + # Set initial zoom to 100 for predictable testing + magnifier.zoomLevel = 100 # Test zoom in magnifier._zoom(Direction.IN) - self.assertEqual(magnifier.zoomLevel, 1.5) + self.assertEqual(magnifier.zoomLevel, 150) # Test zoom out magnifier._zoom(Direction.OUT) - self.assertEqual(magnifier.zoomLevel, 1.0) - self.assertEqual(magnifier.zoomLevel, 1.0) + self.assertEqual(magnifier.zoomLevel, 100) + self.assertEqual(magnifier.zoomLevel, 100) # Cleanup magnifier._stopMagnifier() @@ -132,16 +133,16 @@ def testMagnifierZoomBoundaries(self): """Test zoom boundaries.""" magnifier = FullScreenMagnifier() magnifier._startMagnifier() - magnifier.zoomLevel = 1.0 + magnifier.zoomLevel = ZoomLevel.MIN_ZOOM # Test minimum boundary magnifier._zoom(Direction.OUT) # Try to zoom out below minimum - self.assertEqual(magnifier.zoomLevel, 1.0) + self.assertEqual(magnifier.zoomLevel, ZoomLevel.MIN_ZOOM) # Test maximum boundary - magnifier.zoomLevel = 10.0 + magnifier.zoomLevel = ZoomLevel.MAX_ZOOM magnifier._zoom(Direction.IN) # Try to zoom in above maximum - self.assertEqual(magnifier.zoomLevel, 10.0) + self.assertEqual(magnifier.zoomLevel, ZoomLevel.MAX_ZOOM) # Cleanup magnifier._stopMagnifier() @@ -202,11 +203,11 @@ def testMagnifierSimpleLifecycle(self): magnifier = FullScreenMagnifier() magnifier._startMagnifier() self.assertTrue(magnifier._isActive) - self.assertEqual(magnifier.zoomLevel, 2.0) + self.assertEqual(magnifier.zoomLevel, 200) # Zoom a bit magnifier._zoom(Direction.IN) - self.assertEqual(magnifier.zoomLevel, 2.5) + self.assertEqual(magnifier.zoomLevel, 250) # Set some coordinates magnifier._currentCoordinates = (200, 300) @@ -239,7 +240,7 @@ def testAttemptRecoverySuccess(self): mock_mag.MagUninitialize.assert_called_once() mock_mag.MagInitialize.assert_called_once() - mock_mag.MagSetFullscreenTransform.assert_called_once_with(magnifier.zoomLevel, 0, 0) + mock_mag.MagSetFullscreenTransform.assert_called_once_with(magnifier.zoomLevel / 100.0, 0, 0) mock_mag.MagSetFullscreenColorEffect.assert_called_once() self.assertEqual(magnifier._consecutiveErrors, 0) magnifier._startTimer.assert_called_once_with(magnifier._updateMagnifier) diff --git a/tests/unit/test_magnifier/test_magnifier.py b/tests/unit/test_magnifier/test_magnifier.py index 9f69891671a..8fa56d1cba8 100644 --- a/tests/unit/test_magnifier/test_magnifier.py +++ b/tests/unit/test_magnifier/test_magnifier.py @@ -1,8 +1,9 @@ # A part of NonVisual Desktop Access (NVDA) -# Copyright (C) 2025 NV Access Limited, Antoine Haffreingue +# Copyright (C) 2025-2026 NV Access Limited, Antoine Haffreingue # This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt +from _magnifier.config import ZoomLevel from _magnifier.magnifier import Magnifier from _magnifier.utils.types import Filter, Direction, Coordinates, MagnifierAction from comtypes import COMError @@ -63,7 +64,7 @@ def tearDown(self): def testMagnifierCreation(self): """Can we create a magnifier with valid parameters?""" - self.assertEqual(self.magnifier.zoomLevel, 2.0) + self.assertEqual(self.magnifier.zoomLevel, 200) self.assertEqual(self.magnifier._filterType, Filter.NORMAL) self.assertFalse(self.magnifier._isActive) self.assertIsNotNone(self.magnifier._focusManager) @@ -72,25 +73,25 @@ def testMagnifierCreation(self): def testZoomLevelProperty(self): """ZoomLevel property.""" # Test valid directions - self.magnifier.zoomLevel = 5.0 - self.assertEqual(self.magnifier.zoomLevel, 5.0) + self.magnifier.zoomLevel = 500 + self.assertEqual(self.magnifier.zoomLevel, 500) - self.magnifier.zoomLevel = 1.0 + self.magnifier.zoomLevel = 100 self.magnifier._zoom(Direction.IN) - self.assertEqual(self.magnifier.zoomLevel, 1.5) + self.assertEqual(self.magnifier.zoomLevel, 150) - self.magnifier.zoomLevel = 10.0 + self.magnifier.zoomLevel = 1000 self.magnifier._zoom(Direction.OUT) - self.assertEqual(self.magnifier.zoomLevel, 9.5) + self.assertEqual(self.magnifier.zoomLevel, 950) # Test limits - self.magnifier.zoomLevel = 1.0 + self.magnifier.zoomLevel = ZoomLevel.MIN_ZOOM self.magnifier._zoom(Direction.OUT) # Should stay at min - self.assertEqual(self.magnifier.zoomLevel, 1.0) + self.assertEqual(self.magnifier.zoomLevel, ZoomLevel.MIN_ZOOM) - self.magnifier.zoomLevel = 10.0 + self.magnifier.zoomLevel = ZoomLevel.MAX_ZOOM self.magnifier._zoom(Direction.IN) # Should stay at max - self.assertEqual(self.magnifier.zoomLevel, 10.0) + self.assertEqual(self.magnifier.zoomLevel, ZoomLevel.MAX_ZOOM) def testStartMagnifier(self): """Activating the magnifier.""" @@ -269,25 +270,25 @@ def testStopMagnifier(self): def testZoom(self): """zoom in and out with valid values and check boundaries.""" # Set initial zoom to 1.0 for predictable testing - self.magnifier.zoomLevel = 1.0 + self.magnifier.zoomLevel = 100 # Test zoom in self.magnifier._zoom(Direction.IN) - self.assertEqual(self.magnifier.zoomLevel, 1.5) + self.assertEqual(self.magnifier.zoomLevel, 150) # Test zoom out self.magnifier._zoom(Direction.OUT) - self.assertEqual(self.magnifier.zoomLevel, 1.0) + self.assertEqual(self.magnifier.zoomLevel, 100) # Test zoom in at maximum boundary - self.magnifier.zoomLevel = 10.0 + self.magnifier.zoomLevel = ZoomLevel.MAX_ZOOM self.magnifier._zoom(Direction.IN) - self.assertEqual(self.magnifier.zoomLevel, 10.0) # Should remain at max + self.assertEqual(self.magnifier.zoomLevel, ZoomLevel.MAX_ZOOM) # Should remain at max # Test zoom out at minimum boundary - self.magnifier.zoomLevel = 1.0 + self.magnifier.zoomLevel = ZoomLevel.MIN_ZOOM self.magnifier._zoom(Direction.OUT) - self.assertEqual(self.magnifier.zoomLevel, 1.0) # Should remain at min + self.assertEqual(self.magnifier.zoomLevel, ZoomLevel.MIN_ZOOM) # Should remain at min def _setupPanTest(self): """Common setup for pan tests.""" @@ -298,7 +299,7 @@ def _setupPanTest(self): centerY = self.screenHeight // 2 self.magnifier.currentCoordinates = Coordinates(centerX, centerY) expectedPanPixels = int( - (self.screenWidth / self.magnifier.zoomLevel) * 10 / 100, + (self.screenWidth / self.magnifier.zoomLevelRatio) * 10 / 100, ) return centerX, centerY, expectedPanPixels @@ -510,7 +511,7 @@ def testStopTimer(self): def testClampCoordinates(self): """Test all boundary clamps (left, right, top, bottom) for both modes.""" for isTrueCentered in (False, True): - self.magnifier.zoomLevel = 2.0 + self.magnifier.zoomLevel = 200 with patch("_magnifier.magnifier.isTrueCentered", return_value=isTrueCentered): minX, minY, maxX, maxY = self.magnifier._getScreenLimits() @@ -536,7 +537,7 @@ def testClampCoordinates(self): def testClampCoordinatesWithinBounds(self): """Coordinates within bounds are not modified.""" - self.magnifier.zoomLevel = 2.0 + self.magnifier.zoomLevel = 200 with patch("_magnifier.magnifier.isTrueCentered", return_value=False): minX, minY, maxX, maxY = self.magnifier._getScreenLimits() centerX = (minX + maxX) // 2 diff --git a/tests/unit/test_magnifier/test_magnifierCommands.py b/tests/unit/test_magnifier/test_magnifierCommands.py index ddf41f318c4..ab3681b2382 100644 --- a/tests/unit/test_magnifier/test_magnifierCommands.py +++ b/tests/unit/test_magnifier/test_magnifierCommands.py @@ -23,7 +23,7 @@ def tearDown(self): def _makeMockMagnifier(self, isActive: bool): magnifier = MagicMock() magnifier.configure_mock(**{"_isActive": isActive}) - magnifier.zoomLevel = 2.0 + magnifier.zoomLevel = 200 return magnifier def testInactiveZoomIn(self): diff --git a/tests/unit/test_magnifier/test_spotlightManager.py b/tests/unit/test_magnifier/test_spotlightManager.py index ba88fa1fcd1..e62fe60f205 100644 --- a/tests/unit/test_magnifier/test_spotlightManager.py +++ b/tests/unit/test_magnifier/test_spotlightManager.py @@ -20,7 +20,7 @@ def testSpotlightManagerCreation(self): self.assertIsNotNone(spotlightManager) self.assertFalse(spotlightManager._spotlightIsActive) self.assertEqual(spotlightManager._animationSteps, 40) - self.assertEqual(spotlightManager._originalZoomLevel, 0.0) + self.assertEqual(spotlightManager._originalZoomLevel, 0) self.assertEqual(spotlightManager._currentZoomLevel, 0.0) magnifier._stopMagnifier() @@ -74,8 +74,8 @@ def testComputeAnimationSteps(self): # Test animation from zoom 2.0 to 1.0, coordinates (500, 400) to (960, 540) steps = spotlightManager._computeAnimationSteps( - 2.0, - 1.0, + 200, + 100, (500, 400), (960, 540), ) @@ -85,21 +85,21 @@ def testComputeAnimationSteps(self): # First step should be closer to start firstZoom, firstCoords = steps[0] - self.assertLess(abs(firstZoom - 2.0), abs(firstZoom - 1.0)) + self.assertLess(abs(firstZoom - 200), abs(firstZoom - 100)) # Last step should be at target lastZoom, lastCoords = steps[-1] - self.assertEqual(lastZoom, 1.0) + self.assertEqual(lastZoom, 100) self.assertEqual(lastCoords, (960, 540)) - # Steps should progress linearly (decreasing from 2.0 to 1.0) + # Steps should progress linearly (decreasing from 200 to 100) for i in range(len(steps) - 1): currentZoom, _ = steps[i] nextZoom, _ = steps[i + 1] self.assertGreater( currentZoom, nextZoom, - ) # Zoom should decrease from 2.0 to 1.0 + ) # Zoom should decrease from 200 to 100 magnifier._stopMagnifier() @@ -116,7 +116,7 @@ def testMouseMonitoring(self): spotlightManager._startMouseMonitoring() # Verify initial state - self.assertEqual(spotlightManager._lastMousePosition, (100, 200)) + self.assertEqual(spotlightManager._lastMousePosition, Coordinates(100, 200)) self.assertIsNotNone(spotlightManager._timer) magnifier._stopMagnifier() @@ -127,7 +127,7 @@ def testMouseIdleDetection(self): spotlightManager = magnifier._spotlightManager # Set initial position - spotlightManager._lastMousePosition = (100, 200) + spotlightManager._lastMousePosition = Coordinates(100, 200) # Mock wx.GetMousePosition to return same position (idle) with patch("wx.GetMousePosition") as mockGetMousePosition: @@ -148,7 +148,7 @@ def testMouseMovementDetection(self): spotlightManager = magnifier._spotlightManager # Set initial position - spotlightManager._lastMousePosition = (100, 200) + spotlightManager._lastMousePosition = Coordinates(100, 200) # Mock wx.GetMousePosition to return different position (moved) with patch("wx.GetMousePosition") as mockGetMousePosition: diff --git a/user_docs/en/userGuide.md b/user_docs/en/userGuide.md index d89a05884cf..9d645530664 100644 --- a/user_docs/en/userGuide.md +++ b/user_docs/en/userGuide.md @@ -2890,16 +2890,16 @@ The selected state is also saved for future NVDA sessions, so if you enable Magn ##### Zoom level {#MagnifierZoom} -This slider allows you to set the zoom level when using the magnifier. -The zoom level can range from 1.0 (no magnification) to 10.0 (maximum magnification). -The default value is 2.0 (200% zoom). +This setting allows you to set the zoom level when using the magnifier. +The zoom level can range from 100% (no magnification) to 5000% (maximum magnification). +The default value is 200%. You can always adjust the zoom level on the fly using the zoom in (`NVDA+shift+equals`) and zoom out (`NVDA+shift+minus`) commands while the magnifier is active. | . {.hideHeaderRow} |.| |---|---| -| Options | 1.0 to 10.0 | -| Default | 2.0 | +| Options | 100% to 5000% | +| Default | 200% | ##### Filter {#MagnifierFilter}