Skip to content
Open
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
17 changes: 17 additions & 0 deletions internal/driver/glfw/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ func (c *glCanvas) Resize(size fyne.Size) {
// This can easily be seen with fyne/cmd/hello and a scale == 1 as the text will happear blurry without the following line.
nearestSize := fyne.NewSize(float32(math.Ceil(float64(size.Width))), float32(math.Ceil(float64(size.Height))))

// Use actual window size when available and different from expected (important for tiling window managers)
if ctx, ok := c.context.(*window); ok && ctx.viewport != nil && ctx.visible && !ctx.fullScreen {
actualWidth, actualHeight := ctx.viewport.GetSize()
if actualWidth > 0 && actualHeight > 0 {
// Check if actual size differs from what we expect
expectedWidth, expectedHeight := ctx.screenSize(nearestSize)
if actualWidth != expectedWidth || actualHeight != expectedHeight {
// Size mismatch detected - recalculate canvas size based on actual window size
actualCanvasSize := ctx.computeCanvasSize(actualWidth, actualHeight)
nearestSize = fyne.NewSize(float32(math.Ceil(float64(actualCanvasSize.Width))), float32(math.Ceil(float64(actualCanvasSize.Height))))

ctx.width = actualWidth
ctx.height = actualHeight
}
}
}

c.size = nearestSize

if c.webExtraWindows != nil {
Expand Down
21 changes: 18 additions & 3 deletions internal/driver/glfw/window_desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,24 @@ func (w *window) RescaleContext() {
return
}

size := w.canvas.size.Max(w.canvas.MinSize())
newWidth, newHeight := w.screenSize(size)
w.viewport.SetSize(newWidth, newHeight)
// When scale changes (e.g., moving between monitors), we need to recalculate
// the canvas size based on the current window size, not the other way around
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the source of the change in behavior - with previous setup it was correct to adjust the window size based on the content and scale so that when moving when scale changes the window changed too (worked on all except gnome moving from a low scale to high scale monitor - desktop would not allow the window to shrink).

I wonder if there is a way to fix tiling whilst maintaining the old behavior?

I don't really know why tiling breaks in this code so I'm not sure if there is a happy medium.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the issue that fyne can't control the window size in every environment? But it can control the canvas size so when it doesn't get the window size it wants, it needs to adjust the canvas size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make sense.

So instead of trying to control it with this PR it doesn't try any more? Can we somehow attempt to resize and fallback if it doesn't work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't regularly work with non-tiled windows, but the automatic resizing feels awkward to me.

as-of-19dcdbdb5

The behavior with the changes in this branch feel more intuitive to me.

with-scaling-fix-branch

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would want to test this PR again but I do agree that using this PR felt more natural when I moved the window between screens.

Copy link
Member

@Jacalz Jacalz Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to compare how other apps behave when moving them between windows. This is a GTK4 app:

Screencast.From.2025-09-06.08-20-01.webm

This is Fyne before this change (jumps a bit in size also):

Screencast.From.2025-09-06.08-20-38.webm

This is with this PR:

Screencast.From.2025-09-06.08-23-05.webm

With this, it actually behaves more like the other apps on my system and like what I would expect. The scaling and/or window size seems to potentially be a bit smaller than before though, I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that comparing to GTK+ is a good idea - they have declared that fractional scaling on X11 is not possible and rely on gnome desktop hinting instead of DPI detection.
Not apples and Oranges.

If you compare with how apps work on Windows or macOS you will see the scale changes and the size changes so that the UI is a consistent size unless user specifies other default for the monitors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want the GTK version then turn off DPI detection completely rather than removing it from the codebase.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that comparing to GTK+ is a good idea - they have declared that fractional scaling on X11 is not possible and rely on gnome desktop hinting instead of DPI detection.
Not apples and Oranges.

What does their opinion on X11 and fractional scaling have to do with it? I am using Wayland and have 125% fractional scaling enabled on the right monitor. It works perfectly. The behaviour of this app seems more sane after this change than before.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry - are you doing all of this testing with pure-wayland compiled Fyne? I did not realise that was the case. If so it is a different code path and I will have to re-analyse as the wayland scale handling is not the same as X11.

if w.visible {
currentWidth, currentHeight := w.viewport.GetSize()
if currentWidth > 0 && currentHeight > 0 {
canvasSize := fyne.NewSize(
scale.ToFyneCoordinate(w.canvas, currentWidth),
scale.ToFyneCoordinate(w.canvas, currentHeight))
w.canvas.Resize(canvasSize)

w.width = currentWidth
w.height = currentHeight
}
} else {
size := w.canvas.size.Max(w.canvas.MinSize())
newWidth, newHeight := w.screenSize(size)
w.viewport.SetSize(newWidth, newHeight)
}

// Ensure textures re-rasterize at the new scale
cache.DeleteTextTexturesFor(w.canvas)
Expand Down
71 changes: 71 additions & 0 deletions internal/driver/glfw/window_rescale_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//go:build !wasm && !test_web_driver && !mobile && !no_glfw

package glfw

import (
"testing"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/stretchr/testify/assert"
)

func TestWindow_RescaleContext_Basic(t *testing.T) {
// Basic test to ensure RescaleContext doesn't panic
w := createWindow("Test")
defer w.Close()
w.SetContent(widget.NewLabel("Test"))

// Test that RescaleContext can be called without panic
runOnMain(func() {
w.RescaleContext()
})

// Verify window still has valid canvas
assert.NotNil(t, w.Canvas())
assert.NotNil(t, w.Canvas().Content())
}

func TestWindow_RescaleContext_WithContent(t *testing.T) {
// Test RescaleContext with actual content
w := createWindow("Test")
defer w.Close()

content := container.NewVBox(
widget.NewLabel("Test App"),
widget.NewButton("Click Me", func() {}),
)
w.SetContent(content)

// Call RescaleContext
runOnMain(func() {
w.RescaleContext()
})

// Verify content is preserved
assert.Equal(t, content, w.Canvas().Content())
assert.Greater(t, w.Canvas().Size().Width, float32(0))
assert.Greater(t, w.Canvas().Size().Height, float32(0))
}

func TestWindow_RescaleContext_MinSize(t *testing.T) {
// Test that RescaleContext respects minimum size
w := createWindow("Test")
defer w.Close()

label := widget.NewLabel("Test with minimum size")
label.Resize(fyne.NewSize(300, 200))
w.SetContent(label)

// Call RescaleContext
runOnMain(func() {
w.RescaleContext()
})

// Canvas should respect content minimum size
minSize := label.MinSize()
canvasSize := w.Canvas().Size()
assert.GreaterOrEqual(t, canvasSize.Width, minSize.Width)
assert.GreaterOrEqual(t, canvasSize.Height, minSize.Height)
}
Loading