diff --git a/pkg/window/messages.go b/pkg/window/messages.go index 755f4ee..fc91a8f 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -58,7 +58,8 @@ type TaskOpenWindow struct { Opts []WindowOption } -// TaskCloseWindow closes a window. Handler persists state BEFORE emitting ActionWindowClosed. +// TaskCloseWindow closes a window after persisting state. +// Platform close events emit ActionWindowClosed through the tracked window handler. type TaskCloseWindow struct{ Name string } // TaskSetPosition moves a window. diff --git a/pkg/window/mock_platform.go b/pkg/window/mock_platform.go index 293faf6..f257988 100644 --- a/pkg/window/mock_platform.go +++ b/pkg/window/mock_platform.go @@ -63,7 +63,10 @@ func (w *MockWindow) Maximise() { w.maximised = 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) Close() { + w.closed = true + w.emit(WindowEvent{Type: "close", Name: w.name}) +} func (w *MockWindow) Show() { w.visible = true } func (w *MockWindow) Hide() { w.visible = false } func (w *MockWindow) Fullscreen() {} @@ -76,3 +79,15 @@ func (w *MockWindow) OnWindowEvent(handler func(WindowEvent)) { func (w *MockWindow) OnFileDrop(handler func(paths []string, targetID string)) { w.fileDropHandlers = append(w.fileDropHandlers, handler) } + +func (w *MockWindow) emit(e WindowEvent) { + for _, h := range w.eventHandlers { + h(e) + } +} + +func (w *MockWindow) emitFileDrop(paths []string, targetID string) { + for _, h := range w.fileDropHandlers { + h(paths, targetID) + } +} diff --git a/pkg/window/mock_test.go b/pkg/window/mock_test.go index 452a1b9..4e0edad 100644 --- a/pkg/window/mock_test.go +++ b/pkg/window/mock_test.go @@ -63,7 +63,10 @@ func (w *mockWindow) Maximise() { w.maximised = 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) Close() { + w.closed = true + w.emit(WindowEvent{Type: "close", Name: w.name}) +} func (w *mockWindow) Show() { w.visible = true } func (w *mockWindow) Hide() { w.visible = false } func (w *mockWindow) Fullscreen() {} diff --git a/pkg/window/service.go b/pkg/window/service.go index c2e53f3..e8ae9e4 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -274,7 +274,6 @@ func (s *Service) taskCloseWindow(name string) error { s.manager.State().CaptureState(pw) pw.Close() s.manager.Remove(name) - _ = s.Core().ACTION(ActionWindowClosed{Name: name}) return nil } @@ -454,8 +453,8 @@ func (s *Service) taskTileWindows(mode string, names []string) error { if len(names) == 0 { names = s.manager.List() } - // Default screen size — callers can query screen_primary for actual values. - return s.manager.TileWindows(tm, names, 1920, 1080) + screenW, screenH := s.primaryScreenSize() + return s.manager.TileWindows(tm, names, screenW, screenH) } var snapPosMap = map[string]SnapPosition{ diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index f14ce9a..c4987a0 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -6,6 +6,7 @@ import ( "testing" "forge.lthn.ai/core/go/pkg/core" + "forge.lthn.ai/core/gui/pkg/screen" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,6 +23,41 @@ func newTestWindowService(t *testing.T) (*Service, *core.Core) { return svc, c } +type testScreenPlatform struct { + screens []screen.Screen +} + +func (p *testScreenPlatform) GetAll() []screen.Screen { return p.screens } + +func (p *testScreenPlatform) GetPrimary() *screen.Screen { + for i := range p.screens { + if p.screens[i].IsPrimary { + return &p.screens[i] + } + } + return nil +} + +func newTestWindowServiceWithScreen(t *testing.T) (*Service, *core.Core) { + t.Helper() + c, err := core.New( + core.WithService(Register(newMockPlatform())), + core.WithService(screen.Register(&testScreenPlatform{ + screens: []screen.Screen{{ + ID: "primary", Name: "Primary", IsPrimary: true, + Size: screen.Size{Width: 2560, Height: 1440}, + Bounds: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440}, + WorkArea: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440}, + }}, + })), + core.WithServiceLock(), + ) + require.NoError(t, err) + require.NoError(t, c.ServiceStartup(context.Background(), nil)) + svc := core.MustServiceFor[*Service](c, "window") + return svc, c +} + func TestRegister_Good(t *testing.T) { svc, _ := newTestWindowService(t) assert.NotNil(t, svc) @@ -218,6 +254,26 @@ func TestTaskSetBackgroundColour_Good(t *testing.T) { assert.Equal(t, [4]uint8{10, 20, 30, 40}, pw.(*mockWindow).backgroundColor) } +func TestTaskTileWindows_UsesPrimaryScreenSize(t *testing.T) { + _, c := newTestWindowServiceWithScreen(t) + _, _, _ = c.PERFORM(TaskOpenWindow{Opts: []WindowOption{WithName("left")}}) + _, _, _ = c.PERFORM(TaskOpenWindow{Opts: []WindowOption{WithName("right")}}) + + _, handled, err := c.PERFORM(TaskTileWindows{Mode: "left-right", Windows: []string{"left", "right"}}) + require.NoError(t, err) + assert.True(t, handled) + + left, _, _ := c.QUERY(QueryWindowByName{Name: "left"}) + right, _, _ := c.QUERY(QueryWindowByName{Name: "right"}) + leftInfo := left.(*WindowInfo) + rightInfo := right.(*WindowInfo) + + assert.Equal(t, 1280, leftInfo.Width) + assert.Equal(t, 1280, rightInfo.Width) + assert.Equal(t, 0, leftInfo.X) + assert.Equal(t, 1280, rightInfo.X) +} + func TestTaskSetOpacity_Good(t *testing.T) { _, c := newTestWindowService(t) _, _, _ = c.PERFORM(TaskOpenWindow{Opts: []WindowOption{WithName("test")}})