From 77e03060ac6cdcebef68e1d210f41aabf19e68ae Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 14:30:42 +0000 Subject: [PATCH] feat(window): expose visibility and minimized state Co-Authored-By: Virgil --- pkg/display/display_test.go | 8 +++++++ pkg/window/messages.go | 2 ++ pkg/window/mock_platform.go | 11 +++++---- pkg/window/mock_test.go | 11 +++++---- pkg/window/platform.go | 2 ++ pkg/window/service.go | 22 +++++++++++++++-- pkg/window/service_test.go | 24 +++++++++++++++++++ pkg/window/wails.go | 2 ++ stubs/wails/v3/pkg/application/application.go | 9 ++++--- 9 files changed, 78 insertions(+), 13 deletions(-) diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index 344742a..3c2aa1d 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -426,6 +426,8 @@ func TestGetWindowInfo_Good(t *testing.T) { assert.Equal(t, 200, info.Y) assert.Equal(t, 800, info.Width) assert.Equal(t, 600, info.Height) + assert.True(t, info.Visible) + assert.False(t, info.Minimized) } func TestGetWindowInfo_Bad(t *testing.T) { @@ -559,9 +561,15 @@ func TestSetWindowVisibility_Good(t *testing.T) { err := svc.SetWindowVisibility("vis-win", false) assert.NoError(t, err) + info, err := svc.GetWindowInfo("vis-win") + require.NoError(t, err) + assert.False(t, info.Visible) err = svc.SetWindowVisibility("vis-win", true) assert.NoError(t, err) + info, err = svc.GetWindowInfo("vis-win") + require.NoError(t, err) + assert.True(t, info.Visible) } func TestSetWindowAlwaysOnTop_Good(t *testing.T) { diff --git a/pkg/window/messages.go b/pkg/window/messages.go index a7fabd5..e2ed36c 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -8,6 +8,8 @@ type WindowInfo struct { Y int `json:"y"` Width int `json:"width"` Height int `json:"height"` + Visible bool `json:"visible"` + Minimized bool `json:"minimized"` Maximized bool `json:"maximized"` Focused bool `json:"focused"` } diff --git a/pkg/window/mock_platform.go b/pkg/window/mock_platform.go index d79bcc1..d031f9e 100644 --- a/pkg/window/mock_platform.go +++ b/pkg/window/mock_platform.go @@ -34,7 +34,8 @@ func (m *MockPlatform) GetWindows() []PlatformWindow { type MockWindow struct { name, title, url string width, height, x, y int - maximised, focused bool + maximised, minimised bool + focused bool visible, alwaysOnTop bool backgroundColor [4]uint8 closed bool @@ -46,6 +47,8 @@ func (w *MockWindow) Name() string { return w.name } func (w *MockWindow) Title() string { return w.title } func (w *MockWindow) Position() (int, int) { return w.x, w.y } func (w *MockWindow) Size() (int, int) { return w.width, w.height } +func (w *MockWindow) IsVisible() bool { return w.visible } +func (w *MockWindow) IsMinimised() bool { return w.minimised } func (w *MockWindow) IsMaximised() bool { return w.maximised } func (w *MockWindow) IsFocused() bool { return w.focused } func (w *MockWindow) SetTitle(title string) { w.title = title } @@ -54,9 +57,9 @@ func (w *MockWindow) SetSize(width, height int) { w.width = width; w. func (w *MockWindow) SetBackgroundColour(r, g, b, a uint8) { w.backgroundColor = [4]uint8{r, g, b, a} } func (w *MockWindow) SetVisibility(visible bool) { w.visible = visible } func (w *MockWindow) SetAlwaysOnTop(alwaysOnTop bool) { w.alwaysOnTop = alwaysOnTop } -func (w *MockWindow) Maximise() { w.maximised = true } -func (w *MockWindow) Restore() { w.maximised = false } -func (w *MockWindow) Minimise() {} +func (w *MockWindow) Maximise() { w.maximised = true; w.minimised = false; w.visible = true } +func (w *MockWindow) Restore() { w.maximised = false; w.minimised = false; w.visible = true } +func (w *MockWindow) Minimise() { w.minimised = true; w.maximised = false; w.visible = false } func (w *MockWindow) Focus() { w.focused = true } func (w *MockWindow) Close() { w.closed = true } func (w *MockWindow) Show() { w.visible = true } diff --git a/pkg/window/mock_test.go b/pkg/window/mock_test.go index caa7f9a..442578d 100644 --- a/pkg/window/mock_test.go +++ b/pkg/window/mock_test.go @@ -33,7 +33,8 @@ func (m *mockPlatform) GetWindows() []PlatformWindow { type mockWindow struct { name, title, url string width, height, x, y int - maximised, focused bool + maximised, minimised bool + focused bool visible, alwaysOnTop bool backgroundColor [4]uint8 devtoolsOpen bool @@ -46,6 +47,8 @@ func (w *mockWindow) Name() string { return w.name } func (w *mockWindow) Title() string { return w.title } func (w *mockWindow) Position() (int, int) { return w.x, w.y } func (w *mockWindow) Size() (int, int) { return w.width, w.height } +func (w *mockWindow) IsVisible() bool { return w.visible } +func (w *mockWindow) IsMinimised() bool { return w.minimised } func (w *mockWindow) IsMaximised() bool { return w.maximised } func (w *mockWindow) IsFocused() bool { return w.focused } func (w *mockWindow) SetTitle(title string) { w.title = title } @@ -54,9 +57,9 @@ func (w *mockWindow) SetSize(width, height int) { w.width = width; w. func (w *mockWindow) SetBackgroundColour(r, g, b, a uint8) { w.backgroundColor = [4]uint8{r, g, b, a} } func (w *mockWindow) SetVisibility(visible bool) { w.visible = visible } func (w *mockWindow) SetAlwaysOnTop(alwaysOnTop bool) { w.alwaysOnTop = alwaysOnTop } -func (w *mockWindow) Maximise() { w.maximised = true } -func (w *mockWindow) Restore() { w.maximised = false } -func (w *mockWindow) Minimise() {} +func (w *mockWindow) Maximise() { w.maximised = true; w.minimised = false; w.visible = true } +func (w *mockWindow) Restore() { w.maximised = false; w.minimised = false; w.visible = true } +func (w *mockWindow) Minimise() { w.minimised = true; w.maximised = false; w.visible = false } func (w *mockWindow) Focus() { w.focused = true } func (w *mockWindow) Close() { w.closed = true } func (w *mockWindow) Show() { w.visible = true } diff --git a/pkg/window/platform.go b/pkg/window/platform.go index 58943b0..ecc1533 100644 --- a/pkg/window/platform.go +++ b/pkg/window/platform.go @@ -33,6 +33,8 @@ type PlatformWindow interface { // Queries Position() (int, int) Size() (int, int) + IsVisible() bool + IsMinimised() bool IsMaximised() bool IsFocused() bool diff --git a/pkg/window/service.go b/pkg/window/service.go index 4d3f4d2..b98ab52 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -119,7 +119,14 @@ func (s *Service) queryWindowByName(name string) *WindowInfo { x, y := pw.Position() w, h := pw.Size() return &WindowInfo{ - Name: name, Title: pw.Title(), X: x, Y: y, Width: w, Height: h, + Name: name, + Title: pw.Title(), + X: x, + Y: y, + Width: w, + Height: h, + Visible: pw.IsVisible(), + Minimized: pw.IsMinimised(), Maximized: pw.IsMaximised(), Focused: pw.IsFocused(), } @@ -195,7 +202,18 @@ func (s *Service) taskOpenWindow(t TaskOpenWindow) (any, bool, error) { } x, y := pw.Position() w, h := pw.Size() - info := WindowInfo{Name: pw.Name(), Title: pw.Title(), X: x, Y: y, Width: w, Height: h} + info := WindowInfo{ + Name: pw.Name(), + Title: pw.Title(), + X: x, + Y: y, + Width: w, + Height: h, + Visible: pw.IsVisible(), + Minimized: pw.IsMinimised(), + Maximized: pw.IsMaximised(), + Focused: pw.IsFocused(), + } // Attach platform event listeners that convert to IPC actions s.trackWindow(pw) diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index 57e79a0..883e2a0 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -95,6 +95,8 @@ func TestQueryWindowByName_Good(t *testing.T) { assert.True(t, handled) info := result.(*WindowInfo) assert.Equal(t, "test", info.Name) + assert.True(t, info.Visible) + assert.False(t, info.Minimized) } func TestQueryWindowByName_Bad(t *testing.T) { @@ -153,6 +155,28 @@ func TestTaskSetSize_Good(t *testing.T) { assert.Equal(t, 600, info.Height) } +func TestTaskMinimiseAndVisibility_Good(t *testing.T) { + _, c := newTestWindowService(t) + _, _, _ = c.PERFORM(TaskOpenWindow{Opts: []WindowOption{WithName("test")}}) + + _, handled, err := c.PERFORM(TaskMinimise{Name: "test"}) + require.NoError(t, err) + assert.True(t, handled) + + result, _, _ := c.QUERY(QueryWindowByName{Name: "test"}) + info := result.(*WindowInfo) + assert.True(t, info.Minimized) + assert.False(t, info.Visible) + + _, handled, err = c.PERFORM(TaskSetVisibility{Name: "test", Visible: true}) + require.NoError(t, err) + assert.True(t, handled) + + result, _, _ = c.QUERY(QueryWindowByName{Name: "test"}) + info = result.(*WindowInfo) + assert.True(t, info.Visible) +} + func TestTaskSetAlwaysOnTop_Good(t *testing.T) { _, c := newTestWindowService(t) _, _, _ = c.PERFORM(TaskOpenWindow{Opts: []WindowOption{WithName("test")}}) diff --git a/pkg/window/wails.go b/pkg/window/wails.go index a6424f3..4a10646 100644 --- a/pkg/window/wails.go +++ b/pkg/window/wails.go @@ -62,6 +62,8 @@ func (ww *wailsWindow) Name() string { return ww.w.Name() } func (ww *wailsWindow) Title() string { return ww.title } func (ww *wailsWindow) Position() (int, int) { return ww.w.Position() } func (ww *wailsWindow) Size() (int, int) { return ww.w.Size() } +func (ww *wailsWindow) IsVisible() bool { return ww.w.IsVisible() } +func (ww *wailsWindow) IsMinimised() bool { return ww.w.IsMinimised() } func (ww *wailsWindow) IsMaximised() bool { return ww.w.IsMaximised() } func (ww *wailsWindow) IsFocused() bool { return ww.w.IsFocused() } func (ww *wailsWindow) SetTitle(title string) { ww.title = title; ww.w.SetTitle(title) } diff --git a/stubs/wails/v3/pkg/application/application.go b/stubs/wails/v3/pkg/application/application.go index c4ef368..c1969ae 100644 --- a/stubs/wails/v3/pkg/application/application.go +++ b/stubs/wails/v3/pkg/application/application.go @@ -89,6 +89,7 @@ type WebviewWindow struct { title string x, y int width, height int + minimised bool maximised bool focused bool visible bool @@ -116,6 +117,8 @@ func newWebviewWindow(opts WebviewWindowOptions) *WebviewWindow { func (w *WebviewWindow) Name() string { return w.opts.Name } func (w *WebviewWindow) Position() (int, int) { return w.x, w.y } func (w *WebviewWindow) Size() (int, int) { return w.width, w.height } +func (w *WebviewWindow) IsVisible() bool { return w.visible } +func (w *WebviewWindow) IsMinimised() bool { return w.minimised } func (w *WebviewWindow) IsMaximised() bool { return w.maximised } func (w *WebviewWindow) IsFocused() bool { return w.focused } func (w *WebviewWindow) SetTitle(title string) { w.title = title } @@ -126,9 +129,9 @@ func (w *WebviewWindow) SetSize(width, height int) { func (w *WebviewWindow) SetBackgroundColour(colour RGBA) {} func (w *WebviewWindow) SetVisibility(visible bool) { w.visible = visible } func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) { w.alwaysOnTop = alwaysOnTop } -func (w *WebviewWindow) Maximise() { w.maximised = true } -func (w *WebviewWindow) Restore() { w.maximised = false } -func (w *WebviewWindow) Minimise() {} +func (w *WebviewWindow) Maximise() { w.maximised = true; w.minimised = false; w.visible = true } +func (w *WebviewWindow) Restore() { w.maximised = false; w.minimised = false; w.visible = true } +func (w *WebviewWindow) Minimise() { w.minimised = true; w.maximised = false; w.visible = false } func (w *WebviewWindow) Focus() { w.focused = true } func (w *WebviewWindow) Close() {} func (w *WebviewWindow) Show() { w.visible = true }