refactor(ax): clarify default and named window creation
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
450d04411a
commit
78a2a33ed2
9 changed files with 77 additions and 105 deletions
|
|
@ -156,7 +156,6 @@ This document tracks the implementation of display server features that enable A
|
|||
|
||||
### Focus Management
|
||||
- [x] `window_focused` - Get currently focused window
|
||||
- [x] `focus_set` - Set focus to specific window (alias for window_focus)
|
||||
|
||||
### Event Subscriptions (WebSocket)
|
||||
- [x] `event_subscribe` - Subscribe to events (via WebSocket /events endpoint)
|
||||
|
|
@ -215,7 +214,7 @@ This document tracks the implementation of display server features that enable A
|
|||
- [x] WebSocket event subscriptions (/events endpoint)
|
||||
- [x] Real-time window tracking (focus, blur, move, resize, close, create)
|
||||
- [x] Theme change events
|
||||
- [x] focus_set, screen_get, screen_primary, screen_at_point, screen_for_window
|
||||
- [x] screen_get, screen_primary, screen_at_point, screen_for_window
|
||||
|
||||
### Phase 7 - Advanced Features (DONE)
|
||||
- [x] `window_background_colour` - Window transparency via RGBA alpha
|
||||
|
|
|
|||
|
|
@ -21,10 +21,8 @@
|
|||
|
||||
## Declarative Windows
|
||||
|
||||
Windows are created from a `window.Window` spec instead of a fluent option chain:
|
||||
Windows are created from a `window.Window` spec instead of a fluent option chain.
|
||||
|
||||
```go
|
||||
svc.OpenWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"})
|
||||
```
|
||||
Use `OpenWindow(window.Window{})` for the default app window, or `CreateWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"})` when you need a named window and the returned `WindowInfo`.
|
||||
|
||||
The same spec is used by `CreateWindow`, layout restore, tiling, snapping, and workflow presets.
|
||||
The same spec shape is used by layout restore, tiling, snapping, and workflow presets.
|
||||
|
|
|
|||
|
|
@ -588,7 +588,8 @@ func (s *Service) performWindowTask(operation string, task core.Task) (any, erro
|
|||
|
||||
// --- Window Management (delegates via IPC) ---
|
||||
|
||||
// OpenWindow creates a new window from a declarative Window spec.
|
||||
// OpenWindow opens a window using manager defaults.
|
||||
// Example: s.OpenWindow(window.Window{})
|
||||
func (s *Service) OpenWindow(spec window.Window) error {
|
||||
_, err := s.performWindowTask("display.OpenWindow", window.TaskOpenWindow{Window: spec})
|
||||
return err
|
||||
|
|
@ -746,7 +747,8 @@ func (s *Service) GetSavedWindowStates() map[string]window.WindowState {
|
|||
return out
|
||||
}
|
||||
|
||||
// CreateWindow creates a new named window from a declarative Window spec.
|
||||
// CreateWindow opens a named window and returns its info.
|
||||
// Example: s.CreateWindow(window.Window{Name: "editor", Title: "Editor", URL: "/#/editor"})
|
||||
func (s *Service) CreateWindow(spec window.Window) (*window.WindowInfo, error) {
|
||||
if spec.Name == "" {
|
||||
return nil, coreerr.E("display.CreateWindow", "window name is required", nil)
|
||||
|
|
@ -877,14 +879,12 @@ func ptr[T any](v T) *T { return &v }
|
|||
// --- Menu handler methods ---
|
||||
|
||||
func (s *Service) handleNewWorkspace() {
|
||||
_, _, _ = s.Core().PERFORM(window.TaskOpenWindow{
|
||||
Window: window.Window{
|
||||
Name: "workspace-new",
|
||||
Title: "New Workspace",
|
||||
URL: "/workspace/new",
|
||||
Width: 500,
|
||||
Height: 400,
|
||||
},
|
||||
_, _ = s.CreateWindow(window.Window{
|
||||
Name: "workspace-new",
|
||||
Title: "New Workspace",
|
||||
URL: "/workspace/new",
|
||||
Width: 500,
|
||||
Height: 400,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -901,14 +901,12 @@ func (s *Service) handleListWorkspaces() {
|
|||
}
|
||||
|
||||
func (s *Service) handleNewFile() {
|
||||
_, _, _ = s.Core().PERFORM(window.TaskOpenWindow{
|
||||
Window: window.Window{
|
||||
Name: "editor",
|
||||
Title: "New File - Editor",
|
||||
URL: "/#/developer/editor?new=true",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
},
|
||||
_, _ = s.CreateWindow(window.Window{
|
||||
Name: "editor",
|
||||
Title: "New File - Editor",
|
||||
URL: "/#/developer/editor?new=true",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -926,38 +924,32 @@ func (s *Service) handleOpenFile() {
|
|||
if !ok || len(paths) == 0 {
|
||||
return
|
||||
}
|
||||
_, _, _ = s.Core().PERFORM(window.TaskOpenWindow{
|
||||
Window: window.Window{
|
||||
Name: "editor",
|
||||
Title: paths[0] + " - Editor",
|
||||
URL: "/#/developer/editor?file=" + paths[0],
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
},
|
||||
_, _ = s.CreateWindow(window.Window{
|
||||
Name: "editor",
|
||||
Title: paths[0] + " - Editor",
|
||||
URL: "/#/developer/editor?file=" + paths[0],
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) handleSaveFile() { _ = s.Core().ACTION(ActionIDECommand{Command: "save"}) }
|
||||
func (s *Service) handleOpenEditor() {
|
||||
_, _, _ = s.Core().PERFORM(window.TaskOpenWindow{
|
||||
Window: window.Window{
|
||||
Name: "editor",
|
||||
Title: "Editor",
|
||||
URL: "/#/developer/editor",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
},
|
||||
_, _ = s.CreateWindow(window.Window{
|
||||
Name: "editor",
|
||||
Title: "Editor",
|
||||
URL: "/#/developer/editor",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
}
|
||||
func (s *Service) handleOpenTerminal() {
|
||||
_, _, _ = s.Core().PERFORM(window.TaskOpenWindow{
|
||||
Window: window.Window{
|
||||
Name: "terminal",
|
||||
Title: "Terminal",
|
||||
URL: "/#/developer/terminal",
|
||||
Width: 800,
|
||||
Height: 500,
|
||||
},
|
||||
_, _ = s.CreateWindow(window.Window{
|
||||
Name: "terminal",
|
||||
Title: "Terminal",
|
||||
URL: "/#/developer/terminal",
|
||||
Width: 800,
|
||||
Height: 500,
|
||||
})
|
||||
}
|
||||
func (s *Service) handleRun() { _ = s.Core().ACTION(ActionIDECommand{Command: "run"}) }
|
||||
|
|
|
|||
|
|
@ -163,33 +163,20 @@ func TestServiceConclave_Bad(t *testing.T) {
|
|||
|
||||
// --- IPC delegation tests (full conclave) ---
|
||||
|
||||
func TestOpenWindow_Good(t *testing.T) {
|
||||
func TestOpenWindow_Defaults_Good(t *testing.T) {
|
||||
c := newTestConclave(t)
|
||||
svc := core.MustServiceFor[*Service](c, "display")
|
||||
|
||||
t.Run("creates window with default spec", func(t *testing.T) {
|
||||
err := svc.OpenWindow(window.Window{})
|
||||
assert.NoError(t, err)
|
||||
err := svc.OpenWindow(window.Window{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify via IPC query
|
||||
infos := svc.ListWindowInfos()
|
||||
assert.GreaterOrEqual(t, len(infos), 1)
|
||||
})
|
||||
|
||||
t.Run("creates window with custom spec", func(t *testing.T) {
|
||||
err := svc.OpenWindow(window.Window{
|
||||
Name: "custom-window",
|
||||
Title: "Custom Title",
|
||||
Width: 640,
|
||||
Height: 480,
|
||||
URL: "/custom",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, _, _ := c.QUERY(window.QueryWindowByName{Name: "custom-window"})
|
||||
info := result.(*window.WindowInfo)
|
||||
assert.Equal(t, "custom-window", info.Name)
|
||||
})
|
||||
info, err := svc.GetWindowInfo("main")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, info)
|
||||
assert.Equal(t, "main", info.Name)
|
||||
assert.Equal(t, "Core", info.Title)
|
||||
assert.Greater(t, info.Width, 0)
|
||||
assert.Greater(t, info.Height, 0)
|
||||
}
|
||||
|
||||
func TestGetWindowInfo_Good(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ The `Service` struct is the main entry point for the display logic.
|
|||
- `OnStartup(ctx context.Context) error`: Loads config and registers IPC handlers.
|
||||
|
||||
- **Window Management:**
|
||||
- `OpenWindow(spec window.Window) error`: Opens a new window from a declarative spec.
|
||||
- `CreateWindow(spec window.Window) (*window.WindowInfo, error)`: Opens a window and returns its info.
|
||||
- `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.
|
||||
- `SetWindowPosition(name string, x, y int) error`
|
||||
|
|
@ -36,7 +36,7 @@ The `Service` struct is the main entry point for the display logic.
|
|||
Example:
|
||||
|
||||
```go
|
||||
svc.OpenWindow(window.Window{
|
||||
svc.CreateWindow(window.Window{
|
||||
Name: "editor",
|
||||
Title: "Editor",
|
||||
URL: "/#/editor",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ The project consists of two main parts:
|
|||
|
||||
### Display Service (`display`)
|
||||
The core service manages the application lifecycle and exposes declarative operations such as:
|
||||
- `OpenWindow(window.Window{Name: "editor", URL: "/#/editor"})`
|
||||
- `OpenWindow(window.Window{})`
|
||||
- `CreateWindow(window.Window{Name: "editor", URL: "/#/editor"})`
|
||||
- `SaveLayout("coding")`
|
||||
- `TileWindows(window.TileModeLeftRight, []string{"editor", "terminal"})`
|
||||
- `ApplyWorkflowLayout(window.WorkflowCoding)`
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// WindowState holds the persisted position/size of a window.
|
||||
// JSON tags match existing window_state.json format for backward compat.
|
||||
// JSON tags match the existing window_state.json format.
|
||||
type WindowState struct {
|
||||
X int `json:"x,omitempty"`
|
||||
Y int `json:"y,omitempty"`
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// Window is CoreGUI's own window descriptor — NOT a Wails type alias.
|
||||
// Window is CoreGUI's own window descriptor, not a Wails type alias.
|
||||
type Window struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
|
|
@ -84,13 +84,8 @@ func (m *Manager) SetDefaultHeight(height int) {
|
|||
}
|
||||
}
|
||||
|
||||
// Open creates a window from a declarative Window spec, for example:
|
||||
// m.Open(Window{Name: "editor", Title: "Editor", URL: "/"})
|
||||
func (m *Manager) Open(spec Window) (PlatformWindow, error) {
|
||||
return m.Create(spec)
|
||||
}
|
||||
|
||||
// Create creates a window from a Window descriptor.
|
||||
// Create opens a window from a declarative spec.
|
||||
// Example: m.Create(Window{Name: "editor", Title: "Editor", URL: "/"})
|
||||
func (m *Manager) Create(spec Window) (PlatformWindow, error) {
|
||||
w := spec
|
||||
if w.Name == "" {
|
||||
|
|
|
|||
|
|
@ -45,18 +45,18 @@ func newTestManager() (*Manager, *mockPlatform) {
|
|||
return m, p
|
||||
}
|
||||
|
||||
func TestManager_Open_Good(t *testing.T) {
|
||||
func TestManager_Create_Good(t *testing.T) {
|
||||
m, p := newTestManager()
|
||||
pw, err := m.Open(Window{Name: "test", Title: "Test", URL: "/test", Width: 800, Height: 600})
|
||||
pw, err := m.Create(Window{Name: "test", Title: "Test", URL: "/test", Width: 800, Height: 600})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, pw)
|
||||
assert.Equal(t, "test", pw.Name())
|
||||
assert.Len(t, p.windows, 1)
|
||||
}
|
||||
|
||||
func TestManager_Open_Defaults_Good(t *testing.T) {
|
||||
func TestManager_Create_Defaults_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
pw, err := m.Open(Window{})
|
||||
pw, err := m.Create(Window{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "main", pw.Name())
|
||||
w, h := pw.Size()
|
||||
|
|
@ -64,12 +64,12 @@ func TestManager_Open_Defaults_Good(t *testing.T) {
|
|||
assert.Equal(t, 800, h)
|
||||
}
|
||||
|
||||
func TestManager_Open_CustomDefaults_Good(t *testing.T) {
|
||||
func TestManager_Create_CustomDefaults_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
m.SetDefaultWidth(1440)
|
||||
m.SetDefaultHeight(900)
|
||||
|
||||
pw, err := m.Open(Window{})
|
||||
pw, err := m.Create(Window{})
|
||||
require.NoError(t, err)
|
||||
|
||||
w, h := pw.Size()
|
||||
|
|
@ -79,7 +79,7 @@ func TestManager_Open_CustomDefaults_Good(t *testing.T) {
|
|||
|
||||
func TestManager_Get_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "findme"})
|
||||
_, _ = m.Create(Window{Name: "findme"})
|
||||
pw, ok := m.Get("findme")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "findme", pw.Name())
|
||||
|
|
@ -93,8 +93,8 @@ func TestManager_Get_Bad(t *testing.T) {
|
|||
|
||||
func TestManager_List_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "a"})
|
||||
_, _ = m.Open(Window{Name: "b"})
|
||||
_, _ = m.Create(Window{Name: "a"})
|
||||
_, _ = m.Create(Window{Name: "b"})
|
||||
names := m.List()
|
||||
assert.Len(t, names, 2)
|
||||
assert.Contains(t, names, "a")
|
||||
|
|
@ -103,7 +103,7 @@ func TestManager_List_Good(t *testing.T) {
|
|||
|
||||
func TestManager_Remove_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "temp"})
|
||||
_, _ = m.Create(Window{Name: "temp"})
|
||||
m.Remove("temp")
|
||||
_, ok := m.Get("temp")
|
||||
assert.False(t, ok)
|
||||
|
|
@ -118,8 +118,8 @@ func TestTileMode_String_Good(t *testing.T) {
|
|||
|
||||
func TestManager_TileWindows_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "a", Width: 800, Height: 600})
|
||||
_, _ = m.Open(Window{Name: "b", Width: 800, Height: 600})
|
||||
_, _ = m.Create(Window{Name: "a", Width: 800, Height: 600})
|
||||
_, _ = m.Create(Window{Name: "b", Width: 800, Height: 600})
|
||||
err := m.TileWindows(TileModeLeftRight, []string{"a", "b"}, 1920, 1080)
|
||||
require.NoError(t, err)
|
||||
a, _ := m.Get("a")
|
||||
|
|
@ -138,7 +138,7 @@ func TestManager_TileWindows_Bad(t *testing.T) {
|
|||
|
||||
func TestManager_SnapWindow_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "snap", Width: 800, Height: 600})
|
||||
_, _ = m.Create(Window{Name: "snap", Width: 800, Height: 600})
|
||||
err := m.SnapWindow("snap", SnapLeft, 1920, 1080)
|
||||
require.NoError(t, err)
|
||||
w, _ := m.Get("snap")
|
||||
|
|
@ -150,8 +150,8 @@ func TestManager_SnapWindow_Good(t *testing.T) {
|
|||
|
||||
func TestManager_StackWindows_Good(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, _ = m.Open(Window{Name: "s1", Width: 800, Height: 600})
|
||||
_, _ = m.Open(Window{Name: "s2", Width: 800, Height: 600})
|
||||
_, _ = m.Create(Window{Name: "s1", Width: 800, Height: 600})
|
||||
_, _ = m.Create(Window{Name: "s2", Width: 800, Height: 600})
|
||||
err := m.StackWindows([]string{"s1", "s2"}, 30, 30)
|
||||
require.NoError(t, err)
|
||||
s2, _ := m.Get("s2")
|
||||
|
|
@ -192,7 +192,7 @@ func TestTileWindows_AllModes_Good(t *testing.T) {
|
|||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, err := m.Open(Window{Name: "win", Width: 800, Height: 600})
|
||||
_, err := m.Create(Window{Name: "win", Width: 800, Height: 600})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = m.TileWindows(tc.mode, []string{"win"}, screenW, screenH)
|
||||
|
|
@ -238,7 +238,7 @@ func TestSnapWindow_AllPositions_Good(t *testing.T) {
|
|||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, err := m.Open(Window{Name: "snap", Width: tc.initW, Height: tc.initH})
|
||||
_, err := m.Create(Window{Name: "snap", Width: tc.initW, Height: tc.initH})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = m.SnapWindow("snap", tc.pos, screenW, screenH)
|
||||
|
|
@ -261,7 +261,7 @@ func TestStackWindows_ThreeWindows_Good(t *testing.T) {
|
|||
m, _ := newTestManager()
|
||||
names := []string{"s1", "s2", "s3"}
|
||||
for _, name := range names {
|
||||
_, err := m.Open(Window{Name: name, Width: 800, Height: 600})
|
||||
_, err := m.Create(Window{Name: name, Width: 800, Height: 600})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -317,9 +317,9 @@ func TestApplyWorkflow_AllLayouts_Good(t *testing.T) {
|
|||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m, _ := newTestManager()
|
||||
_, err := m.Open(Window{Name: "editor", Width: 800, Height: 600})
|
||||
_, err := m.Create(Window{Name: "editor", Width: 800, Height: 600})
|
||||
require.NoError(t, err)
|
||||
_, err = m.Open(Window{Name: "terminal", Width: 800, Height: 600})
|
||||
_, err = m.Create(Window{Name: "terminal", Width: 800, Height: 600})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = m.ApplyWorkflow(tc.workflow, []string{"editor", "terminal"}, screenW, screenH)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue