diff --git a/ILSpy.Tests/Editor/EditorZoomTests.cs b/ILSpy.Tests/Editor/EditorZoomTests.cs
index 04f7c6426..83c3fc6c2 100644
--- a/ILSpy.Tests/Editor/EditorZoomTests.cs
+++ b/ILSpy.Tests/Editor/EditorZoomTests.cs
@@ -16,8 +16,11 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using Avalonia.Headless.NUnit;
+
using AwesomeAssertions;
+using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpy.TextView;
using NUnit.Framework;
@@ -30,53 +33,87 @@ public class EditorZoomTests
[Test]
public void ZoomIn_Multiplies_By_Factor()
{
- EditorZoom.ZoomIn(10.0).Should().BeApproximately(11.0, 0.001);
+ EditorZoom.ZoomIn(1.0).Should().BeApproximately(1.1, 0.001);
}
[Test]
public void ZoomOut_Divides_By_Factor()
{
- EditorZoom.ZoomOut(11.0).Should().BeApproximately(10.0, 0.001);
+ EditorZoom.ZoomOut(1.1).Should().BeApproximately(1.0, 0.001);
}
[Test]
- public void Reset_Returns_Default_Font_Size()
+ public void Reset_Returns_Default_Zoom()
{
- EditorZoom.Reset().Should().Be(EditorZoom.DefaultFontSize);
+ EditorZoom.Reset().Should().Be(EditorZoom.DefaultZoom);
}
[Test]
public void Zoom_Round_Trip_Returns_To_Default_Without_Floating_Point_Drift()
{
// 1.1 * (1/1.1) is bit-equal-ish but not exactly equal in float; without the
- // snap-to-default heuristic, the rounded-trip value would be 13.3333000001
- // instead of 13.3333333... and the user would see a sticky "non-100% zoom"
- // even after they explicitly returned to default. The Reset path bypasses
- // this entirely, but Ctrl+Wheel round trips need the snap.
- var step1 = EditorZoom.ZoomIn(EditorZoom.DefaultFontSize);
+ // snap-to-default heuristic, the round-tripped value would be 1.0000000001
+ // instead of 1.0 and the user would see a sticky "non-100% zoom" even after
+ // they explicitly returned to default. The Reset path bypasses this entirely,
+ // but Ctrl+Wheel round trips need the snap.
+ var step1 = EditorZoom.ZoomIn(EditorZoom.DefaultZoom);
var step2 = EditorZoom.ZoomOut(step1);
- step2.Should().Be(EditorZoom.DefaultFontSize);
+ step2.Should().Be(EditorZoom.DefaultZoom);
}
[Test]
public void ZoomIn_Clamps_At_Upper_Bound()
{
- EditorZoom.ZoomIn(EditorZoom.MaxFontSize).Should().Be(EditorZoom.MaxFontSize);
+ EditorZoom.ZoomIn(EditorZoom.MaxZoom).Should().Be(EditorZoom.MaxZoom);
}
[Test]
public void ZoomOut_Clamps_At_Lower_Bound()
{
- EditorZoom.ZoomOut(EditorZoom.MinFontSize).Should().Be(EditorZoom.MinFontSize);
+ EditorZoom.ZoomOut(EditorZoom.MinZoom).Should().Be(EditorZoom.MinZoom);
}
[Test]
public void ZoomOut_Of_Slightly_Above_Min_Saturates_Not_Below()
{
- // Slightly above MinFontSize zoomed out by the standard factor should land
- // AT min, not below. Guards against an off-by-clamp bug where the post-divide
- // value is below min but clamping only runs on the multiplier output.
- var justAbove = EditorZoom.MinFontSize * 1.05;
- EditorZoom.ZoomOut(justAbove).Should().Be(EditorZoom.MinFontSize);
+ // Slightly above MinZoom zoomed out by the standard factor should land AT min,
+ // not below. Guards against an off-by-clamp bug where the post-divide value is
+ // below min but clamping only runs on the multiplier output.
+ EditorZoom.ZoomOut(EditorZoom.MinZoom * 1.05).Should().Be(EditorZoom.MinZoom);
+ }
+
+ [Test]
+ public void EffectiveFontSize_Scales_The_Configured_Size()
+ {
+ var settings = new DisplaySettings { SelectedFontSize = 20, EditorZoomFactor = 1.5 };
+ EditorZoom.EffectiveFontSize(settings).Should().BeApproximately(30, 0.001);
+ }
+
+ [AvaloniaTest]
+ public void Changing_The_Configured_Font_Size_Leaves_The_Zoom_Buttons_Hidden()
+ {
+ // The options dialog moves the base font size; that is not a zoom, so the
+ // overlay must stay hidden and the label must keep reading 100%.
+ var settings = new DisplaySettings();
+ var buttons = new ZoomButtons();
+ buttons.Bind(settings);
+
+ settings.SelectedFontSize = 24;
+
+ buttons.IsVisible.Should().BeFalse();
+ buttons.ZoomPercentText.Should().Be("100%");
+ }
+
+ [AvaloniaTest]
+ public void Zooming_Shows_The_Zoom_Buttons()
+ {
+ var settings = new DisplaySettings();
+ var buttons = new ZoomButtons();
+ buttons.Bind(settings);
+
+ settings.EditorZoomFactor = 1.5;
+
+ buttons.IsVisible.Should().BeTrue();
+ buttons.ZoomPercentText.Should().Be("150%");
}
}
diff --git a/ILSpy/Options/DisplaySettingReactions.cs b/ILSpy/Options/DisplaySettingReactions.cs
index 11a3cb6e4..b8a66f9a7 100644
--- a/ILSpy/Options/DisplaySettingReactions.cs
+++ b/ILSpy/Options/DisplaySettingReactions.cs
@@ -77,6 +77,7 @@ namespace ICSharpCode.ILSpy.Options
// Editor-only (DecompilerTextView applies these directly to the AvaloniaEdit control).
[nameof(DisplaySettings.SelectedFont)] = DisplaySettingReaction.EditorLive,
[nameof(DisplaySettings.SelectedFontSize)] = DisplaySettingReaction.EditorLive,
+ [nameof(DisplaySettings.EditorZoomFactor)] = DisplaySettingReaction.EditorLive,
[nameof(DisplaySettings.ShowLineNumbers)] = DisplaySettingReaction.EditorLive,
[nameof(DisplaySettings.EnableWordWrap)] = DisplaySettingReaction.EditorLive,
[nameof(DisplaySettings.HighlightCurrentLine)] = DisplaySettingReaction.EditorLive,
diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs
index ca74d7280..932c15185 100644
--- a/ILSpy/Options/DisplaySettings.cs
+++ b/ILSpy/Options/DisplaySettings.cs
@@ -39,6 +39,12 @@ namespace ICSharpCode.ILSpy.Options
[ObservableProperty]
double selectedFontSize = 10.0 * 4 / 3;
+ /// Editor zoom (Ctrl+Wheel / the zoom overlay), as a multiplier on top of
+ /// . Kept separate so changing the font size in the
+ /// options dialog isn't mistaken for a zoom.
+ [ObservableProperty]
+ double editorZoomFactor = 1.0;
+
[ObservableProperty]
bool showLineNumbers;
@@ -111,6 +117,7 @@ namespace ICSharpCode.ILSpy.Options
{
SelectedFont = (string?)section.Attribute("Font") ?? "Consolas";
SelectedFontSize = (double?)section.Attribute("FontSize") ?? 10.0 * 4 / 3;
+ EditorZoomFactor = (double?)section.Attribute(nameof(EditorZoomFactor)) ?? 1.0;
ShowLineNumbers = (bool?)section.Attribute(nameof(ShowLineNumbers)) ?? false;
ShowMetadataTokens = (bool?)section.Attribute(nameof(ShowMetadataTokens)) ?? false;
ShowMetadataTokensInBase10 = (bool?)section.Attribute(nameof(ShowMetadataTokensInBase10)) ?? false;
@@ -140,6 +147,7 @@ namespace ICSharpCode.ILSpy.Options
var section = new XElement(SectionName);
section.SetAttributeValue("Font", SelectedFont);
section.SetAttributeValue("FontSize", SelectedFontSize);
+ section.SetAttributeValue(nameof(EditorZoomFactor), EditorZoomFactor);
section.SetAttributeValue(nameof(ShowLineNumbers), ShowLineNumbers);
section.SetAttributeValue(nameof(ShowMetadataTokens), ShowMetadataTokens);
section.SetAttributeValue(nameof(ShowMetadataTokensInBase10), ShowMetadataTokensInBase10);
diff --git a/ILSpy/TextView/DecompilerTextEditor.cs b/ILSpy/TextView/DecompilerTextEditor.cs
index 5004c0fe7..71cb27bb6 100644
--- a/ILSpy/TextView/DecompilerTextEditor.cs
+++ b/ILSpy/TextView/DecompilerTextEditor.cs
@@ -86,7 +86,8 @@ namespace ICSharpCode.ILSpy.TextView
void OnDisplaySettingsChanged(object? sender, PropertyChangedEventArgs e)
{
- if (e.PropertyName is nameof(DisplaySettings.SelectedFont) or nameof(DisplaySettings.SelectedFontSize))
+ if (e.PropertyName is nameof(DisplaySettings.SelectedFont) or nameof(DisplaySettings.SelectedFontSize)
+ or nameof(DisplaySettings.EditorZoomFactor))
ApplyFontSettings();
}
@@ -97,7 +98,7 @@ namespace ICSharpCode.ILSpy.TextView
if (!string.IsNullOrEmpty(displaySettings.SelectedFont))
FontFamily = new FontFamily(displaySettings.SelectedFont);
if (displaySettings.SelectedFontSize > 0)
- FontSize = displaySettings.SelectedFontSize;
+ FontSize = EditorZoom.EffectiveFontSize(displaySettings);
}
static DisplaySettings? TryGetDisplaySettings()
diff --git a/ILSpy/TextView/DecompilerTextView.axaml b/ILSpy/TextView/DecompilerTextView.axaml
index e3d119117..2e08f54c3 100644
--- a/ILSpy/TextView/DecompilerTextView.axaml
+++ b/ILSpy/TextView/DecompilerTextView.axaml
@@ -68,9 +68,9 @@
-
+