Modal PopUp/dialog content re-centers on every canvas resize (regression since 2.8.0) — Android soft keyboard causes buttons to shift under finger mid-tap
Checklist
- I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
- This issue only relates to a single bug. I will open new issues for any other problems.
Describe the bug
A modal dialog/PopUp containing a focusable text Entry re-centers its content
every time the canvas resizes. On Android, opening/closing the on-screen
keyboard resizes the app canvas, so the dialog's buttons visibly move
vertically while the keyboard opens/closes.
Because tapping any button in the dialog while the Entry still has focus first triggers focus loss (closing the keyboard) as part of the same touch-down/touch-up gesture, the button positions can shift during that gesture. The result is unreliable tap handling: sometimes the tap only closes the keyboard, sometimes it lands on a different button than the one under the finger.
Root cause (found by bisecting the source between 2.7.4 and 2.8.1)
internal/widget/overlay_container.go, overlayRenderer.Layout:
2.7.4 — no-op, does not touch the content on canvas layout:
func (r *overlayRenderer) Layout(fyne.Size) {
}2.8.0/2.8.1 — actively resizes and re-centers the content on every layout pass:
func (r *overlayRenderer) Layout(s fyne.Size) {
size := internal.MinSizes(internal.MaxSizes(r.o.Content.Size(), r.o.Content.MinSize()), s)
r.o.Content.Resize(size)
if r.o.Background != nil {
r.o.Background.Resize(s)
}
if r.o.manual { // position is overridden
return
}
midX := s.Width/2 - size.Width/2
midY := s.Height/2 - size.Height/2
r.o.Content.Move(fyne.NewPos(midX, midY))
}In 2.7.4 the OverlayContainer never forwarded a canvas resize down to its
Content (the dialog's PopUp), so the popup only positioned itself once
(at Show()/Refresh()), never again on a later canvas resize. In 2.8 this
was changed so overlayRenderer.Layout calls Content.Resize(...) and
re-centers on every layout pass — which the canvas engine runs whenever
the canvas itself resizes, including the resize Android performs when the
soft keyboard opens/closes.
This looks like an unintended side effect of the padding/positioning rework mentioned in the 2.8.0 changelog ("Popups (modal and regular) no longer include padding by default"), rather than an intentional behavior change — I couldn't find any changelog entry describing "dialogs now re-center on every resize" as a deliberate feature.
How to reproduce
Steps to reproduce
- On Android, show a modal dialog (
dialog.NewCustomWithoutButtonsor similar) containing a focusablewidget.Entryand one or more buttons below it. - Tap the entry to give it focus and open the soft keyboard.
- Type something, then tap one of the dialog's buttons.
- Observe: the keyboard closing and the dialog re-centering happen as part
of the same tap gesture. Depending on timing, either only the keyboard
closes (button doesn't fire), or a different button than the one tapped
receives the
Tappedevent because it moved into that screen position during the gesture.
Expected behavior
A dialog's on-screen position should not silently change in response to an unrelated canvas resize (e.g. a soft keyboard opening/closing) unless it was explicitly resized/repositioned by the app. At minimum, this recentering should not happen synchronously with an in-flight tap gesture, since it makes tap targeting non-deterministic.
Screenshots
No response
Example code
Example code
package main
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Overlay recenter repro")
w.Resize(fyne.NewSize(400, 600))
entry := widget.NewPasswordEntry()
entry.SetPlaceHolder("PIN")
var d dialog.Dialog
btn1 := widget.NewButton("Button 1", func() {
w.SetTitle("Button 1 tapped")
})
btn2 := widget.NewButton("Button 2", func() {
w.SetTitle("Button 2 tapped")
})
cancel := widget.NewButton("Cancel", func() {
d.Hide()
})
content := container.NewVBox(
widget.NewLabel("Focus the entry, open the keyboard, type something,\nthen tap any button below."),
entry,
widget.NewSeparator(),
btn1,
btn2,
cancel,
)
d = dialog.NewCustomWithoutButtons("Repro", content, w)
d.Resize(fyne.NewSize(300, 0))
open := widget.NewButton("Open dialog", func() {
d.Show()
})
w.SetContent(container.NewCenter(open))
w.ShowAndRun()
}On Android: tap "Open dialog", tap the entry, type a couple of characters, then tap Button 1/Button 2/Cancel. The dialog shifts down as the keyboard closes, and the tap can miss its target or hit the wrong button — the window title (set by whichever button's callback actually fired, if any) shows which one really received the tap.
Fyne version
2.8.1
Go compiler version
1.27
Operating system and version
windows 11 and fyne-cross (android)
Additional Information
No response
Source: fyne-io/fyne