refactor(ax): make window bounds and state updates more declarative
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
5ce1d67f70
commit
dc53b04d2a
6 changed files with 130 additions and 162 deletions
|
|
@ -24,5 +24,6 @@
|
|||
Windows are created from a `window.Window` spec instead of a fluent option chain.
|
||||
|
||||
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`.
|
||||
Use `SetWindowBounds("editor", 100, 200, 1280, 720)` when you need to move and resize a window in one step.
|
||||
|
||||
The same spec shape is used by layout restore, tiling, snapping, and workflow presets.
|
||||
|
|
|
|||
|
|
@ -95,138 +95,92 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
|
|||
s.buildMenu()
|
||||
s.setupTray()
|
||||
case window.ActionWindowOpened:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowCreate, Window: m.Name,
|
||||
Data: map[string]any{"name": m.Name}})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowCreate, Window: m.Name,
|
||||
Data: map[string]any{"name": m.Name}})
|
||||
case window.ActionWindowClosed:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowClose, Window: m.Name,
|
||||
Data: map[string]any{"name": m.Name}})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowClose, Window: m.Name,
|
||||
Data: map[string]any{"name": m.Name}})
|
||||
case window.ActionWindowMoved:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowMove, Window: m.Name,
|
||||
Data: map[string]any{"x": m.X, "y": m.Y}})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowMove, Window: m.Name,
|
||||
Data: map[string]any{"x": m.X, "y": m.Y}})
|
||||
case window.ActionWindowResized:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowResize, Window: m.Name,
|
||||
Data: map[string]any{"width": m.Width, "height": m.Height}})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowResize, Window: m.Name,
|
||||
Data: map[string]any{"width": m.Width, "height": m.Height}})
|
||||
case window.ActionWindowFocused:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowFocus, Window: m.Name})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowFocus, Window: m.Name})
|
||||
case window.ActionWindowBlurred:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowBlur, Window: m.Name})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowBlur, Window: m.Name})
|
||||
case systray.ActionTrayClicked:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventTrayClick})
|
||||
}
|
||||
s.emit(Event{Type: EventTrayClick})
|
||||
case systray.ActionTrayMenuItemClicked:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventTrayMenuItemClick,
|
||||
Data: map[string]any{"actionId": m.ActionID}})
|
||||
}
|
||||
s.emit(Event{Type: EventTrayMenuItemClick,
|
||||
Data: map[string]any{"actionId": m.ActionID}})
|
||||
s.handleTrayAction(m.ActionID)
|
||||
case environment.ActionThemeChanged:
|
||||
if s.events != nil {
|
||||
theme := "light"
|
||||
if m.IsDark {
|
||||
theme = "dark"
|
||||
}
|
||||
s.events.Emit(Event{Type: EventThemeChange,
|
||||
Data: map[string]any{"isDark": m.IsDark, "theme": theme}})
|
||||
theme := "light"
|
||||
if m.IsDark {
|
||||
theme = "dark"
|
||||
}
|
||||
s.emit(Event{Type: EventThemeChange,
|
||||
Data: map[string]any{"isDark": m.IsDark, "theme": theme}})
|
||||
case notification.ActionNotificationClicked:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventNotificationClick,
|
||||
Data: map[string]any{"id": m.ID}})
|
||||
}
|
||||
s.emit(Event{Type: EventNotificationClick,
|
||||
Data: map[string]any{"id": m.ID}})
|
||||
case screen.ActionScreensChanged:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventScreenChange,
|
||||
Data: map[string]any{"screens": m.Screens}})
|
||||
}
|
||||
s.emit(Event{Type: EventScreenChange,
|
||||
Data: map[string]any{"screens": m.Screens}})
|
||||
case keybinding.ActionTriggered:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventKeybindingTriggered,
|
||||
Data: map[string]any{"accelerator": m.Accelerator}})
|
||||
}
|
||||
s.emit(Event{Type: EventKeybindingTriggered,
|
||||
Data: map[string]any{"accelerator": m.Accelerator}})
|
||||
case window.ActionFilesDropped:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWindowFileDrop, Window: m.Name,
|
||||
Data: map[string]any{"paths": m.Paths, "targetId": m.TargetID}})
|
||||
}
|
||||
s.emit(Event{Type: EventWindowFileDrop, Window: m.Name,
|
||||
Data: map[string]any{"paths": m.Paths, "targetId": m.TargetID}})
|
||||
case dock.ActionVisibilityChanged:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventDockVisibility,
|
||||
Data: map[string]any{"visible": m.Visible}})
|
||||
}
|
||||
s.emit(Event{Type: EventDockVisibility,
|
||||
Data: map[string]any{"visible": m.Visible}})
|
||||
case lifecycle.ActionApplicationStarted:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventAppStarted})
|
||||
}
|
||||
s.emit(Event{Type: EventAppStarted})
|
||||
case lifecycle.ActionOpenedWithFile:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventAppOpenedWithFile,
|
||||
Data: map[string]any{"path": m.Path}})
|
||||
}
|
||||
s.emit(Event{Type: EventAppOpenedWithFile,
|
||||
Data: map[string]any{"path": m.Path}})
|
||||
case lifecycle.ActionWillTerminate:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventAppWillTerminate})
|
||||
}
|
||||
s.emit(Event{Type: EventAppWillTerminate})
|
||||
case lifecycle.ActionDidBecomeActive:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventAppActive})
|
||||
}
|
||||
s.emit(Event{Type: EventAppActive})
|
||||
case lifecycle.ActionDidResignActive:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventAppInactive})
|
||||
}
|
||||
s.emit(Event{Type: EventAppInactive})
|
||||
case lifecycle.ActionPowerStatusChanged:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventSystemPowerChange})
|
||||
}
|
||||
s.emit(Event{Type: EventSystemPowerChange})
|
||||
case lifecycle.ActionSystemSuspend:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventSystemSuspend})
|
||||
}
|
||||
s.emit(Event{Type: EventSystemSuspend})
|
||||
case lifecycle.ActionSystemResume:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventSystemResume})
|
||||
}
|
||||
s.emit(Event{Type: EventSystemResume})
|
||||
case contextmenu.ActionItemClicked:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventContextMenuClick,
|
||||
Data: map[string]any{
|
||||
"menuName": m.MenuName,
|
||||
"actionId": m.ActionID,
|
||||
"data": m.Data,
|
||||
}})
|
||||
}
|
||||
s.emit(Event{Type: EventContextMenuClick,
|
||||
Data: map[string]any{
|
||||
"menuName": m.MenuName,
|
||||
"actionId": m.ActionID,
|
||||
"data": m.Data,
|
||||
}})
|
||||
case webview.ActionConsoleMessage:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWebviewConsole, Window: m.Window,
|
||||
Data: map[string]any{"message": m.Message}})
|
||||
}
|
||||
s.emit(Event{Type: EventWebviewConsole, Window: m.Window,
|
||||
Data: map[string]any{"message": m.Message}})
|
||||
case webview.ActionException:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventWebviewException, Window: m.Window,
|
||||
Data: map[string]any{"exception": m.Exception}})
|
||||
}
|
||||
s.emit(Event{Type: EventWebviewException, Window: m.Window,
|
||||
Data: map[string]any{"exception": m.Exception}})
|
||||
case ActionIDECommand:
|
||||
if s.events != nil {
|
||||
s.events.Emit(Event{Type: EventIDECommand,
|
||||
Data: map[string]any{"command": m.Command}})
|
||||
}
|
||||
s.emit(Event{Type: EventIDECommand,
|
||||
Data: map[string]any{"command": m.Command}})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) emit(event Event) {
|
||||
if s.events != nil {
|
||||
s.events.Emit(event)
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocketMessage represents a command received from a WebSocket client.
|
||||
type WebSocketMessage struct {
|
||||
Action string `json:"action"`
|
||||
|
|
@ -618,19 +572,21 @@ func (s *Service) ListWindowInfos() []window.WindowInfo {
|
|||
return list
|
||||
}
|
||||
|
||||
// SetWindowPosition moves a window via IPC.
|
||||
// Example: s.SetWindowPosition("editor", 100, 200)
|
||||
// Use SetWindowBounds when you are changing position and size together.
|
||||
func (s *Service) SetWindowPosition(name string, x, y int) error {
|
||||
_, err := s.performWindowTask("display.SetWindowPosition", window.TaskSetPosition{Name: name, X: x, Y: y})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetWindowSize resizes a window via IPC.
|
||||
// Example: s.SetWindowSize("editor", 1280, 720)
|
||||
// Use SetWindowBounds when you are changing position and size together.
|
||||
func (s *Service) SetWindowSize(name string, width, height int) error {
|
||||
_, err := s.performWindowTask("display.SetWindowSize", window.TaskSetSize{Name: name, Width: width, Height: height})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetWindowBounds sets both position and size of a window via IPC.
|
||||
// Example: s.SetWindowBounds("editor", 100, 200, 1280, 720)
|
||||
func (s *Service) SetWindowBounds(name string, x, y, width, height int) error {
|
||||
_, err := s.performWindowTask("display.SetWindowBounds", window.TaskSetBounds{
|
||||
Name: name, X: x, Y: y, Width: width, Height: height,
|
||||
|
|
@ -700,7 +656,7 @@ func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) error
|
|||
return err
|
||||
}
|
||||
|
||||
// GetFocusedWindow returns the name of the currently focused window.
|
||||
// Example: focused := s.GetFocusedWindow()
|
||||
func (s *Service) GetFocusedWindow() string {
|
||||
infos := s.ListWindowInfos()
|
||||
for _, info := range infos {
|
||||
|
|
@ -711,7 +667,7 @@ func (s *Service) GetFocusedWindow() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// GetWindowTitle returns the title of a window by name.
|
||||
// Example: title, err := s.GetWindowTitle("editor")
|
||||
func (s *Service) GetWindowTitle(name string) (string, error) {
|
||||
info, err := s.GetWindowInfo(name)
|
||||
if err != nil {
|
||||
|
|
@ -723,13 +679,13 @@ func (s *Service) GetWindowTitle(name string) (string, error) {
|
|||
return info.Title, nil
|
||||
}
|
||||
|
||||
// ResetWindowState clears saved window positions.
|
||||
// Example: s.ResetWindowState()
|
||||
func (s *Service) ResetWindowState() error {
|
||||
_, err := s.performWindowTask("display.ResetWindowState", window.TaskResetWindowState{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSavedWindowStates returns all saved window states.
|
||||
// Example: states := s.GetSavedWindowStates()
|
||||
func (s *Service) GetSavedWindowStates() map[string]window.WindowState {
|
||||
result, handled, _ := s.Core().QUERY(window.QuerySavedWindowStates{})
|
||||
if !handled {
|
||||
|
|
@ -767,13 +723,13 @@ func (s *Service) CreateWindow(spec window.Window) (*window.WindowInfo, error) {
|
|||
|
||||
// --- Layout delegation ---
|
||||
|
||||
// SaveLayout saves the current window arrangement as a named layout.
|
||||
// Example: s.SaveLayout("coding")
|
||||
func (s *Service) SaveLayout(name string) error {
|
||||
_, err := s.performWindowTask("display.SaveLayout", window.TaskSaveLayout{Name: name})
|
||||
return err
|
||||
}
|
||||
|
||||
// RestoreLayout applies a saved layout.
|
||||
// Example: s.RestoreLayout("coding")
|
||||
func (s *Service) RestoreLayout(name string) error {
|
||||
_, err := s.performWindowTask("display.RestoreLayout", window.TaskRestoreLayout{Name: name})
|
||||
return err
|
||||
|
|
@ -789,7 +745,7 @@ func (s *Service) ListLayouts() []window.LayoutInfo {
|
|||
return layouts
|
||||
}
|
||||
|
||||
// DeleteLayout removes a saved layout by name.
|
||||
// Example: s.DeleteLayout("coding")
|
||||
func (s *Service) DeleteLayout(name string) error {
|
||||
_, err := s.performWindowTask("display.DeleteLayout", window.TaskDeleteLayout{Name: name})
|
||||
return err
|
||||
|
|
@ -807,25 +763,25 @@ func (s *Service) GetLayout(name string) *window.Layout {
|
|||
|
||||
// --- Tiling/snapping delegation ---
|
||||
|
||||
// TileWindows arranges windows in a tiled layout.
|
||||
// Example: s.TileWindows(window.TileModeLeftRight, []string{"editor", "terminal"})
|
||||
func (s *Service) TileWindows(mode window.TileMode, windowNames []string) error {
|
||||
_, err := s.performWindowTask("display.TileWindows", window.TaskTileWindows{Mode: mode.String(), Windows: windowNames})
|
||||
return err
|
||||
}
|
||||
|
||||
// SnapWindow snaps a window to a screen edge or corner.
|
||||
// Example: s.SnapWindow("editor", window.SnapRight)
|
||||
func (s *Service) SnapWindow(name string, position window.SnapPosition) error {
|
||||
_, err := s.performWindowTask("display.SnapWindow", window.TaskSnapWindow{Name: name, Position: position.String()})
|
||||
return err
|
||||
}
|
||||
|
||||
// StackWindows arranges windows in a cascade pattern.
|
||||
// Example: s.StackWindows([]string{"editor", "terminal"}, 24, 24)
|
||||
func (s *Service) StackWindows(windowNames []string, offsetX, offsetY int) error {
|
||||
_, err := s.performWindowTask("display.StackWindows", window.TaskStackWindows{Windows: windowNames, OffsetX: offsetX, OffsetY: offsetY})
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyWorkflowLayout applies a predefined layout for a specific workflow.
|
||||
// Example: s.ApplyWorkflowLayout(window.WorkflowCoding)
|
||||
func (s *Service) ApplyWorkflowLayout(workflow window.WorkflowLayout) error {
|
||||
_, err := s.performWindowTask("display.ApplyWorkflowLayout", window.TaskApplyWorkflow{
|
||||
Workflow: workflow.String(),
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ The `Service` struct is the main entry point for the display logic.
|
|||
- `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.
|
||||
- `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`
|
||||
- `SetWindowBounds(name string, x, y, width, height int) error`
|
||||
- `MaximizeWindow(name string) error`
|
||||
- `MinimizeWindow(name string) error`
|
||||
- `RestoreWindow(name string) error`
|
||||
|
|
@ -43,6 +43,8 @@ svc.CreateWindow(window.Window{
|
|||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
svc.SetWindowBounds("editor", 100, 200, 1280, 720)
|
||||
```
|
||||
|
||||
## Subsystems
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ The project consists of two main parts:
|
|||
The core service manages the application lifecycle and exposes declarative operations such as:
|
||||
- `OpenWindow(window.Window{})`
|
||||
- `CreateWindow(window.Window{Name: "editor", URL: "/#/editor"})`
|
||||
- `SetWindowBounds("editor", 100, 200, 1280, 720)`
|
||||
- `SaveLayout("coding")`
|
||||
- `TileWindows(window.TileModeLeftRight, []string{"editor", "terminal"})`
|
||||
- `ApplyWorkflowLayout(window.WorkflowCoding)`
|
||||
|
|
|
|||
|
|
@ -26,17 +26,20 @@ type TaskOpenWindow struct {
|
|||
|
||||
type TaskCloseWindow struct{ Name string }
|
||||
|
||||
// Example: c.PERFORM(TaskSetPosition{Name: "editor", X: 100, Y: 200})
|
||||
type TaskSetPosition struct {
|
||||
Name string
|
||||
X, Y int
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskSetBounds{Name: "editor", X: 100, Y: 200, Width: 1280, Height: 720})
|
||||
type TaskSetBounds struct {
|
||||
Name string
|
||||
X, Y int
|
||||
Width, Height int
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskSetSize{Name: "editor", Width: 1280, Height: 720})
|
||||
type TaskSetSize struct {
|
||||
Name string
|
||||
Width, Height int
|
||||
|
|
@ -84,35 +87,44 @@ type QueryLayoutList struct{}
|
|||
|
||||
type QueryLayoutGet struct{ Name string }
|
||||
|
||||
// Example: c.PERFORM(TaskSaveLayout{Name: "coding"})
|
||||
type TaskSaveLayout struct{ Name string }
|
||||
|
||||
// Example: c.PERFORM(TaskRestoreLayout{Name: "coding"})
|
||||
type TaskRestoreLayout struct{ Name string }
|
||||
|
||||
// Example: c.PERFORM(TaskDeleteLayout{Name: "coding"})
|
||||
type TaskDeleteLayout struct{ Name string }
|
||||
|
||||
// Example: c.PERFORM(TaskResetWindowState{})
|
||||
type TaskResetWindowState struct{}
|
||||
|
||||
// Example: c.PERFORM(TaskTileWindows{Mode: "left-right", Windows: []string{"editor", "terminal"}})
|
||||
type TaskTileWindows struct {
|
||||
Mode string // "left-right", "grid", "left-half", "right-half", etc.
|
||||
Windows []string // window names; empty = all
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskStackWindows{Windows: []string{"editor", "terminal"}, OffsetX: 24, OffsetY: 24})
|
||||
type TaskStackWindows struct {
|
||||
Windows []string // window names; empty = all
|
||||
OffsetX int
|
||||
OffsetY int
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskSnapWindow{Name: "editor", Position: "right"})
|
||||
type TaskSnapWindow struct {
|
||||
Name string // window name
|
||||
Position string // "left", "right", "top", "bottom", "top-left", "top-right", "bottom-left", "bottom-right", "center"
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskApplyWorkflow{Workflow: "coding"})
|
||||
type TaskApplyWorkflow struct {
|
||||
Workflow string
|
||||
Windows []string // window names; empty = all
|
||||
}
|
||||
|
||||
// Example: c.PERFORM(TaskSaveConfig{Config: map[string]any{"default_width": 800}})
|
||||
type TaskSaveConfig struct{ Config map[string]any }
|
||||
|
||||
type ActionWindowOpened struct{ Name string }
|
||||
|
|
|
|||
|
|
@ -77,10 +77,7 @@ func (sm *StateManager) SetPath(path string) {
|
|||
return
|
||||
}
|
||||
sm.mu.Lock()
|
||||
if sm.saveTimer != nil {
|
||||
sm.saveTimer.Stop()
|
||||
sm.saveTimer = nil
|
||||
}
|
||||
sm.stopSaveTimerLocked()
|
||||
sm.statePath = path
|
||||
sm.states = make(map[string]WindowState)
|
||||
sm.mu.Unlock()
|
||||
|
|
@ -117,10 +114,27 @@ func (sm *StateManager) save() {
|
|||
}
|
||||
|
||||
func (sm *StateManager) scheduleSave() {
|
||||
sm.mu.Lock()
|
||||
sm.stopSaveTimerLocked()
|
||||
sm.saveTimer = time.AfterFunc(500*time.Millisecond, sm.save)
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
|
||||
func (sm *StateManager) stopSaveTimerLocked() {
|
||||
if sm.saveTimer != nil {
|
||||
sm.saveTimer.Stop()
|
||||
sm.saveTimer = nil
|
||||
}
|
||||
sm.saveTimer = time.AfterFunc(500*time.Millisecond, sm.save)
|
||||
}
|
||||
|
||||
func (sm *StateManager) updateState(name string, mutate func(*WindowState)) {
|
||||
sm.mu.Lock()
|
||||
state := sm.states[name]
|
||||
mutate(&state)
|
||||
state.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.states[name] = state
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
}
|
||||
|
||||
// GetState returns the saved state for a window name.
|
||||
|
|
@ -133,60 +147,42 @@ func (sm *StateManager) GetState(name string) (WindowState, bool) {
|
|||
|
||||
// SetState saves state for a window name (debounced disk write).
|
||||
func (sm *StateManager) SetState(name string, state WindowState) {
|
||||
state.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.mu.Lock()
|
||||
sm.states[name] = state
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
sm.updateState(name, func(current *WindowState) {
|
||||
*current = state
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePosition updates only the position fields.
|
||||
func (sm *StateManager) UpdatePosition(name string, x, y int) {
|
||||
sm.mu.Lock()
|
||||
s := sm.states[name]
|
||||
s.X = x
|
||||
s.Y = y
|
||||
s.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.states[name] = s
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
sm.updateState(name, func(state *WindowState) {
|
||||
state.X = x
|
||||
state.Y = y
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateSize updates only the size fields.
|
||||
func (sm *StateManager) UpdateSize(name string, width, height int) {
|
||||
sm.mu.Lock()
|
||||
s := sm.states[name]
|
||||
s.Width = width
|
||||
s.Height = height
|
||||
s.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.states[name] = s
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
sm.updateState(name, func(state *WindowState) {
|
||||
state.Width = width
|
||||
state.Height = height
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBounds updates position and size in one state write.
|
||||
func (sm *StateManager) UpdateBounds(name string, x, y, width, height int) {
|
||||
sm.mu.Lock()
|
||||
s := sm.states[name]
|
||||
s.X = x
|
||||
s.Y = y
|
||||
s.Width = width
|
||||
s.Height = height
|
||||
s.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.states[name] = s
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
sm.updateState(name, func(state *WindowState) {
|
||||
state.X = x
|
||||
state.Y = y
|
||||
state.Width = width
|
||||
state.Height = height
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMaximized updates the maximized flag.
|
||||
func (sm *StateManager) UpdateMaximized(name string, maximized bool) {
|
||||
sm.mu.Lock()
|
||||
s := sm.states[name]
|
||||
s.Maximized = maximized
|
||||
s.UpdatedAt = time.Now().UnixMilli()
|
||||
sm.states[name] = s
|
||||
sm.mu.Unlock()
|
||||
sm.scheduleSave()
|
||||
sm.updateState(name, func(state *WindowState) {
|
||||
state.Maximized = maximized
|
||||
})
|
||||
}
|
||||
|
||||
// CaptureState snapshots the current state from a PlatformWindow.
|
||||
|
|
@ -237,8 +233,8 @@ func (sm *StateManager) Clear() {
|
|||
|
||||
// ForceSync writes state to disk immediately.
|
||||
func (sm *StateManager) ForceSync() {
|
||||
if sm.saveTimer != nil {
|
||||
sm.saveTimer.Stop()
|
||||
}
|
||||
sm.mu.Lock()
|
||||
sm.stopSaveTimerLocked()
|
||||
sm.mu.Unlock()
|
||||
sm.save()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue