feat(gui): bridge arrange-pair and find-space
Some checks failed
Security Scan / security (push) Failing after 42s
Test / test (push) Successful in 1m34s

This commit is contained in:
Virgil 2026-04-02 14:08:32 +00:00
parent a07fa49c20
commit 973217ae54
6 changed files with 104 additions and 3 deletions

View file

@ -615,6 +615,19 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) {
Workflow: workflow,
Windows: names,
})
case "window:arrange-pair":
first, e := wsRequire(msg.Data, "first")
if e != nil {
return nil, false, e
}
second, e := wsRequire(msg.Data, "second")
if e != nil {
return nil, false, e
}
result, handled, err = s.Core().PERFORM(window.TaskArrangePair{
First: first,
Second: second,
})
case "layout:suggest":
windowCount := 0
if count, ok := msg.Data["windowCount"].(float64); ok {
@ -639,6 +652,19 @@ func (s *Service) handleWSMessage(msg WSMessage) (any, bool, error) {
ScreenWidth: screenWidth,
ScreenHeight: screenHeight,
})
case "screen:find-space":
width := 0
if w, ok := msg.Data["width"].(float64); ok {
width = int(w)
}
height := 0
if h, ok := msg.Data["height"].(float64); ok {
height = int(h)
}
result, handled, err = s.Core().QUERY(window.QueryFindSpace{
Width: width,
Height: height,
})
case "screen:list":
result, handled, err = s.Core().QUERY(screen.QueryAll{})
case "screen:get":
@ -1422,6 +1448,32 @@ func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error {
return ws.Manager().ApplyWorkflow(workflow, ws.Manager().List(), screenWidth, screenHeight)
}
// ArrangeWindowPair places two windows side by side using the window manager's balanced split.
func (s *Service) ArrangeWindowPair(first, second string) error {
ws := s.windowService()
if ws == nil {
return fmt.Errorf("window service not available")
}
screenWidth, screenHeight := s.primaryScreenSize()
return ws.Manager().ArrangePair(first, second, screenWidth, screenHeight)
}
// FindSpace returns a free placement suggestion for a new window.
func (s *Service) FindSpace(width, height int) (window.SpaceInfo, error) {
ws := s.windowService()
if ws == nil {
return window.SpaceInfo{}, fmt.Errorf("window service not available")
}
screenWidth, screenHeight := s.primaryScreenSize()
if width <= 0 {
width = screenWidth / 2
}
if height <= 0 {
height = screenHeight / 2
}
return ws.Manager().FindSpace(screenWidth, screenHeight, width, height), nil
}
// --- Screen management ---
// GetScreens returns all known screens.

View file

@ -919,6 +919,36 @@ func TestHandleWSMessage_Extended_Good(t *testing.T) {
assert.True(t, handled)
})
t.Run("window arrange pair", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:arrange-pair",
Data: map[string]any{
"first": "editor",
"second": "assistant",
},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("screen find space", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "screen:find-space",
Data: map[string]any{
"width": float64(400),
"height": float64(300),
},
})
require.NoError(t, err)
assert.True(t, handled)
space, ok := result.(window.SpaceInfo)
require.True(t, ok)
assert.Equal(t, 2560, space.ScreenWidth)
assert.Equal(t, 1440, space.ScreenHeight)
assert.Equal(t, 400, space.Width)
assert.Equal(t, 300, space.Height)
})
t.Run("clipboard image read", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{Action: "clipboard:read-image"})
require.NoError(t, err)

View file

@ -36,6 +36,7 @@ type mockWindow struct {
maximised, focused bool
visible, alwaysOnTop bool
backgroundColor [4]uint8
devtoolsOpen bool
closed bool
eventHandlers []func(WindowEvent)
fileDropHandlers []func(paths []string, targetID string)
@ -62,8 +63,8 @@ func (w *mockWindow) Show() { w.visible = true }
func (w *mockWindow) Hide() { w.visible = false }
func (w *mockWindow) Fullscreen() {}
func (w *mockWindow) UnFullscreen() {}
func (w *mockWindow) OpenDevTools() {}
func (w *mockWindow) CloseDevTools() {}
func (w *mockWindow) OpenDevTools() { w.devtoolsOpen = true }
func (w *mockWindow) CloseDevTools() { w.devtoolsOpen = false }
func (w *mockWindow) OnWindowEvent(handler func(WindowEvent)) {
w.eventHandlers = append(w.eventHandlers, handler)
}

View file

@ -88,7 +88,7 @@ func (ww *wailsWindow) Hide() { ww.w.Hide() }
func (ww *wailsWindow) Fullscreen() { ww.w.Fullscreen() }
func (ww *wailsWindow) UnFullscreen() { ww.w.UnFullscreen() }
func (ww *wailsWindow) OpenDevTools() { ww.w.OpenDevTools() }
func (ww *wailsWindow) CloseDevTools() {}
func (ww *wailsWindow) CloseDevTools() { ww.w.CloseDevTools() }
func (ww *wailsWindow) OnWindowEvent(handler func(event WindowEvent)) {
name := ww.w.Name()

View file

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wailsapp/wails/v3/pkg/application"
)
func TestWindowDefaults(t *testing.T) {
@ -162,6 +163,21 @@ func TestManager_Remove_Good(t *testing.T) {
assert.False(t, ok)
}
func TestWailsWindow_DevToolsToggle_Good(t *testing.T) {
app := application.NewApp()
platform := NewWailsPlatform(app)
pw := platform.CreateWindow(PlatformWindowOptions{Name: "devtools"})
ww, ok := pw.(*wailsWindow)
require.True(t, ok)
ww.OpenDevTools()
assert.True(t, ww.w.DevToolsOpen())
ww.CloseDevTools()
assert.False(t, ww.w.DevToolsOpen())
}
// --- StateManager Tests ---
// newTestStateManager creates a clean StateManager with a temp dir for testing.

View file

@ -136,6 +136,8 @@ func (w *WebviewWindow) Hide() { w.visible = false }
func (w *WebviewWindow) Fullscreen() { w.fullscreen = true }
func (w *WebviewWindow) UnFullscreen() { w.fullscreen = false }
func (w *WebviewWindow) OpenDevTools() { w.devtoolsOpen = true }
func (w *WebviewWindow) CloseDevTools() { w.devtoolsOpen = false }
func (w *WebviewWindow) DevToolsOpen() bool { return w.devtoolsOpen }
func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
w.mu.Lock()