From 80614bf78931b5d9326bb554bd5e2fc1bc0605b7 Mon Sep 17 00:00:00 2001 From: Roffe Date: Mon, 9 Dec 2024 17:34:21 +0100 Subject: [PATCH 1/9] allow customization of button location on InnerWindow --- container/innerwindow.go | 44 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index 32e0839113..3503887756 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -11,6 +11,14 @@ import ( var _ fyne.Widget = (*InnerWindow)(nil) +// InnerWindowButtonAlignment defines the alignment of the buttons in the window title. +type InnerWindowButtonAlignment int + +const ( + InnerWindowButtonAlignLeft InnerWindowButtonAlignment = iota // OSX style (default) + InnerWindowButtonAlignRight // Linux, Windows style +) + // InnerWindow defines a container that wraps content in a window border - that can then be placed inside // a regular container/canvas. // @@ -18,6 +26,7 @@ var _ fyne.Widget = (*InnerWindow)(nil) type InnerWindow struct { widget.BaseWidget + ButtonAlignment InnerWindowButtonAlignment CloseIntercept func() `json:"-"` OnDragged, OnResized func(*fyne.DragEvent) `json:"-"` OnMinimized, OnMaximized, OnTappedBar, OnTappedIcon func() `json:"-"` @@ -53,15 +62,13 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { max.Disable() } - buttons := NewHBox( - &widget.Button{Icon: theme.WindowCloseIcon(), Importance: widget.DangerImportance, OnTapped: func() { - if f := w.CloseIntercept; f != nil { - f() - } else { - w.Close() - } - }}, - min, max) + close := &widget.Button{Icon: theme.WindowCloseIcon(), Importance: widget.DangerImportance, OnTapped: func() { + if f := w.CloseIntercept; f != nil { + f() + } else { + w.Close() + } + }} var icon fyne.CanvasObject if w.Icon != nil { @@ -74,12 +81,29 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { icon.(*widget.Button).Disable() } } + title := newDraggableLabel(w.title, w) title.Truncation = fyne.TextTruncateEllipsis + + var buttons *fyne.Container + var bar *fyne.Container + + switch w.ButtonAlignment { + case InnerWindowButtonAlignRight: // Right side + buttons = NewHBox( + min, max, close, + ) + bar = NewBorder(nil, nil, icon, buttons, title) + default: // Left side + buttons = NewHBox( + close, min, max, + ) + bar = NewBorder(nil, nil, buttons, icon, title) + } + th := w.Theme() v := fyne.CurrentApp().Settings().ThemeVariant() - bar := NewBorder(nil, nil, buttons, icon, title) bg := canvas.NewRectangle(th.Color(theme.ColorNameOverlayBackground, v)) contentBG := canvas.NewRectangle(th.Color(theme.ColorNameBackground, v)) corner := newDraggableCorner(w) From 5a1eda1673a8071cd951c81986c1531ec626e77a Mon Sep 17 00:00:00 2001 From: Roffe Date: Mon, 9 Dec 2024 21:56:08 +0100 Subject: [PATCH 2/9] introduce default alternative --- container/innerwindow.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index 3503887756..b77a64bf1c 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -1,6 +1,8 @@ package container import ( + "runtime" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" intWidget "fyne.io/fyne/v2/internal/widget" @@ -15,8 +17,9 @@ var _ fyne.Widget = (*InnerWindow)(nil) type InnerWindowButtonAlignment int const ( - InnerWindowButtonAlignLeft InnerWindowButtonAlignment = iota // OSX style (default) - InnerWindowButtonAlignRight // Linux, Windows style + InnerWindowButtonAlignDefault InnerWindowButtonAlignment = iota // Default, OS specific alignment ( macOS: Left, Windows & Linux: Right ) + InnerWindowButtonAlignLeft // Left, macOS style + InnerWindowButtonAlignRight // Right, Windows style ) // InnerWindow defines a container that wraps content in a window border - that can then be placed inside @@ -88,16 +91,15 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { var buttons *fyne.Container var bar *fyne.Container - switch w.ButtonAlignment { - case InnerWindowButtonAlignRight: // Right side - buttons = NewHBox( - min, max, close, - ) + isLeftSide := w.ButtonAlignment == InnerWindowButtonAlignLeft || (w.ButtonAlignment == InnerWindowButtonAlignDefault && runtime.GOOS == "darwin") + + if isLeftSide { + // Left side (macOS default or explicit left alignment) + buttons = NewHBox(min, max, close) bar = NewBorder(nil, nil, icon, buttons, title) - default: // Left side - buttons = NewHBox( - close, min, max, - ) + } else { + // Right side (Windows/Linux default and explicit right alignment) + buttons = NewHBox(close, min, max) bar = NewBorder(nil, nil, buttons, icon, title) } @@ -128,6 +130,10 @@ func (w *InnerWindow) SetPadded(pad bool) { w.content.Refresh() } +func (w *InnerWindow) Title() string { + return w.title +} + func (w *InnerWindow) SetTitle(title string) { w.title = title w.Refresh() From 27070193f46085bcd71a1890553f853e53a076ea Mon Sep 17 00:00:00 2001 From: Roffe Date: Mon, 9 Dec 2024 22:00:10 +0100 Subject: [PATCH 3/9] fix order logic --- container/innerwindow.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index b77a64bf1c..2f487005dc 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -44,7 +44,11 @@ type InnerWindow struct { // // Since: 2.5 func NewInnerWindow(title string, content fyne.CanvasObject) *InnerWindow { - w := &InnerWindow{title: title, content: NewPadded(content)} + w := &InnerWindow{ + title: title, + content: NewPadded(content), + ButtonAlignment: InnerWindowButtonAlignDefault, + } w.ExtendBaseWidget(w) return w } @@ -94,13 +98,13 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { isLeftSide := w.ButtonAlignment == InnerWindowButtonAlignLeft || (w.ButtonAlignment == InnerWindowButtonAlignDefault && runtime.GOOS == "darwin") if isLeftSide { - // Left side (macOS default or explicit left alignment) - buttons = NewHBox(min, max, close) - bar = NewBorder(nil, nil, icon, buttons, title) - } else { - // Right side (Windows/Linux default and explicit right alignment) + // Left side (darwin default or explicit left alignment) buttons = NewHBox(close, min, max) bar = NewBorder(nil, nil, buttons, icon, title) + } else { + // Right side (Windows/Linux default and explicit right alignment) + buttons = NewHBox(min, max, close) + bar = NewBorder(nil, nil, icon, buttons, title) } th := w.Theme() From ee9ac756677ad81baa2f6df2737768a5ede30bfd Mon Sep 17 00:00:00 2001 From: Roffe Date: Tue, 10 Dec 2024 00:15:34 +0100 Subject: [PATCH 4/9] fix tests --- container/innerwindow_test.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/container/innerwindow_test.go b/container/innerwindow_test.go index 55d1436ded..61c3ef25d9 100644 --- a/container/innerwindow_test.go +++ b/container/innerwindow_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" ) -func TestInnerWindow_Close(t *testing.T) { +func TestInnerWindow_Close_Left(t *testing.T) { w := NewInnerWindow("Thing", widget.NewLabel("Content")) - + w.ButtonAlignment = InnerWindowButtonAlignLeft outer := test.NewTempWindow(t, w) outer.SetPadded(false) outer.Resize(w.MinSize()) @@ -36,6 +36,31 @@ func TestInnerWindow_Close(t *testing.T) { assert.True(t, w.Visible()) } +func TestInnerWindow_Close_Right(t *testing.T) { + w := NewInnerWindow("Thing", widget.NewLabel("Content")) + w.ButtonAlignment = InnerWindowButtonAlignRight + outer := test.NewTempWindow(t, w) + outer.SetPadded(false) + outer.Resize(w.MinSize()) + assert.True(t, w.Visible()) + + closePos := fyne.NewPos(w.Size().Width-10, 10) + test.TapCanvas(outer.Canvas(), closePos) + assert.False(t, w.Visible()) + + w.Show() + assert.True(t, w.Visible()) + + closing := true + w.CloseIntercept = func() { + closing = true + } + + test.TapCanvas(outer.Canvas(), closePos) + assert.True(t, closing) + assert.True(t, w.Visible()) +} + func TestInnerWindow_MinSize(t *testing.T) { w := NewInnerWindow("Thing", widget.NewLabel("Content")) From 635ab8cf9d6f1cdc080ea234fd759bc32865147f Mon Sep 17 00:00:00 2001 From: Roffe Date: Tue, 10 Dec 2024 12:46:24 +0100 Subject: [PATCH 5/9] feedback --- container/innerwindow.go | 27 +++++++++--------- container/innerwindow_test.go | 52 +++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index 2f487005dc..187b8cb65a 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -13,15 +13,6 @@ import ( var _ fyne.Widget = (*InnerWindow)(nil) -// InnerWindowButtonAlignment defines the alignment of the buttons in the window title. -type InnerWindowButtonAlignment int - -const ( - InnerWindowButtonAlignDefault InnerWindowButtonAlignment = iota // Default, OS specific alignment ( macOS: Left, Windows & Linux: Right ) - InnerWindowButtonAlignLeft // Left, macOS style - InnerWindowButtonAlignRight // Right, Windows style -) - // InnerWindow defines a container that wraps content in a window border - that can then be placed inside // a regular container/canvas. // @@ -29,7 +20,13 @@ const ( type InnerWindow struct { widget.BaseWidget - ButtonAlignment InnerWindowButtonAlignment + // ButtonAlignment specifies where the window buttons (close, minimize, maximize) should be placed. + // The default is widget.ButtonAlignCenter which will auto select based on the OS. + // - On Windows and Linux this will be `widget.ButtonAlignTrailing` + // - On Darwin this will be `widget.ButtonAlignLeading` + // + // Since: 2.6 + ButtonAlignment widget.ButtonAlign CloseIntercept func() `json:"-"` OnDragged, OnResized func(*fyne.DragEvent) `json:"-"` OnMinimized, OnMaximized, OnTappedBar, OnTappedIcon func() `json:"-"` @@ -47,7 +44,7 @@ func NewInnerWindow(title string, content fyne.CanvasObject) *InnerWindow { w := &InnerWindow{ title: title, content: NewPadded(content), - ButtonAlignment: InnerWindowButtonAlignDefault, + ButtonAlignment: widget.ButtonAlignCenter, } w.ExtendBaseWidget(w) return w @@ -76,6 +73,7 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { w.Close() } }} + close.Resize(fyne.NewSize(15, 15)) var icon fyne.CanvasObject if w.Icon != nil { @@ -95,9 +93,9 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { var buttons *fyne.Container var bar *fyne.Container - isLeftSide := w.ButtonAlignment == InnerWindowButtonAlignLeft || (w.ButtonAlignment == InnerWindowButtonAlignDefault && runtime.GOOS == "darwin") + isLeading := w.ButtonAlignment == widget.ButtonAlignLeading || (w.ButtonAlignment == widget.ButtonAlignCenter && runtime.GOOS == "darwin") - if isLeftSide { + if isLeading { // Left side (darwin default or explicit left alignment) buttons = NewHBox(close, min, max) bar = NewBorder(nil, nil, buttons, icon, title) @@ -134,6 +132,9 @@ func (w *InnerWindow) SetPadded(pad bool) { w.content.Refresh() } +// Title returns the current title of the window +// +// Since: 2.6 func (w *InnerWindow) Title() string { return w.title } diff --git a/container/innerwindow_test.go b/container/innerwindow_test.go index 61c3ef25d9..27cac21c67 100644 --- a/container/innerwindow_test.go +++ b/container/innerwindow_test.go @@ -11,9 +11,57 @@ import ( "github.com/stretchr/testify/assert" ) +func TestInnerWindow_Title(t *testing.T) { + w := NewInnerWindow("Thing", widget.NewLabel("Content")) + w.SetTitle("New Title 123") + assert.Equal(t, "New Title 123", w.Title()) +} + +func TestInnerWindowIcon_Tap_Left(t *testing.T) { + w := NewInnerWindow("Thing", widget.NewLabel("Content")) + w.Icon = theme.GridIcon() + + var testValue bool + w.OnTappedIcon = func() { + testValue = true + } + w.ButtonAlignment = widget.ButtonAlignLeading + + outer := test.NewTempWindow(t, w) + outer.SetPadded(false) + outer.Resize(w.MinSize()) + assert.True(t, w.Visible()) + + iconPos := fyne.NewPos(w.Size().Width-10, 10) + test.TapCanvas(outer.Canvas(), iconPos) + assert.True(t, testValue) + +} + +func TestInnerWindowIcon_Tap_Right(t *testing.T) { + w := NewInnerWindow("Thing", widget.NewLabel("Content")) + w.Icon = theme.GridIcon() + + var testValue bool + w.OnTappedIcon = func() { + testValue = true + } + w.ButtonAlignment = widget.ButtonAlignTrailing + + outer := test.NewTempWindow(t, w) + outer.SetPadded(false) + outer.Resize(w.MinSize()) + assert.True(t, w.Visible()) + + iconPos := fyne.NewPos(10, 10) + test.TapCanvas(outer.Canvas(), iconPos) + assert.True(t, testValue) + +} + func TestInnerWindow_Close_Left(t *testing.T) { w := NewInnerWindow("Thing", widget.NewLabel("Content")) - w.ButtonAlignment = InnerWindowButtonAlignLeft + w.ButtonAlignment = widget.ButtonAlignLeading outer := test.NewTempWindow(t, w) outer.SetPadded(false) outer.Resize(w.MinSize()) @@ -38,7 +86,7 @@ func TestInnerWindow_Close_Left(t *testing.T) { func TestInnerWindow_Close_Right(t *testing.T) { w := NewInnerWindow("Thing", widget.NewLabel("Content")) - w.ButtonAlignment = InnerWindowButtonAlignRight + w.ButtonAlignment = widget.ButtonAlignTrailing outer := test.NewTempWindow(t, w) outer.SetPadded(false) outer.Resize(w.MinSize()) From e01345354ba24f6c0324e6e9078c4135ac83256e Mon Sep 17 00:00:00 2001 From: Roffe Date: Tue, 10 Dec 2024 12:47:36 +0100 Subject: [PATCH 6/9] tidy --- container/innerwindow_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/container/innerwindow_test.go b/container/innerwindow_test.go index 27cac21c67..30d41d57d8 100644 --- a/container/innerwindow_test.go +++ b/container/innerwindow_test.go @@ -35,7 +35,6 @@ func TestInnerWindowIcon_Tap_Left(t *testing.T) { iconPos := fyne.NewPos(w.Size().Width-10, 10) test.TapCanvas(outer.Canvas(), iconPos) assert.True(t, testValue) - } func TestInnerWindowIcon_Tap_Right(t *testing.T) { @@ -56,7 +55,6 @@ func TestInnerWindowIcon_Tap_Right(t *testing.T) { iconPos := fyne.NewPos(10, 10) test.TapCanvas(outer.Canvas(), iconPos) assert.True(t, testValue) - } func TestInnerWindow_Close_Left(t *testing.T) { From b6be5d147d0a32d98dd274b2b49678a272b9008d Mon Sep 17 00:00:00 2001 From: Roffe Date: Tue, 10 Dec 2024 15:16:59 +0100 Subject: [PATCH 7/9] remove code i forgot in there --- container/innerwindow.go | 1 - 1 file changed, 1 deletion(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index 187b8cb65a..bbd21bfb3c 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -73,7 +73,6 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { w.Close() } }} - close.Resize(fyne.NewSize(15, 15)) var icon fyne.CanvasObject if w.Icon != nil { From 2f9b74b61fb4077f8cc7179f9faa592d1ee32c1c Mon Sep 17 00:00:00 2001 From: Roffe Date: Tue, 10 Dec 2024 17:14:43 +0100 Subject: [PATCH 8/9] last changes --- container/innerwindow.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index bbd21bfb3c..af3adbb80a 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -22,8 +22,8 @@ type InnerWindow struct { // ButtonAlignment specifies where the window buttons (close, minimize, maximize) should be placed. // The default is widget.ButtonAlignCenter which will auto select based on the OS. - // - On Windows and Linux this will be `widget.ButtonAlignTrailing` // - On Darwin this will be `widget.ButtonAlignLeading` + // - On all other OS this will be `widget.ButtonAlignTrailing` // // Since: 2.6 ButtonAlignment widget.ButtonAlign @@ -42,9 +42,8 @@ type InnerWindow struct { // Since: 2.5 func NewInnerWindow(title string, content fyne.CanvasObject) *InnerWindow { w := &InnerWindow{ - title: title, - content: NewPadded(content), - ButtonAlignment: widget.ButtonAlignCenter, + title: title, + content: NewPadded(content), } w.ExtendBaseWidget(w) return w From b9ca162d2b134daa0a27d26b83e299622aec3846 Mon Sep 17 00:00:00 2001 From: Roffe Date: Wed, 11 Dec 2024 17:35:52 +0100 Subject: [PATCH 9/9] add a dot and move the close resource down a little --- container/innerwindow.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/container/innerwindow.go b/container/innerwindow.go index af3adbb80a..b541d70c31 100644 --- a/container/innerwindow.go +++ b/container/innerwindow.go @@ -65,14 +65,6 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { max.Disable() } - close := &widget.Button{Icon: theme.WindowCloseIcon(), Importance: widget.DangerImportance, OnTapped: func() { - if f := w.CloseIntercept; f != nil { - f() - } else { - w.Close() - } - }} - var icon fyne.CanvasObject if w.Icon != nil { icon = &widget.Button{Icon: w.Icon, Importance: widget.LowImportance, OnTapped: func() { @@ -88,11 +80,18 @@ func (w *InnerWindow) CreateRenderer() fyne.WidgetRenderer { title := newDraggableLabel(w.title, w) title.Truncation = fyne.TextTruncateEllipsis - var buttons *fyne.Container - var bar *fyne.Container + close := &widget.Button{Icon: theme.WindowCloseIcon(), Importance: widget.DangerImportance, OnTapped: func() { + if f := w.CloseIntercept; f != nil { + f() + } else { + w.Close() + } + }} isLeading := w.ButtonAlignment == widget.ButtonAlignLeading || (w.ButtonAlignment == widget.ButtonAlignCenter && runtime.GOOS == "darwin") + var buttons *fyne.Container + var bar *fyne.Container if isLeading { // Left side (darwin default or explicit left alignment) buttons = NewHBox(close, min, max) @@ -130,7 +129,7 @@ func (w *InnerWindow) SetPadded(pad bool) { w.content.Refresh() } -// Title returns the current title of the window +// Title returns the current title of the window. // // Since: 2.6 func (w *InnerWindow) Title() string {