fix(ui): give the Settings Theme row back its top gap

The Application section stacks its rows with rowOverlap(), a negative
spacing that trades away one label's duplicated text inset. The Theme
row's value is a Select, which paints its box out to the row's edge and
has no inset to give, so the overlap closed the gap instead: 0.46 px
between the Notifications checkbox and the dropdown, against ~8 px
between the checkbox rows.

cancelRowOverlap adds that one padding back on the Theme row's top edge
only, restoring the gap to 7.5 px without touching the other rows or the
column width.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mixeme
2026-07-27 22:04:18 +03:00
parent 9dd461e35e
commit 9a45a7be6f
4 changed files with 55 additions and 1 deletions
+5
View File
@@ -46,6 +46,11 @@ dragged.**
- The **Application** and **About** blocks are about 2 px tighter: every stacked
row group in the app now shares one spacing derived from the theme rather than
three separately tuned numbers.
- The **Theme** dropdown is no longer flush against the **Notifications**
checkbox. That shared row spacing pulls rows together by one text inset, which
the rows above have to give but a dropdown — which paints its box out to the
row's edge — does not, so the gap collapsed to about a pixel. The Theme row
now keeps the same gap the checkbox rows have.
## 0.15.0 - 2026-07-26
+13
View File
@@ -2,6 +2,8 @@ package ui
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
@@ -59,6 +61,17 @@ func (l minWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
// than hard-coded so it follows a theme that changes SizeNameInnerPadding.
func rowOverlap() float32 { return -theme.InnerPadding() }
// cancelRowOverlap exempts one row from the rowOverlap() spacing of the section
// it sits in, by padding its top edge with exactly what rowOverlap takes away.
// The overlap assumes both neighbours are text rows: each insets its text, so
// one padding's worth is duplicated and can go. A row whose value paints its own
// box to the row's edge — a Select, an Entry, a Button — has no such inset, so
// the overlap eats the visible gap instead and the box ends up flush against the
// row above it.
func cancelRowOverlap(row fyne.CanvasObject) fyne.CanvasObject {
return container.New(layout.NewCustomPaddedLayout(-rowOverlap(), 0, 0, 0), row)
}
// initialSplitOffset returns the container.Split offset that opens a horizontal
// split with its leading pane at the given natural width. SetOffset takes a
// ratio, but a pane's natural width is absolute, so the ratio is derived from
+32
View File
@@ -5,6 +5,7 @@ import (
"fyne.io/fyne/v2/test"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
// TestRowOverlapMatchesInnerPadding pins rowOverlap to theme.InnerPadding, the
@@ -27,6 +28,37 @@ func TestRowOverlapMatchesInnerPadding(t *testing.T) {
}
}
// TestCancelRowOverlapAddsBackOneInnerPadding is the regression guard for the
// Settings tab's Theme row sitting flush against the Notifications checkbox:
// the wrapper must add exactly the padding rowOverlap removes, on the top edge
// only, so the row below is unaffected and the width does not change.
func TestCancelRowOverlapAddsBackOneInnerPadding(t *testing.T) {
testApp := test.NewApp()
defer testApp.Quit()
child := widget.NewSelect([]string{"System"}, nil)
wrapped := cancelRowOverlap(child)
childMin, wrappedMin := child.MinSize(), wrapped.MinSize()
if got, want := wrappedMin.Height, childMin.Height-rowOverlap(); got != want {
t.Errorf("wrapped height = %v, want %v (child %v plus one inner padding)", got, want, childMin.Height)
}
if got, want := wrappedMin.Width, childMin.Width; got != want {
t.Errorf("wrapped width = %v, want the child's %v", got, want)
}
wrapped.Resize(wrappedMin)
if got, want := child.Position().Y, -rowOverlap(); got != want {
t.Errorf("child Y = %v, want %v", got, want)
}
if got := child.Position().X; got != 0 {
t.Errorf("child X = %v, want 0", got)
}
if got, want := child.Size().Height, childMin.Height; got != want {
t.Errorf("child height = %v, want %v: the padding must not be taken out of the row", got, want)
}
}
// TestCaptionColumnWidth covers the shapes F10's shared helper has to handle:
// no captions, one, and several of varying length at two text sizes.
func TestCaptionColumnWidth(t *testing.T) {
+5 -1
View File
@@ -60,7 +60,11 @@ func newSettingsLayout(f settingsFormFields) fyne.CanvasObject {
settingsRow(capW, "", f.autostartStatus),
settingsRow(capW, "Tray", f.minimizeToTray),
settingsRow(capW, "Notifications", f.notifications),
settingsRow(capW, "Theme", f.themeSelect),
// Theme is the one row here whose value is not text: the Select paints
// a box out to the row's edge, so the section's overlap would leave it
// flush against the Notifications checkbox. Cancelling the overlap for
// this row alone restores the gap the checkbox rows have.
cancelRowOverlap(settingsRow(capW, "Theme", f.themeSelect)),
),
widget.NewSeparator(),
// Queue used to inline its own container.NewVBox at the theme's default