diff --git a/src/Modern.Forms/Form.cs b/src/Modern.Forms/Form.cs index c456f25..f3991c8 100644 --- a/src/Modern.Forms/Form.cs +++ b/src/Modern.Forms/Form.cs @@ -24,6 +24,7 @@ public class Form : WindowBase, ICloseable private bool show_focus_cues; private string text = string.Empty; private bool use_system_decorations; + private readonly ControlStyle maximized_style; /// /// Initializes a new instance of the Form class. @@ -32,6 +33,11 @@ public Form () : base (AvaloniaGlobals.GetRequiredService () { TitleBar = Controls.AddImplicitControl (new FormTitleBar ()); + // Style used when the window is maximized: inherits everything from the instance + // Style but suppresses the border so no thin colored strip is visible at the edges. + maximized_style = new ControlStyle (Style); + maximized_style.Border.Width = 0; + Resizeable = true; Window.SetSystemDecorations (SystemDecorations.None); Window.SetExtendClientAreaToDecorationsHint (true); @@ -79,6 +85,10 @@ public bool AllowMinimize { } } + /// + /// Returns a no-border style when the window is maximized to prevent a thin border artifact at screen edges. + public override ControlStyle CurrentStyle => WindowState == FormWindowState.Maximized ? maximized_style : base.CurrentStyle; + /// public override void Close () { @@ -183,6 +193,9 @@ private WindowElement GetElementAtLocation (int x, int y) internal override bool HandleMouseDown (int x, int y) { + if (WindowState == FormWindowState.Maximized) + return false; + var element = GetElementAtLocation (x, y); switch (element) { @@ -217,6 +230,9 @@ internal override bool HandleMouseDown (int x, int y) internal override bool HandleMouseMove (int x, int y) { + if (WindowState == FormWindowState.Maximized) + return base.HandleMouseMove (x, y); + var element = GetElementAtLocation (x, y); switch (element) { diff --git a/src/Modern.Forms/WindowBase.cs b/src/Modern.Forms/WindowBase.cs index 0a89e82..84ca328 100644 --- a/src/Modern.Forms/WindowBase.cs +++ b/src/Modern.Forms/WindowBase.cs @@ -294,17 +294,26 @@ private void DoPaint (Rect r) var framebufferImageInfo = new SKImageInfo (framebuffer.Size.Width, framebuffer.Size.Height, framebuffer.Format.ToSkColorType (), framebuffer.Format == PixelFormat.Rgb565 ? SKAlphaType.Opaque : SKAlphaType.Premul); - var scaled_client_size = ScaledClientSize; - var scaled_display_rect = ScaledDisplayRectangle; + // Use the actual framebuffer pixel dimensions directly. Computing the size via + // (int)(ClientSize * RenderScaling) can lose 1 pixel at fractional DPI (e.g. 150%) + // because the float round-trip (pixels / scale * scale) truncates to one fewer pixel, + // leaving a strip of unfilled background at the right/bottom edges. + var fb_width = framebuffer.Size.Width; + var fb_height = framebuffer.Size.Height; + var border = CurrentStyle.Border; + var fb_display_rect = new System.Drawing.Rectangle ( + border.Left.GetWidth (), border.Top.GetWidth (), + fb_width - border.Left.GetWidth () - border.Right.GetWidth (), + fb_height - border.Top.GetWidth () - border.Bottom.GetWidth ()); using var surface = SKSurface.Create (framebufferImageInfo, framebuffer.Address, framebuffer.RowBytes); var e = new PaintEventArgs (framebufferImageInfo, surface.Canvas, Scaling); OnPaintBackground (e); - e.Canvas.DrawBorder (new System.Drawing.Rectangle (0, 0, (int)scaled_client_size.Width, (int)scaled_client_size.Height), CurrentStyle); + e.Canvas.DrawBorder (new System.Drawing.Rectangle (0, 0, fb_width, fb_height), CurrentStyle); OnPaint (e); - e.Canvas.ClipRect (new SKRect (scaled_display_rect.Left, scaled_display_rect.Top, scaled_display_rect.Width + 1, scaled_display_rect.Height + 1)); + e.Canvas.ClipRect (new SKRect (fb_display_rect.Left, fb_display_rect.Top, fb_display_rect.Right, fb_display_rect.Bottom)); adapter.RaisePaintBackground (e); adapter.RaisePaint (e); @@ -344,7 +353,11 @@ protected virtual void OnPaintBackground (PaintEventArgs e) private void OnResize (Size size, WindowResizeReason reason) { - adapter.SetBounds (DisplayRectangle.Left, DisplayRectangle.Top, Size.Width, Size.Height); + // Use Math.Ceiling so controls are sized to cover the full framebuffer even when + // the logical window size is fractional (e.g. 2576 device px / 1.5 = 1717.33 logical px). + // Without ceiling, (int) truncation would make controls 1 logical pixel too narrow, + // leaving a strip of form background showing at the right/bottom edges. + adapter.SetBounds (DisplayRectangle.Left, DisplayRectangle.Top, (int)Math.Ceiling (size.Width), (int)Math.Ceiling (size.Height)); } ///