diff --git a/docs/framework/display.md b/docs/framework/display.md index 0784f57..1d586cb 100644 --- a/docs/framework/display.md +++ b/docs/framework/display.md @@ -46,23 +46,23 @@ Returns: ```go type WindowInfo struct { - Name string - Title string - X int - Y int - Width int - Height int - IsVisible bool - IsFocused bool - IsMaximized bool - IsMinimized bool + Name string + Title string + X int + Y int + Width int + Height int + Visible bool + Minimized bool + Maximized bool + Focused bool } ``` ### ListWindowInfos ```go -func (s *Service) ListWindowInfos() []*WindowInfo +func (s *Service) ListWindowInfos() []WindowInfo ``` ### Window Position & Size diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index 00d14fe..871c6e4 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -177,6 +177,8 @@ func TestOpenWindow_Defaults_Good(t *testing.T) { assert.Equal(t, "Core", info.Title) assert.Greater(t, info.Width, 0) assert.Greater(t, info.Height, 0) + assert.True(t, info.Visible) + assert.False(t, info.Minimized) } func TestGetWindowInfo_Good(t *testing.T) { @@ -279,6 +281,18 @@ func TestMaximizeWindow_Good(t *testing.T) { assert.True(t, info.Maximized) } +func TestMinimizeWindow_Good(t *testing.T) { + c := newTestConclave(t) + svc := core.MustServiceFor[*Service](c, "display") + _ = svc.OpenWindow(window.Window{Name: "min-win"}) + + err := svc.MinimizeWindow("min-win") + assert.NoError(t, err) + + info, _ := svc.GetWindowInfo("min-win") + assert.True(t, info.Minimized) +} + func TestRestoreWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") @@ -325,8 +339,16 @@ 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/display/docs/backend.md b/pkg/display/docs/backend.md index ccf5e91..372ec40 100644 --- a/pkg/display/docs/backend.md +++ b/pkg/display/docs/backend.md @@ -15,8 +15,8 @@ The `Service` struct is the main entry point for the display logic. - **Window Management:** - `OpenWindow(spec window.Window) error`: Opens the default window using manager defaults. - `CreateWindow(spec window.Window) (*window.WindowInfo, error)`: Opens a named window and returns its info. - - `GetWindowInfo(name string) (*window.WindowInfo, error)`: Queries a single window. - - `ListWindowInfos() []window.WindowInfo`: Queries all tracked windows. + - `GetWindowInfo(name string) (*window.WindowInfo, error)`: Queries a single window, including visibility, minimization, focus, and maximized state. + - `ListWindowInfos() []window.WindowInfo`: Queries all tracked windows with the same live state fields. - `SetWindowBounds(name string, x, y, width, height int) error` - preferred when position and size change together. - `SetWindowPosition(name string, x, y int) error` - `SetWindowSize(name string, width, height int) error` diff --git a/pkg/window/messages.go b/pkg/window/messages.go index 82165d2..5ce7b38 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -7,6 +7,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 a12f5e6..2ba1138 100644 --- a/pkg/window/mock_platform.go +++ b/pkg/window/mock_platform.go @@ -15,6 +15,7 @@ func (m *MockPlatform) CreateWindow(options PlatformWindowOptions) PlatformWindo name: options.Name, title: options.Title, url: options.URL, width: options.Width, height: options.Height, x: options.X, y: options.Y, + visible: !options.Hidden, } m.Windows = append(m.Windows, w) return w @@ -31,7 +32,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 backgroundColour [4]uint8 closed bool @@ -44,6 +46,8 @@ 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) IsMaximised() bool { return w.maximised } +func (w *MockWindow) IsMinimised() bool { return w.minimised } +func (w *MockWindow) IsVisible() bool { return w.visible } func (w *MockWindow) IsFocused() bool { return w.focused } func (w *MockWindow) SetTitle(title string) { w.title = title } func (w *MockWindow) SetBounds(x, y, width, height int) { @@ -54,9 +58,9 @@ func (w *MockWindow) SetSize(width, height int) { w.width = width; w. func (w *MockWindow) SetBackgroundColour(r, g, b, a uint8) { w.backgroundColour = [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 } +func (w *MockWindow) Restore() { w.maximised = false; w.minimised = false } +func (w *MockWindow) Minimise() { w.maximised = false; w.minimised = true } func (w *MockWindow) Focus() { w.focused = true } func (w *MockWindow) Close() { w.closed = true diff --git a/pkg/window/mock_test.go b/pkg/window/mock_test.go index 6bd3186..1559548 100644 --- a/pkg/window/mock_test.go +++ b/pkg/window/mock_test.go @@ -13,6 +13,7 @@ func (m *mockPlatform) CreateWindow(options PlatformWindowOptions) PlatformWindo name: options.Name, title: options.Title, url: options.URL, width: options.Width, height: options.Height, x: options.X, y: options.Y, + visible: !options.Hidden, } m.windows = append(m.windows, w) return w @@ -29,11 +30,11 @@ 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 backgroundColour [4]uint8 closed bool - minimised bool fullscreened bool eventHandlers []func(WindowEvent) fileDropHandlers []func(paths []string, targetID string) @@ -44,6 +45,8 @@ 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) IsMaximised() bool { return w.maximised } +func (w *mockWindow) IsMinimised() bool { return w.minimised } +func (w *mockWindow) IsVisible() bool { return w.visible } func (w *mockWindow) IsFocused() bool { return w.focused } func (w *mockWindow) SetTitle(title string) { w.title = title } func (w *mockWindow) SetBounds(x, y, width, height int) { @@ -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.backgroundColour = [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() { w.minimised = true } +func (w *mockWindow) Maximise() { w.maximised = true; w.minimised = false } +func (w *mockWindow) Restore() { w.maximised = false; w.minimised = false } +func (w *mockWindow) Minimise() { w.maximised = false; w.minimised = true } func (w *mockWindow) Focus() { w.focused = true } func (w *mockWindow) Close() { w.closed = true diff --git a/pkg/window/platform.go b/pkg/window/platform.go index 9974a53..1e54a26 100644 --- a/pkg/window/platform.go +++ b/pkg/window/platform.go @@ -34,6 +34,8 @@ type PlatformWindow interface { Position() (int, int) Size() (int, int) IsMaximised() bool + IsMinimised() bool + IsVisible() bool IsFocused() bool // Mutations diff --git a/pkg/window/service.go b/pkg/window/service.go index c777ddf..9a5c662 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -90,7 +90,14 @@ func (s *Service) queryWindowList() []WindowInfo { x, y := platformWindow.Position() width, height := platformWindow.Size() result = append(result, WindowInfo{ - Name: name, Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height, + Name: name, + Title: platformWindow.Title(), + X: x, + Y: y, + Width: width, + Height: height, + Visible: platformWindow.IsVisible(), + Minimized: platformWindow.IsMinimised(), Maximized: platformWindow.IsMaximised(), Focused: platformWindow.IsFocused(), }) @@ -107,7 +114,14 @@ func (s *Service) queryWindowByName(name string) *WindowInfo { x, y := platformWindow.Position() width, height := platformWindow.Size() return &WindowInfo{ - Name: name, Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height, + Name: name, + Title: platformWindow.Title(), + X: x, + Y: y, + Width: width, + Height: height, + Visible: platformWindow.IsVisible(), + Minimized: platformWindow.IsMinimised(), Maximized: platformWindow.IsMaximised(), Focused: platformWindow.IsFocused(), } @@ -219,7 +233,18 @@ func (s *Service) taskOpenWindow(t TaskOpenWindow) (any, bool, error) { } x, y := platformWindow.Position() width, height := platformWindow.Size() - info := WindowInfo{Name: platformWindow.Name(), Title: platformWindow.Title(), X: x, Y: y, Width: width, Height: height} + info := WindowInfo{ + Name: platformWindow.Name(), + Title: platformWindow.Title(), + X: x, + Y: y, + Width: width, + Height: height, + Visible: platformWindow.IsVisible(), + Minimized: platformWindow.IsMinimised(), + Maximized: platformWindow.IsMaximised(), + Focused: platformWindow.IsFocused(), + } // Attach platform event listeners that convert to IPC actions s.trackWindow(platformWindow) diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index 1bf9007..6239b58 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -39,6 +39,8 @@ func TestTaskOpenWindow_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 TestTaskOpenWindow_Declarative_Good(t *testing.T) { @@ -322,6 +324,11 @@ func TestTaskMinimize_Good(t *testing.T) { require.True(t, ok) mw := pw.(*mockWindow) assert.True(t, mw.minimised) + + result, _, err := c.QUERY(QueryWindowByName{Name: "test"}) + require.NoError(t, err) + info := result.(*WindowInfo) + assert.True(t, info.Minimized) } func TestTaskMinimize_Bad(t *testing.T) { @@ -470,11 +477,21 @@ func TestTaskSetVisibility_Good(t *testing.T) { mw := pw.(*mockWindow) assert.True(t, mw.visible) + result, _, err := c.QUERY(QueryWindowByName{Name: "test"}) + require.NoError(t, err) + info := result.(*WindowInfo) + assert.True(t, info.Visible) + // Now hide it _, handled, err = c.PERFORM(TaskSetVisibility{Name: "test", Visible: false}) require.NoError(t, err) assert.True(t, handled) assert.False(t, mw.visible) + + result, _, err = c.QUERY(QueryWindowByName{Name: "test"}) + require.NoError(t, err) + info = result.(*WindowInfo) + assert.False(t, info.Visible) } func TestTaskSetVisibility_Bad(t *testing.T) { diff --git a/pkg/window/wails.go b/pkg/window/wails.go index 01a6aa4..2e90500 100644 --- a/pkg/window/wails.go +++ b/pkg/window/wails.go @@ -63,6 +63,8 @@ func (windowHandle *wailsWindow) Title() string { return windowHandle.tit func (windowHandle *wailsWindow) Position() (int, int) { return windowHandle.w.Position() } func (windowHandle *wailsWindow) Size() (int, int) { return windowHandle.w.Size() } func (windowHandle *wailsWindow) IsMaximised() bool { return windowHandle.w.IsMaximised() } +func (windowHandle *wailsWindow) IsMinimised() bool { return windowHandle.w.IsMinimised() } +func (windowHandle *wailsWindow) IsVisible() bool { return windowHandle.w.IsVisible() } func (windowHandle *wailsWindow) IsFocused() bool { return windowHandle.w.IsFocused() } func (windowHandle *wailsWindow) SetTitle(title string) { windowHandle.title = title diff --git a/stubs/wails/pkg/application/application.go b/stubs/wails/pkg/application/application.go index 211611c..670ceb2 100644 --- a/stubs/wails/pkg/application/application.go +++ b/stubs/wails/pkg/application/application.go @@ -189,6 +189,7 @@ type WebviewWindow struct { x, y int width, height int maximised bool + minimised bool focused bool visible bool alwaysOnTop bool @@ -232,6 +233,16 @@ func (w *WebviewWindow) IsMaximised() bool { defer w.mu.RUnlock() return w.maximised } +func (w *WebviewWindow) IsMinimised() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.minimised +} +func (w *WebviewWindow) IsVisible() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.visible +} func (w *WebviewWindow) IsFocused() bool { w.mu.RLock() defer w.mu.RUnlock() @@ -269,17 +280,24 @@ func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) { func (w *WebviewWindow) Maximise() { w.mu.Lock() w.maximised = true + w.minimised = false w.mu.Unlock() } func (w *WebviewWindow) Restore() { w.mu.Lock() w.maximised = false + w.minimised = false w.fullscreen = false w.mu.Unlock() } -func (w *WebviewWindow) Minimise() {} +func (w *WebviewWindow) Minimise() { + w.mu.Lock() + w.maximised = false + w.minimised = true + w.mu.Unlock() +} func (w *WebviewWindow) Focus() { w.mu.Lock()