diff --git a/internal/wails3 b/internal/wails3 new file mode 160000 index 0000000..bb4fbf9 --- /dev/null +++ b/internal/wails3 @@ -0,0 +1 @@ +Subproject commit bb4fbf95744fafe5acf84e143a419bfffc2159e6 diff --git a/stubs/wails/pkg/application/application.go b/stubs/wails/pkg/application/application.go index 211611c..60a86e5 100644 --- a/stubs/wails/pkg/application/application.go +++ b/stubs/wails/pkg/application/application.go @@ -2,12 +2,36 @@ package application import ( "sync" + "unsafe" "github.com/wailsapp/wails/v3/pkg/events" ) // Context mirrors the callback context type exposed by Wails. -type Context struct{} +// +// item.OnClick(func(ctx *Context) { openPrefs() }) +type Context struct { + clickedMenuItem *MenuItem + contextMenuData *ContextMenuData + checked bool +} + +func newContext() *Context { return &Context{} } + +func (ctx *Context) withClickedMenuItem(item *MenuItem) *Context { + ctx.clickedMenuItem = item + return ctx +} + +func (ctx *Context) withContextMenuData(data *ContextMenuData) *Context { + ctx.contextMenuData = data + return ctx +} + +func (ctx *Context) withChecked(checked bool) *Context { + ctx.checked = checked + return ctx +} // Logger is a minimal logger surface used by the GUI packages. type Logger struct{} @@ -15,90 +39,92 @@ type Logger struct{} func (l Logger) Info(message string, args ...any) {} // RGBA stores a colour with alpha. +// +// colour := NewRGBA(255, 128, 0, 255) // opaque orange type RGBA struct { Red, Green, Blue, Alpha uint8 } // NewRGBA constructs an RGBA value. +// +// colour := NewRGBA(255, 128, 0, 255) // opaque orange func NewRGBA(red, green, blue, alpha uint8) RGBA { return RGBA{Red: red, Green: green, Blue: blue, Alpha: alpha} } -// MenuRole identifies a platform menu role. -type MenuRole int - -const ( - AppMenu MenuRole = iota - FileMenu - EditMenu - ViewMenu - WindowMenu - HelpMenu -) - -// MenuItem is a minimal menu item implementation. -type MenuItem struct { - Label string - Accelerator string - Tooltip string - Checked bool - Enabled bool - onClick func(*Context) -} - -func (mi *MenuItem) SetAccelerator(accel string) { mi.Accelerator = accel } -func (mi *MenuItem) SetTooltip(text string) { mi.Tooltip = text } -func (mi *MenuItem) SetChecked(checked bool) { mi.Checked = checked } -func (mi *MenuItem) SetEnabled(enabled bool) { mi.Enabled = enabled } -func (mi *MenuItem) OnClick(fn func(*Context)) { mi.onClick = fn } - -// Menu is a minimal menu tree used by the GUI wrappers. +// Menu is a menu tree used by the GUI wrappers. +// +// menu := NewMenu() +// menu.Add("Save").SetAccelerator("CmdOrCtrl+S").OnClick(func(ctx *Context) { save() }) type Menu struct { + label string Items []*MenuItem } +// NewMenu creates a new, empty Menu. +// +// menu := NewMenu() func NewMenu() *Menu { return &Menu{} } +// Add appends a new text item with the given label. func (m *Menu) Add(label string) *MenuItem { - item := &MenuItem{Label: label, Enabled: true} + item := NewMenuItem(label) + item.disabled = false m.Items = append(m.Items, item) return item } +// AddSeparator appends a separator item. func (m *Menu) AddSeparator() { - m.Items = append(m.Items, &MenuItem{Label: "---"}) + m.Items = append(m.Items, NewMenuItemSeparator()) } +// AddSubmenu appends a submenu item and returns the child Menu. func (m *Menu) AddSubmenu(label string) *Menu { - submenu := &Menu{} - m.Items = append(m.Items, &MenuItem{Label: label}) - return submenu + item := NewSubMenuItem(label) + m.Items = append(m.Items, item) + return item.submenu } -func (m *Menu) AddRole(role MenuRole) { - m.Items = append(m.Items, &MenuItem{Label: role.String(), Enabled: true}) +// AddRole appends a platform-role item. +func (m *Menu) AddRole(role Role) { + m.Items = append(m.Items, NewRole(role)) } -func (role MenuRole) String() string { - switch role { - case AppMenu: - return "app" - case FileMenu: - return "file" - case EditMenu: - return "edit" - case ViewMenu: - return "view" - case WindowMenu: - return "window" - case HelpMenu: - return "help" - default: - return "unknown" +// AppendItem appends an already-constructed MenuItem. +func (m *Menu) AppendItem(item *MenuItem) { + m.Items = append(m.Items, item) +} + +// Clone returns a deep copy of the menu tree. +func (m *Menu) Clone() *Menu { + cloned := &Menu{label: m.label} + for _, item := range m.Items { + cloned.Items = append(cloned.Items, item.Clone()) + } + return cloned +} + +// Destroy frees all items in the menu. +func (m *Menu) Destroy() { + for _, item := range m.Items { + item.Destroy() + } + m.Items = nil +} + +func (m *Menu) setContextData(data *ContextMenuData) { + for _, item := range m.Items { + item.contextMenuData = data + if item.submenu != nil { + item.submenu.setContextData(data) + } } } // MenuManager owns the application menu. +// +// app.Menu.SetApplicationMenu(menu) type MenuManager struct { applicationMenu *Menu } @@ -112,7 +138,7 @@ type SystemTray struct { tooltip string label string menu *Menu - attachedWindow *WebviewWindow + attachedWindow Window } func (t *SystemTray) SetIcon(data []byte) { t.icon = append([]byte(nil), data...) } @@ -120,9 +146,9 @@ func (t *SystemTray) SetTemplateIcon(data []byte) { t.templateIcon = append([]by func (t *SystemTray) SetTooltip(text string) { t.tooltip = text } func (t *SystemTray) SetLabel(text string) { t.label = text } func (t *SystemTray) SetMenu(menu *Menu) { t.menu = menu } -func (t *SystemTray) AttachWindow(w *WebviewWindow) { - t.attachedWindow = w -} + +// AttachWindow associates a window with the tray icon (shown on click). +func (t *SystemTray) AttachWindow(w Window) { t.attachedWindow = w } // SystemTrayManager creates tray instances. type SystemTrayManager struct{} @@ -164,159 +190,408 @@ func (e *WindowEvent) Context() *WindowEventContext { return e.ctx } -// WebviewWindowOptions configures a window instance. -type WebviewWindowOptions struct { - Name string - Title string - URL string - Width, Height int - X, Y int - MinWidth, MinHeight int - MaxWidth, MaxHeight int - Frameless bool - Hidden bool - AlwaysOnTop bool - DisableResize bool - EnableFileDrop bool - BackgroundColour RGBA +// WebviewWindow is a lightweight, in-memory window implementation +// that satisfies the Window interface. +type WebviewWindow struct { + mu sync.RWMutex + opts WebviewWindowOptions + windowID uint + title string + posX, posY int + sizeW, sizeH int + maximised bool + focused bool + visible bool + alwaysOnTop bool + isFullscreen bool + closed bool + zoom float64 + resizable bool + ignoreMouseEvents bool + enabled bool + eventHandlers map[events.WindowEventType][]func(*WindowEvent) } -// WebviewWindow is a lightweight, in-memory window implementation. -type WebviewWindow struct { - mu sync.RWMutex - opts WebviewWindowOptions - title string - x, y int - width, height int - maximised bool - focused bool - visible bool - alwaysOnTop bool - fullscreen bool - closed bool - eventHandlers map[events.WindowEventType][]func(*WindowEvent) +var globalWindowID uint +var globalWindowIDMu sync.Mutex + +func nextWindowID() uint { + globalWindowIDMu.Lock() + defer globalWindowIDMu.Unlock() + globalWindowID++ + return globalWindowID } func newWebviewWindow(options WebviewWindowOptions) *WebviewWindow { return &WebviewWindow{ opts: options, + windowID: nextWindowID(), title: options.Title, - x: options.X, - y: options.Y, - width: options.Width, - height: options.Height, + posX: options.X, + posY: options.Y, + sizeW: options.Width, + sizeH: options.Height, visible: !options.Hidden, alwaysOnTop: options.AlwaysOnTop, + zoom: options.Zoom, + resizable: !options.DisableResize, + enabled: true, eventHandlers: make(map[events.WindowEventType][]func(*WindowEvent)), } } +// ID returns the unique numeric identifier for the window. +func (w *WebviewWindow) ID() uint { return w.windowID } + +// Name returns the window name set in WebviewWindowOptions. func (w *WebviewWindow) Name() string { return w.opts.Name } -func (w *WebviewWindow) Title() string { + +// Show makes the window visible and returns it for chaining. +func (w *WebviewWindow) Show() Window { + w.mu.Lock() + w.visible = true + w.mu.Unlock() + return w +} + +// Hide makes the window invisible and returns it for chaining. +func (w *WebviewWindow) Hide() Window { + w.mu.Lock() + w.visible = false + w.mu.Unlock() + return w +} + +// IsVisible reports whether the window is currently visible. +func (w *WebviewWindow) IsVisible() bool { w.mu.RLock() defer w.mu.RUnlock() - return w.title -} -func (w *WebviewWindow) Position() (int, int) { - w.mu.RLock() - defer w.mu.RUnlock() - return w.x, w.y -} -func (w *WebviewWindow) Size() (int, int) { - w.mu.RLock() - defer w.mu.RUnlock() - return w.width, w.height -} -func (w *WebviewWindow) IsMaximised() bool { - w.mu.RLock() - defer w.mu.RUnlock() - return w.maximised -} -func (w *WebviewWindow) IsFocused() bool { - w.mu.RLock() - defer w.mu.RUnlock() - return w.focused -} - -func (w *WebviewWindow) SetTitle(title string) { - w.mu.Lock() - w.title = title - w.mu.Unlock() -} - -func (w *WebviewWindow) SetPosition(x, y int) { - w.mu.Lock() - w.x = x - w.y = y - w.mu.Unlock() -} - -func (w *WebviewWindow) SetSize(width, height int) { - w.mu.Lock() - w.width = width - w.height = height - w.mu.Unlock() -} - -func (w *WebviewWindow) SetBackgroundColour(colour RGBA) {} - -func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) { - w.mu.Lock() - w.alwaysOnTop = alwaysOnTop - w.mu.Unlock() -} - -func (w *WebviewWindow) Maximise() { - w.mu.Lock() - w.maximised = true - w.mu.Unlock() -} - -func (w *WebviewWindow) Restore() { - w.mu.Lock() - w.maximised = false - w.fullscreen = false - w.mu.Unlock() -} - -func (w *WebviewWindow) Minimise() {} - -func (w *WebviewWindow) Focus() { - w.mu.Lock() - w.focused = true - w.mu.Unlock() + return w.visible } +// Close marks the window as closed. func (w *WebviewWindow) Close() { w.mu.Lock() w.closed = true w.mu.Unlock() } -func (w *WebviewWindow) Show() { +// Focus marks the window as focused. +func (w *WebviewWindow) Focus() { + w.mu.Lock() + w.focused = true + w.mu.Unlock() +} + +// Run is a no-op in the stub (the real implementation enters the run loop). +func (w *WebviewWindow) Run() {} + +// Center is a no-op in the stub. +func (w *WebviewWindow) Center() {} + +// Position returns the current x/y position. +func (w *WebviewWindow) Position() (int, int) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.posX, w.posY +} + +// RelativePosition returns the position relative to the screen. +func (w *WebviewWindow) RelativePosition() (int, int) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.posX, w.posY +} + +// Size returns the current width and height. +func (w *WebviewWindow) Size() (int, int) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.sizeW, w.sizeH +} + +// Width returns the current window width. +func (w *WebviewWindow) Width() int { + w.mu.RLock() + defer w.mu.RUnlock() + return w.sizeW +} + +// Height returns the current window height. +func (w *WebviewWindow) Height() int { + w.mu.RLock() + defer w.mu.RUnlock() + return w.sizeH +} + +// Bounds returns the window's position and size as a Rect. +func (w *WebviewWindow) Bounds() Rect { + w.mu.RLock() + defer w.mu.RUnlock() + return Rect{X: w.posX, Y: w.posY, Width: w.sizeW, Height: w.sizeH} +} + +// SetPosition sets the top-left corner position. +func (w *WebviewWindow) SetPosition(x, y int) { + w.mu.Lock() + w.posX, w.posY = x, y + w.mu.Unlock() +} + +// SetRelativePosition sets position relative to the screen and returns the window. +func (w *WebviewWindow) SetRelativePosition(x, y int) Window { + w.SetPosition(x, y) + return w +} + +// SetSize sets the window dimensions and returns the window. +func (w *WebviewWindow) SetSize(width, height int) Window { + w.mu.Lock() + w.sizeW, w.sizeH = width, height + w.mu.Unlock() + return w +} + +// SetBounds sets position and size in one call. +func (w *WebviewWindow) SetBounds(bounds Rect) { + w.mu.Lock() + w.posX, w.posY = bounds.X, bounds.Y + w.sizeW, w.sizeH = bounds.Width, bounds.Height + w.mu.Unlock() +} + +// SetMaxSize is a no-op in the stub. +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { return w } + +// SetMinSize is a no-op in the stub. +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) Window { return w } + +// EnableSizeConstraints is a no-op in the stub. +func (w *WebviewWindow) EnableSizeConstraints() {} + +// DisableSizeConstraints is a no-op in the stub. +func (w *WebviewWindow) DisableSizeConstraints() {} + +// Resizable reports whether the user can resize the window. +func (w *WebviewWindow) Resizable() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.resizable +} + +// SetResizable enables or disables user resizing and returns the window. +func (w *WebviewWindow) SetResizable(b bool) Window { + w.mu.Lock() + w.resizable = b + w.mu.Unlock() + return w +} + +// Maximise maximises the window and returns it. +func (w *WebviewWindow) Maximise() Window { + w.mu.Lock() + w.maximised = true + w.mu.Unlock() + return w +} + +// UnMaximise restores from maximised state. +func (w *WebviewWindow) UnMaximise() { + w.mu.Lock() + w.maximised = false + w.mu.Unlock() +} + +// ToggleMaximise toggles between maximised and normal. +func (w *WebviewWindow) ToggleMaximise() { + w.mu.Lock() + w.maximised = !w.maximised + w.mu.Unlock() +} + +// IsMaximised reports whether the window is maximised. +func (w *WebviewWindow) IsMaximised() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.maximised +} + +// Minimise minimises the window and returns it. +func (w *WebviewWindow) Minimise() Window { + w.mu.Lock() + w.visible = false + w.mu.Unlock() + return w +} + +// UnMinimise restores from minimised state. +func (w *WebviewWindow) UnMinimise() { w.mu.Lock() w.visible = true w.mu.Unlock() } -func (w *WebviewWindow) Hide() { +// IsMinimised always returns false in the stub. +func (w *WebviewWindow) IsMinimised() bool { return false } + +// Fullscreen enters fullscreen and returns the window. +func (w *WebviewWindow) Fullscreen() Window { w.mu.Lock() - w.visible = false - w.mu.Unlock() -} - -func (w *WebviewWindow) Fullscreen() { - w.mu.Lock() - w.fullscreen = true + w.isFullscreen = true w.mu.Unlock() + return w } +// UnFullscreen exits fullscreen. func (w *WebviewWindow) UnFullscreen() { w.mu.Lock() - w.fullscreen = false + w.isFullscreen = false w.mu.Unlock() } +// ToggleFullscreen toggles between fullscreen and normal. +func (w *WebviewWindow) ToggleFullscreen() { + w.mu.Lock() + w.isFullscreen = !w.isFullscreen + w.mu.Unlock() +} + +// IsFullscreen reports whether the window is in fullscreen mode. +func (w *WebviewWindow) IsFullscreen() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.isFullscreen +} + +// Restore exits both fullscreen and maximised states. +func (w *WebviewWindow) Restore() { + w.mu.Lock() + w.maximised = false + w.isFullscreen = false + w.mu.Unlock() +} + +// SnapAssist is a no-op in the stub. +func (w *WebviewWindow) SnapAssist() {} + +// SetTitle updates the window title and returns the window. +func (w *WebviewWindow) SetTitle(title string) Window { + w.mu.Lock() + w.title = title + w.mu.Unlock() + return w +} + +// SetURL is a no-op in the stub. +func (w *WebviewWindow) SetURL(s string) Window { return w } + +// SetHTML is a no-op in the stub. +func (w *WebviewWindow) SetHTML(html string) Window { return w } + +// SetMinimiseButtonState is a no-op in the stub. +func (w *WebviewWindow) SetMinimiseButtonState(state ButtonState) Window { return w } + +// SetMaximiseButtonState is a no-op in the stub. +func (w *WebviewWindow) SetMaximiseButtonState(state ButtonState) Window { return w } + +// SetCloseButtonState is a no-op in the stub. +func (w *WebviewWindow) SetCloseButtonState(state ButtonState) Window { return w } + +// SetMenu is a no-op in the stub. +func (w *WebviewWindow) SetMenu(menu *Menu) {} + +// ShowMenuBar is a no-op in the stub. +func (w *WebviewWindow) ShowMenuBar() {} + +// HideMenuBar is a no-op in the stub. +func (w *WebviewWindow) HideMenuBar() {} + +// ToggleMenuBar is a no-op in the stub. +func (w *WebviewWindow) ToggleMenuBar() {} + +// SetBackgroundColour is a no-op in the stub. +func (w *WebviewWindow) SetBackgroundColour(colour RGBA) Window { return w } + +// SetAlwaysOnTop sets the always-on-top flag and returns the window. +func (w *WebviewWindow) SetAlwaysOnTop(b bool) Window { + w.mu.Lock() + w.alwaysOnTop = b + w.mu.Unlock() + return w +} + +// SetFrameless is a no-op in the stub. +func (w *WebviewWindow) SetFrameless(frameless bool) Window { return w } + +// ToggleFrameless is a no-op in the stub. +func (w *WebviewWindow) ToggleFrameless() {} + +// SetIgnoreMouseEvents sets the mouse-event passthrough flag and returns the window. +func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window { + w.mu.Lock() + w.ignoreMouseEvents = ignore + w.mu.Unlock() + return w +} + +// IsIgnoreMouseEvents reports whether mouse events are being ignored. +func (w *WebviewWindow) IsIgnoreMouseEvents() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.ignoreMouseEvents +} + +// SetContentProtection is a no-op in the stub. +func (w *WebviewWindow) SetContentProtection(protection bool) Window { return w } + +// GetZoom returns the current zoom magnification. +func (w *WebviewWindow) GetZoom() float64 { + w.mu.RLock() + defer w.mu.RUnlock() + return w.zoom +} + +// SetZoom sets the zoom magnification and returns the window. +func (w *WebviewWindow) SetZoom(magnification float64) Window { + w.mu.Lock() + w.zoom = magnification + w.mu.Unlock() + return w +} + +// Zoom is a no-op in the stub. +func (w *WebviewWindow) Zoom() {} + +// ZoomIn is a no-op in the stub. +func (w *WebviewWindow) ZoomIn() {} + +// ZoomOut is a no-op in the stub. +func (w *WebviewWindow) ZoomOut() {} + +// ZoomReset resets zoom to 1.0 and returns the window. +func (w *WebviewWindow) ZoomReset() Window { + w.mu.Lock() + w.zoom = 1.0 + w.mu.Unlock() + return w +} + +// GetBorderSizes returns zero insets in the stub. +func (w *WebviewWindow) GetBorderSizes() *LRTB { return &LRTB{} } + +// GetScreen returns nil in the stub. +func (w *WebviewWindow) GetScreen() (*Screen, error) { return nil, nil } + +// ExecJS is a no-op in the stub. +func (w *WebviewWindow) ExecJS(js string) {} + +// EmitEvent always returns false in the stub. +func (w *WebviewWindow) EmitEvent(name string, data ...any) bool { return false } + +// DispatchWailsEvent is a no-op in the stub. +func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) {} + +// OnWindowEvent registers an event callback and returns an unsubscribe function. func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { w.mu.Lock() w.eventHandlers[eventType] = append(w.eventHandlers[eventType], callback) @@ -324,40 +599,180 @@ func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback return func() {} } -// WindowManager manages in-memory windows. -type WindowManager struct { - mu sync.RWMutex - windows []*WebviewWindow +// RegisterHook is an alias for OnWindowEvent. +func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return w.OnWindowEvent(eventType, callback) } +// handleDragAndDropMessage is a no-op in the stub. +func (w *WebviewWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) {} + +// InitiateFrontendDropProcessing is a no-op in the stub. +func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) {} + +// HandleMessage is a no-op in the stub. +func (w *WebviewWindow) HandleMessage(message string) {} + +// HandleWindowEvent is a no-op in the stub. +func (w *WebviewWindow) HandleWindowEvent(id uint) {} + +// HandleKeyEvent is a no-op in the stub. +func (w *WebviewWindow) HandleKeyEvent(acceleratorString string) {} + +// OpenContextMenu is a no-op in the stub. +func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) {} + +// AttachModal is a no-op in the stub. +func (w *WebviewWindow) AttachModal(modalWindow Window) {} + +// OpenDevTools is a no-op in the stub. +func (w *WebviewWindow) OpenDevTools() {} + +// Print always returns nil in the stub. +func (w *WebviewWindow) Print() error { return nil } + +// Flash is a no-op in the stub. +func (w *WebviewWindow) Flash(enabled bool) {} + +// IsFocused reports whether the window has focus. +func (w *WebviewWindow) IsFocused() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.focused +} + +// NativeWindow returns nil in the stub. +func (w *WebviewWindow) NativeWindow() unsafe.Pointer { return nil } + +// SetEnabled sets the window's enabled state. +func (w *WebviewWindow) SetEnabled(enabled bool) { + w.mu.Lock() + w.enabled = enabled + w.mu.Unlock() +} + +// Reload is a no-op in the stub. +func (w *WebviewWindow) Reload() {} + +// ForceReload is a no-op in the stub. +func (w *WebviewWindow) ForceReload() {} + +// Info is a no-op in the stub. +func (w *WebviewWindow) Info(message string, args ...any) {} + +// Error is a no-op in the stub. +func (w *WebviewWindow) Error(message string, args ...any) {} + +// shouldUnconditionallyClose always returns false in the stub. +func (w *WebviewWindow) shouldUnconditionallyClose() bool { return false } + +// Internal editing stubs — satisfy the Window interface. +func (w *WebviewWindow) cut() {} +func (w *WebviewWindow) copy() {} +func (w *WebviewWindow) paste() {} +func (w *WebviewWindow) undo() {} +func (w *WebviewWindow) redo() {} +func (w *WebviewWindow) delete() {} +func (w *WebviewWindow) selectAll() {} + +// Title returns the current window title. +func (w *WebviewWindow) Title() string { + w.mu.RLock() + defer w.mu.RUnlock() + return w.title +} + +// WindowManager manages in-memory windows. +// +// win := app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "Main"}) +type WindowManager struct { + mu sync.RWMutex + windows map[uint]*WebviewWindow +} + +func (wm *WindowManager) init() { + if wm.windows == nil { + wm.windows = make(map[uint]*WebviewWindow) + } +} + +// NewWithOptions creates and registers a new window. func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWindow { window := newWebviewWindow(options) wm.mu.Lock() - wm.windows = append(wm.windows, window) + wm.init() + wm.windows[window.windowID] = window wm.mu.Unlock() return window } -func (wm *WindowManager) GetAll() []any { +// New creates a window with default options. +func (wm *WindowManager) New() *WebviewWindow { + return wm.NewWithOptions(WebviewWindowOptions{}) +} + +// GetAll returns all managed windows as the Window interface slice. +func (wm *WindowManager) GetAll() []Window { wm.mu.RLock() defer wm.mu.RUnlock() - out := make([]any, 0, len(wm.windows)) + out := make([]Window, 0, len(wm.windows)) for _, window := range wm.windows { out = append(out, window) } return out } -// App is the top-level application object used by the GUI packages. -type App struct { - Logger Logger - Window WindowManager - Menu MenuManager - SystemTray SystemTrayManager +// GetByName finds a window by name, returning it and whether it was found. +func (wm *WindowManager) GetByName(name string) (Window, bool) { + wm.mu.RLock() + defer wm.mu.RUnlock() + for _, window := range wm.windows { + if window.opts.Name == name { + return window, true + } + } + return nil, false } +// GetByID finds a window by its numeric ID. +func (wm *WindowManager) GetByID(id uint) (Window, bool) { + wm.mu.RLock() + defer wm.mu.RUnlock() + window, exists := wm.windows[id] + return window, exists +} + +// Remove unregisters a window by ID. +func (wm *WindowManager) Remove(windowID uint) { + wm.mu.Lock() + wm.init() + delete(wm.windows, windowID) + wm.mu.Unlock() +} + +// App is the top-level application object used by the GUI packages. +// +// app := &application.App{} +// win := app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "Main"}) +type App struct { + Logger Logger + Window WindowManager + Menu MenuManager + SystemTray SystemTrayManager + Dialog DialogManager + Event EventManager + Browser BrowserManager + Clipboard ClipboardManager + ContextMenu ContextMenuManager + Environment EnvironmentManager + Screen ScreenManager + KeyBinding KeyBindingManager +} + +// Quit is a no-op in the stub. func (a *App) Quit() {} +// NewMenu creates a new empty Menu. func (a *App) NewMenu() *Menu { return NewMenu() } diff --git a/stubs/wails/pkg/application/application_options.go b/stubs/wails/pkg/application/application_options.go new file mode 100644 index 0000000..fca6f5c --- /dev/null +++ b/stubs/wails/pkg/application/application_options.go @@ -0,0 +1,348 @@ +package application + +import ( + "net/http" + "time" +) + +// Options is the top-level application configuration passed to New(). +// app := application.New(application.Options{Name: "MyApp", Services: []Service{...}}) +type Options struct { + // Name is displayed in the default about box. + Name string + + // Description is displayed in the default about box. + Description string + + // Icon is the application icon shown in the about box. + Icon []byte + + // Mac contains macOS-specific options. + Mac MacOptions + + // Windows contains Windows-specific options. + Windows WindowsOptions + + // Linux contains Linux-specific options. + Linux LinuxOptions + + // IOS contains iOS-specific options. + IOS IOSOptions + + // Android contains Android-specific options. + Android AndroidOptions + + // Services lists bound Go services exposed to the frontend. + Services []Service + + // MarshalError serialises service method errors to JSON. + // Return nil to fall back to the default error handler. + MarshalError func(error) []byte + + // BindAliases maps alias IDs to bound method IDs. + // Example: map[uint32]uint32{1: 1411160069} + BindAliases map[uint32]uint32 + + // Assets configures the embedded asset server. + Assets AssetOptions + + // Flags are key/value pairs available to the frontend at startup. + Flags map[string]any + + // PanicHandler is called when an unhandled panic occurs. + PanicHandler func(*PanicDetails) + + // DisableDefaultSignalHandler prevents Wails from handling OS signals. + DisableDefaultSignalHandler bool + + // KeyBindings maps accelerator strings to window callbacks. + // Example: map[string]func(Window){"Ctrl+Q": func(w Window) { w.Close() }} + KeyBindings map[string]func(window Window) + + // OnShutdown is called before the application terminates. + OnShutdown func() + + // PostShutdown is called after shutdown, just before process exit. + PostShutdown func() + + // ShouldQuit is called when the user attempts to quit. + // Return false to prevent the application from quitting. + ShouldQuit func() bool + + // RawMessageHandler handles raw messages from the frontend. + RawMessageHandler func(window Window, message string, originInfo *OriginInfo) + + // WarningHandler is called when a non-fatal warning occurs. + WarningHandler func(string) + + // ErrorHandler is called when a non-fatal error occurs. + ErrorHandler func(err error) + + // FileAssociations lists file extensions associated with this application. + // Each extension must include the leading dot, e.g. ".txt". + FileAssociations []string + + // SingleInstance configures single-instance enforcement. + SingleInstance *SingleInstanceOptions + + // Server configures the headless HTTP server (enabled via the "server" build tag). + Server ServerOptions +} + +// ServerOptions configures the headless HTTP server started in server mode. +// opts.Server = application.ServerOptions{Host: "0.0.0.0", Port: 8080} +type ServerOptions struct { + // Host is the bind address. Defaults to "localhost". + Host string + + // Port is the TCP port. Defaults to 8080. + Port int + + // ReadTimeout is the maximum duration for reading a complete request. + ReadTimeout time.Duration + + // WriteTimeout is the maximum duration before timing out a response write. + WriteTimeout time.Duration + + // IdleTimeout is the maximum duration to wait for the next request. + IdleTimeout time.Duration + + // ShutdownTimeout is the maximum duration to wait for active connections on shutdown. + ShutdownTimeout time.Duration + + // TLS configures HTTPS. If nil, plain HTTP is used. + TLS *TLSOptions +} + +// TLSOptions configures HTTPS for the headless server. +// opts.Server.TLS = &application.TLSOptions{CertFile: "cert.pem", KeyFile: "key.pem"} +type TLSOptions struct { + // CertFile is the path to the TLS certificate file. + CertFile string + + // KeyFile is the path to the TLS private key file. + KeyFile string +} + +// AssetOptions configures the embedded asset server. +// opts.Assets = application.AssetOptions{Handler: http.FileServer(http.FS(assets))} +type AssetOptions struct { + // Handler serves all content to the WebView. + Handler http.Handler + + // Middleware injects into the asset server request chain before Wails internals. + Middleware Middleware + + // DisableLogging suppresses per-request asset server log output. + DisableLogging bool +} + +// Middleware is an HTTP middleware function for the asset server. +// type Middleware func(next http.Handler) http.Handler +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware composes multiple Middleware values into a single Middleware. +// chain := application.ChainMiddleware(authMiddleware, loggingMiddleware) +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(handler http.Handler) http.Handler { + for index := len(middleware) - 1; index >= 0; index-- { + handler = middleware[index](handler) + } + return handler + } +} + +// PanicDetails carries information about an unhandled panic. +// opts.PanicHandler = func(d *application.PanicDetails) { log(d.StackTrace) } +type PanicDetails struct { + StackTrace string + Error error + FullStackTrace string +} + +// OriginInfo carries frame-origin metadata for raw message handling. +// opts.RawMessageHandler = func(w Window, msg string, o *application.OriginInfo) { ... } +type OriginInfo struct { + Origin string + TopOrigin string + IsMainFrame bool +} + +// SingleInstanceOptions configures single-instance enforcement. +// opts.SingleInstance = &application.SingleInstanceOptions{UniqueID: "com.example.myapp"} +type SingleInstanceOptions struct { + // UniqueID identifies the application instance, e.g. "com.example.myapp". + UniqueID string + + // OnSecondInstanceLaunch is called when a second instance attempts to start. + OnSecondInstanceLaunch func(data SecondInstanceData) + + // AdditionalData passes custom data from the second instance to the first. + AdditionalData map[string]string +} + +// SecondInstanceData describes a second-instance launch event. +type SecondInstanceData struct { + Args []string `json:"args"` + WorkingDir string `json:"workingDir"` + AdditionalData map[string]string `json:"additionalData,omitempty"` +} + +// ActivationPolicy controls when a macOS application activates. +// opts.Mac.ActivationPolicy = application.ActivationPolicyAccessory +type ActivationPolicy int + +const ( + // ActivationPolicyRegular is used for applications with a main window. + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is used for menu-bar or background applications. + ActivationPolicyAccessory + // ActivationPolicyProhibited prevents the application from activating. + ActivationPolicyProhibited +) + +// MacOptions contains macOS-specific application options. +// opts.Mac = application.MacOptions{ActivationPolicy: application.ActivationPolicyRegular} +type MacOptions struct { + // ActivationPolicy controls how and when the application becomes active. + ActivationPolicy ActivationPolicy + + // ApplicationShouldTerminateAfterLastWindowClosed quits the app when + // the last window closes, matching standard macOS behaviour. + ApplicationShouldTerminateAfterLastWindowClosed bool +} + +// WindowsOptions contains Windows-specific application options. +// opts.Windows = application.WindowsOptions{WndClass: "MyAppWindow"} +type WindowsOptions struct { + // WndClass is the Windows window class name. Defaults to "WailsWebviewWindow". + WndClass string + + // WndProcInterceptor intercepts all Win32 messages in the main message loop. + WndProcInterceptor func(hwnd uintptr, msg uint32, wParam, lParam uintptr) (returnCode uintptr, shouldReturn bool) + + // DisableQuitOnLastWindowClosed prevents auto-quit when the last window closes. + DisableQuitOnLastWindowClosed bool + + // WebviewUserDataPath sets the WebView2 user-data directory. + // Defaults to %APPDATA%\[BinaryName.exe]. + WebviewUserDataPath string + + // WebviewBrowserPath sets the directory containing WebView2 executables. + // Defaults to the system-installed WebView2. + WebviewBrowserPath string + + // EnabledFeatures lists WebView2 feature flags to enable. + EnabledFeatures []string + + // DisabledFeatures lists WebView2 feature flags to disable. + DisabledFeatures []string + + // AdditionalBrowserArgs are extra Chromium command-line arguments. + // Each argument must include the "--" prefix, e.g. "--remote-debugging-port=9222". + AdditionalBrowserArgs []string +} + +// LinuxOptions contains Linux-specific application options. +// opts.Linux = application.LinuxOptions{ProgramName: "myapp"} +type LinuxOptions struct { + // DisableQuitOnLastWindowClosed prevents auto-quit when the last window closes. + DisableQuitOnLastWindowClosed bool + + // ProgramName is passed to g_set_prgname() for window grouping in GTK. + ProgramName string +} + +// IOSOptions contains iOS-specific application options. +// opts.IOS = application.IOSOptions{EnableInlineMediaPlayback: true} +type IOSOptions struct { + // DisableInputAccessoryView hides the Next/Previous/Done toolbar above the keyboard. + DisableInputAccessoryView bool + + // DisableScroll disables WebView scrolling. + DisableScroll bool + + // DisableBounce disables the iOS overscroll bounce effect. + DisableBounce bool + + // DisableScrollIndicators hides scroll indicator bars. + DisableScrollIndicators bool + + // EnableBackForwardNavigationGestures enables swipe navigation gestures. + EnableBackForwardNavigationGestures bool + + // DisableLinkPreview disables long-press link preview (peek and pop). + DisableLinkPreview bool + + // EnableInlineMediaPlayback allows video to play inline rather than full-screen. + EnableInlineMediaPlayback bool + + // EnableAutoplayWithoutUserAction allows media to autoplay without a user gesture. + EnableAutoplayWithoutUserAction bool + + // DisableInspectable disables the Safari remote web inspector. + DisableInspectable bool + + // UserAgent overrides the WKWebView user agent string. + UserAgent string + + // ApplicationNameForUserAgent appends a name to the user agent. Defaults to "wails.io". + ApplicationNameForUserAgent string + + // AppBackgroundColourSet enables BackgroundColour for the main iOS window. + AppBackgroundColourSet bool + + // BackgroundColour is applied to the iOS app window before any WebView is created. + BackgroundColour RGBA + + // EnableNativeTabs enables a native UITabBar at the bottom of the screen. + EnableNativeTabs bool + + // NativeTabsItems configures the labels and SF Symbol icons for the native UITabBar. + NativeTabsItems []NativeTabItem +} + +// NativeTabItem describes a single tab in the iOS native UITabBar. +// item := application.NativeTabItem{Title: "Home", SystemImage: application.NativeTabIconHouse} +type NativeTabItem struct { + Title string `json:"Title"` + SystemImage NativeTabIcon `json:"SystemImage"` +} + +// NativeTabIcon is an SF Symbols name used for a UITabBar icon. +// tab := application.NativeTabItem{SystemImage: application.NativeTabIconGear} +type NativeTabIcon string + +const ( + NativeTabIconNone NativeTabIcon = "" + NativeTabIconHouse NativeTabIcon = "house" + NativeTabIconGear NativeTabIcon = "gear" + NativeTabIconStar NativeTabIcon = "star" + NativeTabIconPerson NativeTabIcon = "person" + NativeTabIconBell NativeTabIcon = "bell" + NativeTabIconMagnify NativeTabIcon = "magnifyingglass" + NativeTabIconList NativeTabIcon = "list.bullet" + NativeTabIconFolder NativeTabIcon = "folder" +) + +// AndroidOptions contains Android-specific application options. +// opts.Android = application.AndroidOptions{EnableZoom: true} +type AndroidOptions struct { + // DisableScroll disables WebView scrolling. + DisableScroll bool + + // DisableOverscroll disables the overscroll bounce effect. + DisableOverscroll bool + + // EnableZoom enables pinch-to-zoom in the WebView. + EnableZoom bool + + // UserAgent overrides the WebView user agent string. + UserAgent string + + // BackgroundColour sets the WebView background colour. + BackgroundColour RGBA + + // DisableHardwareAcceleration disables GPU acceleration for the WebView. + DisableHardwareAcceleration bool +} diff --git a/stubs/wails/pkg/application/browser_manager.go b/stubs/wails/pkg/application/browser_manager.go new file mode 100644 index 0000000..c08311f --- /dev/null +++ b/stubs/wails/pkg/application/browser_manager.go @@ -0,0 +1,21 @@ +package application + +// BrowserManager handles opening URLs and files in the system browser. +// +// manager.OpenURL("https://lthn.io") +// manager.OpenFile("/home/user/document.pdf") +type BrowserManager struct{} + +// OpenURL opens the given URL in the default browser. +// +// err := manager.OpenURL("https://lthn.io") +func (bm *BrowserManager) OpenURL(url string) error { + return nil +} + +// OpenFile opens the given file path in the default browser or file handler. +// +// err := manager.OpenFile("/home/user/report.html") +func (bm *BrowserManager) OpenFile(path string) error { + return nil +} diff --git a/stubs/wails/pkg/application/browser_window.go b/stubs/wails/pkg/application/browser_window.go new file mode 100644 index 0000000..e6dd711 --- /dev/null +++ b/stubs/wails/pkg/application/browser_window.go @@ -0,0 +1,230 @@ +package application + +import ( + "sync" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// ButtonState represents the visual state of a window control button. +// +// window.SetMinimiseButtonState(ButtonHidden) +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) + +// LRTB holds left/right/top/bottom border sizes in pixels. +// +// sizes := window.GetBorderSizes() +type LRTB struct { + Left int + Right int + Top int + Bottom int +} + +// ContextMenuData carries context-menu position and metadata from the frontend. +// +// window.OpenContextMenu(&ContextMenuData{Id: "file-menu"}) +type ContextMenuData struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + Data string `json:"data"` +} + +func (c *ContextMenuData) clone() *ContextMenuData { + if c == nil { + return nil + } + copy := *c + return © +} + +// CustomEvent carries a named event with arbitrary data from the frontend. +// +// window.DispatchWailsEvent(&CustomEvent{Name: "ready", Data: nil}) +type CustomEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender,omitempty"` + cancelled bool +} + +// Cancel prevents the event from reaching further listeners. +func (e *CustomEvent) Cancel() { e.cancelled = true } + +// IsCancelled reports whether Cancel has been called. +func (e *CustomEvent) IsCancelled() bool { return e.cancelled } + +// BrowserWindow represents a browser client connection in server mode. +// It satisfies the Window interface so browser clients are treated +// uniformly with native windows throughout the codebase. +// +// bw := NewBrowserWindow(1, "nano-abc123") +// bw.Focus() // no-op in browser mode +type BrowserWindow struct { + mu sync.RWMutex + id uint + name string + clientID string +} + +// NewBrowserWindow constructs a BrowserWindow with the given numeric ID and client nano-ID. +// +// bw := NewBrowserWindow(1, "nano-abc123") +func NewBrowserWindow(id uint, clientID string) *BrowserWindow { + return &BrowserWindow{ + id: id, + name: "browser-window", + clientID: clientID, + } +} + +// ID returns the numeric window identifier. +func (b *BrowserWindow) ID() uint { return b.id } + +// Name returns the window name. +func (b *BrowserWindow) Name() string { + b.mu.RLock() + defer b.mu.RUnlock() + return b.name +} + +// ClientID returns the runtime nano-ID for this client. +func (b *BrowserWindow) ClientID() string { + b.mu.RLock() + defer b.mu.RUnlock() + return b.clientID +} + +// No-op windowing methods — browser windows have no native chrome. + +func (b *BrowserWindow) Center() {} +func (b *BrowserWindow) Close() {} +func (b *BrowserWindow) DisableSizeConstraints() {} +func (b *BrowserWindow) EnableSizeConstraints() {} +func (b *BrowserWindow) ExecJS(_ string) {} +func (b *BrowserWindow) Focus() {} +func (b *BrowserWindow) ForceReload() {} +func (b *BrowserWindow) HideMenuBar() {} +func (b *BrowserWindow) OpenDevTools() {} +func (b *BrowserWindow) Reload() {} +func (b *BrowserWindow) Restore() {} +func (b *BrowserWindow) Run() {} +func (b *BrowserWindow) SetPosition(_ int, _ int) {} +func (b *BrowserWindow) ShowMenuBar() {} +func (b *BrowserWindow) SnapAssist() {} +func (b *BrowserWindow) ToggleFrameless() {} +func (b *BrowserWindow) ToggleFullscreen() {} +func (b *BrowserWindow) ToggleMaximise() {} +func (b *BrowserWindow) ToggleMenuBar() {} +func (b *BrowserWindow) UnFullscreen() {} +func (b *BrowserWindow) UnMaximise() {} +func (b *BrowserWindow) UnMinimise() {} +func (b *BrowserWindow) SetEnabled(_ bool) {} +func (b *BrowserWindow) Flash(_ bool) {} +func (b *BrowserWindow) SetMenu(_ *Menu) {} +func (b *BrowserWindow) SetBounds(_ Rect) {} +func (b *BrowserWindow) Zoom() {} +func (b *BrowserWindow) ZoomIn() {} +func (b *BrowserWindow) ZoomOut() {} +func (b *BrowserWindow) OpenContextMenu(_ *ContextMenuData) {} +func (b *BrowserWindow) HandleMessage(_ string) {} +func (b *BrowserWindow) HandleWindowEvent(_ uint) {} +func (b *BrowserWindow) HandleKeyEvent(_ string) {} +func (b *BrowserWindow) AttachModal(_ Window) {} + +// Internal editing stubs. +func (b *BrowserWindow) cut() {} +func (b *BrowserWindow) copy() {} +func (b *BrowserWindow) paste() {} +func (b *BrowserWindow) undo() {} +func (b *BrowserWindow) redo() {} +func (b *BrowserWindow) delete() {} +func (b *BrowserWindow) selectAll() {} + +// shouldUnconditionallyClose always returns false for browser windows. +func (b *BrowserWindow) shouldUnconditionallyClose() bool { return false } + +// Methods returning Window for chaining — always no-op for browser windows. + +func (b *BrowserWindow) Fullscreen() Window { return b } +func (b *BrowserWindow) Hide() Window { return b } +func (b *BrowserWindow) Maximise() Window { return b } +func (b *BrowserWindow) Minimise() Window { return b } +func (b *BrowserWindow) Show() Window { return b } +func (b *BrowserWindow) SetAlwaysOnTop(_ bool) Window { return b } +func (b *BrowserWindow) SetBackgroundColour(_ RGBA) Window { return b } +func (b *BrowserWindow) SetFrameless(_ bool) Window { return b } +func (b *BrowserWindow) SetHTML(_ string) Window { return b } +func (b *BrowserWindow) SetMinimiseButtonState(_ ButtonState) Window { return b } +func (b *BrowserWindow) SetMaximiseButtonState(_ ButtonState) Window { return b } +func (b *BrowserWindow) SetCloseButtonState(_ ButtonState) Window { return b } +func (b *BrowserWindow) SetMaxSize(_ int, _ int) Window { return b } +func (b *BrowserWindow) SetMinSize(_ int, _ int) Window { return b } +func (b *BrowserWindow) SetRelativePosition(_ int, _ int) Window { return b } +func (b *BrowserWindow) SetResizable(_ bool) Window { return b } +func (b *BrowserWindow) SetIgnoreMouseEvents(_ bool) Window { return b } +func (b *BrowserWindow) SetSize(_ int, _ int) Window { return b } +func (b *BrowserWindow) SetTitle(_ string) Window { return b } +func (b *BrowserWindow) SetURL(_ string) Window { return b } +func (b *BrowserWindow) SetZoom(_ float64) Window { return b } +func (b *BrowserWindow) SetContentProtection(_ bool) Window { return b } +func (b *BrowserWindow) ZoomReset() Window { return b } + +// Methods returning simple zero values. + +func (b *BrowserWindow) GetBorderSizes() *LRTB { return nil } +func (b *BrowserWindow) GetScreen() (*Screen, error) { return nil, nil } +func (b *BrowserWindow) GetZoom() float64 { return 1.0 } +func (b *BrowserWindow) Height() int { return 0 } +func (b *BrowserWindow) Width() int { return 0 } +func (b *BrowserWindow) IsFocused() bool { return false } +func (b *BrowserWindow) IsFullscreen() bool { return false } +func (b *BrowserWindow) IsIgnoreMouseEvents() bool { return false } +func (b *BrowserWindow) IsMaximised() bool { return false } +func (b *BrowserWindow) IsMinimised() bool { return false } +func (b *BrowserWindow) IsVisible() bool { return true } +func (b *BrowserWindow) Resizable() bool { return false } +func (b *BrowserWindow) Position() (int, int) { return 0, 0 } +func (b *BrowserWindow) RelativePosition() (int, int) { return 0, 0 } +func (b *BrowserWindow) Size() (int, int) { return 0, 0 } +func (b *BrowserWindow) Bounds() Rect { return Rect{} } +func (b *BrowserWindow) NativeWindow() unsafe.Pointer { return nil } +func (b *BrowserWindow) Print() error { return nil } + +// DispatchWailsEvent is a no-op for browser windows; events are broadcast via WebSocket. +func (b *BrowserWindow) DispatchWailsEvent(_ *CustomEvent) {} + +// EmitEvent broadcasts a named event; always returns false in the stub. +func (b *BrowserWindow) EmitEvent(_ string, _ ...any) bool { return false } + +// Error logs an error message (no-op in the stub). +func (b *BrowserWindow) Error(_ string, _ ...any) {} + +// Info logs an info message (no-op in the stub). +func (b *BrowserWindow) Info(_ string, _ ...any) {} + +// OnWindowEvent registers a callback for a window event type; returns an unsubscribe func. +// +// unsubscribe := bw.OnWindowEvent(events.Common.WindowClosing, fn) +func (b *BrowserWindow) OnWindowEvent(_ events.WindowEventType, _ func(*WindowEvent)) func() { + return func() {} +} + +// RegisterHook registers a lifecycle hook; returns an unsubscribe func. +func (b *BrowserWindow) RegisterHook(_ events.WindowEventType, _ func(*WindowEvent)) func() { + return func() {} +} + +// handleDragAndDropMessage is a no-op for browser windows. +func (b *BrowserWindow) handleDragAndDropMessage(_ []string, _ *DropTargetDetails) {} + +// InitiateFrontendDropProcessing is a no-op for browser windows. +func (b *BrowserWindow) InitiateFrontendDropProcessing(_ []string, _ int, _ int) {} diff --git a/stubs/wails/pkg/application/clipboard.go b/stubs/wails/pkg/application/clipboard.go new file mode 100644 index 0000000..a26238e --- /dev/null +++ b/stubs/wails/pkg/application/clipboard.go @@ -0,0 +1,65 @@ +package application + +import "sync" + +// Clipboard provides direct read/write access to the system clipboard. +// +// ok := clipboard.SetText("hello") +// text, ok := clipboard.Text() +type Clipboard struct { + mu sync.RWMutex + text string +} + +// SetText writes the given text to the clipboard and returns true on success. +// +// ok := clipboard.SetText("copied text") +func (c *Clipboard) SetText(text string) bool { + c.mu.Lock() + c.text = text + c.mu.Unlock() + return true +} + +// Text reads the current clipboard text. Returns the text and true on success. +// +// text, ok := clipboard.Text() +func (c *Clipboard) Text() (string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c.text, true +} + +// ClipboardManager is the application-level clipboard surface. +// Lazily initialises the underlying Clipboard on first use. +// +// manager.SetText("hello") +// text, ok := manager.Text() +type ClipboardManager struct { + mu sync.Mutex + clipboard *Clipboard +} + +// SetText writes text to the clipboard and returns true on success. +// +// ok := manager.SetText("hello") +func (cm *ClipboardManager) SetText(text string) bool { + return cm.instance().SetText(text) +} + +// Text reads the current clipboard text. Returns the text and true on success. +// +// text, ok := manager.Text() +func (cm *ClipboardManager) Text() (string, bool) { + return cm.instance().Text() +} + +// instance returns the Clipboard, creating it if it does not yet exist. +func (cm *ClipboardManager) instance() *Clipboard { + cm.mu.Lock() + defer cm.mu.Unlock() + if cm.clipboard == nil { + cm.clipboard = &Clipboard{} + } + return cm.clipboard +} diff --git a/stubs/wails/pkg/application/context_menu.go b/stubs/wails/pkg/application/context_menu.go new file mode 100644 index 0000000..9acd14a --- /dev/null +++ b/stubs/wails/pkg/application/context_menu.go @@ -0,0 +1,73 @@ +package application + +import "sync" + +// ContextMenu is a named Menu used as a right-click context menu. +// +// cm := manager.New() +// cm.Add("Cut").OnClick(func(*Context) { ... }) +type ContextMenu struct { + *Menu + name string +} + +// ContextMenuManager manages named context menus for the application. +// +// manager.Add("fileList", cm) +// menu, ok := manager.Get("fileList") +type ContextMenuManager struct { + mu sync.RWMutex + menus map[string]*ContextMenu +} + +// New creates an empty, unnamed ContextMenu ready for population. +// +// cm := manager.New() +// cm.Add("Open") +func (cmm *ContextMenuManager) New() *ContextMenu { + return &ContextMenu{Menu: NewMenu()} +} + +// Add registers a ContextMenu under the given name, replacing any existing entry. +// +// manager.Add("fileList", cm) +func (cmm *ContextMenuManager) Add(name string, menu *ContextMenu) { + cmm.mu.Lock() + defer cmm.mu.Unlock() + if cmm.menus == nil { + cmm.menus = make(map[string]*ContextMenu) + } + cmm.menus[name] = menu +} + +// Remove unregisters the context menu with the given name. +// +// manager.Remove("fileList") +func (cmm *ContextMenuManager) Remove(name string) { + cmm.mu.Lock() + defer cmm.mu.Unlock() + delete(cmm.menus, name) +} + +// Get retrieves a registered context menu by name. +// +// menu, ok := manager.Get("fileList") +func (cmm *ContextMenuManager) Get(name string) (*ContextMenu, bool) { + cmm.mu.RLock() + defer cmm.mu.RUnlock() + menu, exists := cmm.menus[name] + return menu, exists +} + +// GetAll returns all registered context menus as a slice. +// +// for _, cm := range manager.GetAll() { ... } +func (cmm *ContextMenuManager) GetAll() []*ContextMenu { + cmm.mu.RLock() + defer cmm.mu.RUnlock() + result := make([]*ContextMenu, 0, len(cmm.menus)) + for _, menu := range cmm.menus { + result = append(result, menu) + } + return result +} diff --git a/stubs/wails/pkg/application/dialog.go b/stubs/wails/pkg/application/dialog.go new file mode 100644 index 0000000..4a5090a --- /dev/null +++ b/stubs/wails/pkg/application/dialog.go @@ -0,0 +1,481 @@ +package application + +// DialogType identifies the visual style of a message dialog. +type DialogType int + +const ( + InfoDialogType DialogType = iota + QuestionDialogType DialogType = iota + WarningDialogType DialogType = iota + ErrorDialogType DialogType = iota +) + +// FileFilter describes a file type filter for open/save dialogs. +// +// filter := FileFilter{DisplayName: "Images (*.png;*.jpg)", Pattern: "*.png;*.jpg"} +type FileFilter struct { + DisplayName string + Pattern string +} + +// Button is a labelled action in a MessageDialog. +// +// btn := dialog.AddButton("OK") +// btn.SetAsDefault().OnClick(func() { ... }) +type Button struct { + Label string + IsCancel bool + IsDefault bool + Callback func() +} + +// OnClick registers a click handler on the button and returns itself for chaining. +// +// btn.OnClick(func() { saveFile() }) +func (b *Button) OnClick(callback func()) *Button { + b.Callback = callback + return b +} + +// SetAsDefault marks this button as the default (Enter key) action. +func (b *Button) SetAsDefault() *Button { + b.IsDefault = true + return b +} + +// SetAsCancel marks this button as the cancel (Escape key) action. +func (b *Button) SetAsCancel() *Button { + b.IsCancel = true + return b +} + +// MessageDialogOptions holds configuration for a MessageDialog. +type MessageDialogOptions struct { + DialogType DialogType + Title string + Message string + Buttons []*Button + Icon []byte +} + +// MessageDialog is an in-memory message dialog (info / question / warning / error). +// +// dialog.Info().SetTitle("Done").SetMessage("File saved.").Show() +type MessageDialog struct { + MessageDialogOptions +} + +// SetTitle sets the dialog window title. +// +// dialog.SetTitle("Confirm Delete") +func (d *MessageDialog) SetTitle(title string) *MessageDialog { + d.Title = title + return d +} + +// SetMessage sets the body text shown in the dialog. +// +// dialog.SetMessage("Are you sure?") +func (d *MessageDialog) SetMessage(message string) *MessageDialog { + d.Message = message + return d +} + +// SetIcon sets the icon bytes shown in the dialog. +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog { + d.Icon = icon + return d +} + +// AddButton appends a labelled button and returns it for further configuration. +// +// btn := dialog.AddButton("Yes") +func (d *MessageDialog) AddButton(label string) *Button { + btn := &Button{Label: label} + d.Buttons = append(d.Buttons, btn) + return btn +} + +// AddButtons replaces the button list in bulk. +func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog { + d.Buttons = buttons + return d +} + +// SetDefaultButton marks the given button as the default action. +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsDefault = false + } + button.IsDefault = true + return d +} + +// SetCancelButton marks the given button as the cancel action. +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog { + for _, b := range d.Buttons { + b.IsCancel = false + } + button.IsCancel = true + return d +} + +// AttachToWindow associates the dialog with a parent window (no-op in the stub). +func (d *MessageDialog) AttachToWindow(window *WebviewWindow) *MessageDialog { + return d +} + +// Show presents the dialog. No-op in the stub. +func (d *MessageDialog) Show() {} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{ + MessageDialogOptions: MessageDialogOptions{DialogType: dialogType}, + } +} + +// OpenFileDialogOptions configures an OpenFileDialogStruct. +type OpenFileDialogOptions struct { + CanChooseDirectories bool + CanChooseFiles bool + CanCreateDirectories bool + ShowHiddenFiles bool + ResolvesAliases bool + AllowsMultipleSelection bool + HideExtension bool + CanSelectHiddenExtension bool + TreatsFilePackagesAsDirectories bool + AllowsOtherFileTypes bool + Filters []FileFilter + Title string + Message string + ButtonText string + Directory string +} + +// OpenFileDialogStruct is a builder for file-open dialogs. +// +// path, err := manager.OpenFile().SetTitle("Pick a file").PromptForSingleSelection() +type OpenFileDialogStruct struct { + canChooseDirectories bool + canChooseFiles bool + canCreateDirectories bool + showHiddenFiles bool + resolvesAliases bool + allowsMultipleSelection bool + hideExtension bool + canSelectHiddenExtension bool + treatsFilePackagesAsDirectories bool + allowsOtherFileTypes bool + filters []FileFilter + title string + message string + buttonText string + directory string +} + +func newOpenFileDialog() *OpenFileDialogStruct { + return &OpenFileDialogStruct{ + canChooseFiles: true, + canCreateDirectories: true, + } +} + +// SetOptions applies all fields from OpenFileDialogOptions to the dialog. +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) { + d.title = options.Title + d.message = options.Message + d.buttonText = options.ButtonText + d.directory = options.Directory + d.canChooseDirectories = options.CanChooseDirectories + d.canChooseFiles = options.CanChooseFiles + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.resolvesAliases = options.ResolvesAliases + d.allowsMultipleSelection = options.AllowsMultipleSelection + d.hideExtension = options.HideExtension + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.allowsOtherFileTypes = options.AllowsOtherFileTypes + d.filters = options.Filters +} + +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct { + d.title = title + return d +} + +func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct { + d.message = message + return d +} + +func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct { + d.buttonText = text + return d +} + +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct { + d.directory = directory + return d +} + +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct { + d.canChooseFiles = canChooseFiles + return d +} + +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct { + d.canChooseDirectories = canChooseDirectories + return d +} + +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct { + d.resolvesAliases = resolvesAliases + return d +} + +func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct { + d.allowsOtherFileTypes = allowsOtherFileTypes + return d +} + +func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treats bool) *OpenFileDialogStruct { + d.treatsFilePackagesAsDirectories = treats + return d +} + +// AddFilter appends a file type filter to the dialog. +// +// dialog.AddFilter("Images", "*.png;*.jpg") +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct { + d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern}) + return d +} + +func (d *OpenFileDialogStruct) AttachToWindow(window *WebviewWindow) *OpenFileDialogStruct { + return d +} + +// PromptForSingleSelection shows the dialog and returns the chosen path. +// Always returns ("", nil) in the stub. +// +// path, err := dialog.PromptForSingleSelection() +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { + return "", nil +} + +// PromptForMultipleSelection shows the dialog and returns all chosen paths. +// Always returns (nil, nil) in the stub. +// +// paths, err := dialog.PromptForMultipleSelection() +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { + return nil, nil +} + +// SaveFileDialogOptions configures a SaveFileDialogStruct. +type SaveFileDialogOptions struct { + CanCreateDirectories bool + ShowHiddenFiles bool + CanSelectHiddenExtension bool + AllowOtherFileTypes bool + HideExtension bool + TreatsFilePackagesAsDirectories bool + Title string + Message string + Directory string + Filename string + ButtonText string + Filters []FileFilter +} + +// SaveFileDialogStruct is a builder for file-save dialogs. +// +// path, err := manager.SaveFile().SetTitle("Save As").PromptForSingleSelection() +type SaveFileDialogStruct struct { + canCreateDirectories bool + showHiddenFiles bool + canSelectHiddenExtension bool + allowOtherFileTypes bool + hideExtension bool + treatsFilePackagesAsDirectories bool + title string + message string + directory string + filename string + buttonText string + filters []FileFilter +} + +func newSaveFileDialog() *SaveFileDialogStruct { + return &SaveFileDialogStruct{canCreateDirectories: true} +} + +// SetOptions applies all fields from SaveFileDialogOptions to the dialog. +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) { + d.title = options.Title + d.canCreateDirectories = options.CanCreateDirectories + d.showHiddenFiles = options.ShowHiddenFiles + d.canSelectHiddenExtension = options.CanSelectHiddenExtension + d.allowOtherFileTypes = options.AllowOtherFileTypes + d.hideExtension = options.HideExtension + d.treatsFilePackagesAsDirectories = options.TreatsFilePackagesAsDirectories + d.message = options.Message + d.directory = options.Directory + d.filename = options.Filename + d.buttonText = options.ButtonText + d.filters = options.Filters +} + +func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct { + d.title = title + return d +} + +func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct { + d.message = message + return d +} + +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct { + d.directory = directory + return d +} + +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct { + d.filename = filename + return d +} + +func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct { + d.buttonText = text + return d +} + +func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct { + d.canCreateDirectories = canCreateDirectories + return d +} + +func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct { + d.showHiddenFiles = showHiddenFiles + return d +} + +func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct { + d.canSelectHiddenExtension = canSelectHiddenExtension + return d +} + +func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct { + d.allowOtherFileTypes = allowOtherFileTypes + return d +} + +func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct { + d.hideExtension = hideExtension + return d +} + +func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treats bool) *SaveFileDialogStruct { + d.treatsFilePackagesAsDirectories = treats + return d +} + +// AddFilter appends a file type filter to the dialog. +// +// dialog.AddFilter("Text files", "*.txt") +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct { + d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern}) + return d +} + +func (d *SaveFileDialogStruct) AttachToWindow(window *WebviewWindow) *SaveFileDialogStruct { + return d +} + +// PromptForSingleSelection shows the save dialog and returns the chosen path. +// Always returns ("", nil) in the stub. +// +// path, err := dialog.PromptForSingleSelection() +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { + return "", nil +} + +// DialogManager exposes factory methods for all dialog types. +// +// manager.Info().SetMessage("Saved!").Show() +// path, _ := manager.OpenFile().PromptForSingleSelection() +type DialogManager struct{} + +// OpenFile creates a file-open dialog builder. +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct { + return newOpenFileDialog() +} + +// OpenFileWithOptions creates a file-open dialog builder pre-populated from options. +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { + result := newOpenFileDialog() + result.SetOptions(options) + return result +} + +// SaveFile creates a file-save dialog builder. +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct { + return newSaveFileDialog() +} + +// SaveFileWithOptions creates a file-save dialog builder pre-populated from options. +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct { + result := newSaveFileDialog() + result.SetOptions(options) + return result +} + +// Info creates an information message dialog. +// +// manager.Info().SetMessage("Done").Show() +func (dm *DialogManager) Info() *MessageDialog { + return newMessageDialog(InfoDialogType) +} + +// Question creates a question message dialog. +// +// manager.Question().SetMessage("Continue?").Show() +func (dm *DialogManager) Question() *MessageDialog { + return newMessageDialog(QuestionDialogType) +} + +// Warning creates a warning message dialog. +// +// manager.Warning().SetMessage("Low disk space").Show() +func (dm *DialogManager) Warning() *MessageDialog { + return newMessageDialog(WarningDialogType) +} + +// Error creates an error message dialog. +// +// manager.Error().SetMessage("Write failed").Show() +func (dm *DialogManager) Error() *MessageDialog { + return newMessageDialog(ErrorDialogType) +} diff --git a/stubs/wails/pkg/application/environment.go b/stubs/wails/pkg/application/environment.go new file mode 100644 index 0000000..fe9f982 --- /dev/null +++ b/stubs/wails/pkg/application/environment.go @@ -0,0 +1,63 @@ +package application + +// EnvironmentInfo holds runtime information about the host OS and build. +// +// info := manager.Info() +// fmt.Println(info.OS, info.Arch) +type EnvironmentInfo struct { + OS string + Arch string + Debug bool + PlatformInfo map[string]any +} + +// EnvironmentManager provides queries about the host OS environment. +// +// if manager.IsDarkMode() { applyDarkTheme() } +// accent := manager.GetAccentColor() +type EnvironmentManager struct { + darkMode bool + accentColor string +} + +// IsDarkMode returns true when the OS is using a dark colour scheme. +// +// if manager.IsDarkMode() { applyDarkTheme() } +func (em *EnvironmentManager) IsDarkMode() bool { + return em.darkMode +} + +// GetAccentColor returns the OS accent colour as an rgb() string. +// +// colour := manager.GetAccentColor() // e.g. "rgb(0,122,255)" +func (em *EnvironmentManager) GetAccentColor() string { + if em.accentColor == "" { + return "rgb(0,122,255)" + } + return em.accentColor +} + +// Info returns a snapshot of OS and build environment information. +// +// info := manager.Info() +func (em *EnvironmentManager) Info() EnvironmentInfo { + return EnvironmentInfo{ + PlatformInfo: make(map[string]any), + } +} + +// OpenFileManager opens the file manager at the given path, optionally selecting the file. +// No-op in the stub. +// +// err := manager.OpenFileManager("/home/user/docs", false) +func (em *EnvironmentManager) OpenFileManager(path string, selectFile bool) error { + return nil +} + +// HasFocusFollowsMouse reports whether the Linux desktop is configured for +// focus-follows-mouse. Always returns false in the stub. +// +// if manager.HasFocusFollowsMouse() { adjustMouseBehaviour() } +func (em *EnvironmentManager) HasFocusFollowsMouse() bool { + return false +} diff --git a/stubs/wails/pkg/application/events.go b/stubs/wails/pkg/application/events.go new file mode 100644 index 0000000..184cac0 --- /dev/null +++ b/stubs/wails/pkg/application/events.go @@ -0,0 +1,297 @@ +package application + +import ( + "sync" + "sync/atomic" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// ApplicationEventContext carries structured data for an ApplicationEvent. +// +// files := event.Context().OpenedFiles() +// dark := event.Context().IsDarkMode() +type ApplicationEventContext struct { + data map[string]any +} + +// OpenedFiles returns the list of files provided via the event context, or nil. +// +// for _, path := range event.Context().OpenedFiles() { open(path) } +func (c *ApplicationEventContext) OpenedFiles() []string { + if c.data == nil { + return nil + } + files, ok := c.data["openedFiles"] + if !ok { + return nil + } + result, ok := files.([]string) + if !ok { + return nil + } + return result +} + +// IsDarkMode returns true when the event context reports dark mode active. +// +// if event.Context().IsDarkMode() { applyDark() } +func (c *ApplicationEventContext) IsDarkMode() bool { + return c.getBool("isDarkMode") +} + +// HasVisibleWindows returns true when the event context reports at least one visible window. +// +// if event.Context().HasVisibleWindows() { ... } +func (c *ApplicationEventContext) HasVisibleWindows() bool { + return c.getBool("hasVisibleWindows") +} + +// Filename returns the filename value from the event context, or "". +// +// path := event.Context().Filename() +func (c *ApplicationEventContext) Filename() string { + if c.data == nil { + return "" + } + v, ok := c.data["filename"] + if !ok { + return "" + } + result, ok := v.(string) + if !ok { + return "" + } + return result +} + +// URL returns the URL value from the event context, or "". +// +// url := event.Context().URL() +func (c *ApplicationEventContext) URL() string { + if c.data == nil { + return "" + } + v, ok := c.data["url"] + if !ok { + return "" + } + result, ok := v.(string) + if !ok { + return "" + } + return result +} + +func (c *ApplicationEventContext) getBool(key string) bool { + if c.data == nil { + return false + } + v, ok := c.data[key] + if !ok { + return false + } + result, ok := v.(bool) + if !ok { + return false + } + return result +} + +// ApplicationEvent is the event object delivered to OnApplicationEvent listeners. +// +// em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { +// dark := e.Context().IsDarkMode() +// }) +type ApplicationEvent struct { + Id uint + ctx *ApplicationEventContext + cancelled atomic.Bool +} + +// Context returns the ApplicationEventContext attached to the event. +func (e *ApplicationEvent) Context() *ApplicationEventContext { + if e.ctx == nil { + e.ctx = &ApplicationEventContext{data: make(map[string]any)} + } + return e.ctx +} + +// Cancel marks the event as cancelled, preventing further listener dispatch. +func (e *ApplicationEvent) Cancel() { + e.cancelled.Store(true) +} + +// IsCancelled reports whether the event has been cancelled. +func (e *ApplicationEvent) IsCancelled() bool { + return e.cancelled.Load() +} + +// customEventListener is an internal listener registration. +type customEventListener struct { + callback func(*CustomEvent) + counter int // -1 = unlimited +} + +// applicationEventListener is an internal listener registration. +type applicationEventListener struct { + callback func(*ApplicationEvent) +} + +// EventManager manages custom and application-level event subscriptions. +// +// em.Emit("build:done", result) +// cancel := em.On("build:done", func(e *application.CustomEvent) { ... }) +// em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { ... }) +type EventManager struct { + mu sync.RWMutex + + customListeners map[string][]*customEventListener + applicationListeners map[uint][]*applicationEventListener +} + +func (em *EventManager) ensureCustomListeners() { + if em.customListeners == nil { + em.customListeners = make(map[string][]*customEventListener) + } +} + +func (em *EventManager) ensureApplicationListeners() { + if em.applicationListeners == nil { + em.applicationListeners = make(map[uint][]*applicationEventListener) + } +} + +// Emit emits a custom event by name with optional data arguments. +// Returns true if the event was cancelled by a listener. +// +// cancelled := em.Emit("build:done", buildResult) +func (em *EventManager) Emit(name string, data ...any) bool { + event := &CustomEvent{Name: name} + if len(data) == 1 { + event.Data = data[0] + } else if len(data) > 1 { + event.Data = data + } + return em.EmitEvent(event) +} + +// EmitEvent emits a pre-constructed CustomEvent. +// Returns true if the event was cancelled by a listener. +// +// cancelled := em.EmitEvent(&application.CustomEvent{Name: "ping"}) +func (em *EventManager) EmitEvent(event *CustomEvent) bool { + em.mu.RLock() + listeners := append([]*customEventListener(nil), em.customListeners[event.Name]...) + em.mu.RUnlock() + + toRemove := []int{} + for index, listener := range listeners { + if event.IsCancelled() { + break + } + listener.callback(event) + if listener.counter > 0 { + listener.counter-- + if listener.counter == 0 { + toRemove = append(toRemove, index) + } + } + } + + if len(toRemove) > 0 { + em.mu.Lock() + remaining := em.customListeners[event.Name] + for _, index := range toRemove { + if index < len(remaining) { + remaining = append(remaining[:index], remaining[index+1:]...) + } + } + em.customListeners[event.Name] = remaining + em.mu.Unlock() + } + + return event.IsCancelled() +} + +// On registers a persistent listener for the named custom event. +// Returns a cancel function that deregisters the listener. +// +// cancel := em.On("build:done", func(e *application.CustomEvent) { ... }) +// defer cancel() +func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() { + em.mu.Lock() + em.ensureCustomListeners() + listener := &customEventListener{callback: callback, counter: -1} + em.customListeners[name] = append(em.customListeners[name], listener) + em.mu.Unlock() + + return func() { + em.mu.Lock() + defer em.mu.Unlock() + slice := em.customListeners[name] + for i, l := range slice { + if l == listener { + em.customListeners[name] = append(slice[:i], slice[i+1:]...) + return + } + } + } +} + +// Off removes all listeners for the named custom event. +// +// em.Off("build:done") +func (em *EventManager) Off(name string) { + em.mu.Lock() + delete(em.customListeners, name) + em.mu.Unlock() +} + +// OnMultiple registers a listener that fires at most counter times, then auto-deregisters. +// +// em.OnMultiple("ping", func(e *application.CustomEvent) { ... }, 3) +func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) { + em.mu.Lock() + em.ensureCustomListeners() + em.customListeners[name] = append(em.customListeners[name], &customEventListener{ + callback: callback, + counter: counter, + }) + em.mu.Unlock() +} + +// Reset removes all custom event listeners. +// +// em.Reset() +func (em *EventManager) Reset() { + em.mu.Lock() + em.customListeners = make(map[string][]*customEventListener) + em.mu.Unlock() +} + +// OnApplicationEvent registers a listener for a platform application event. +// Returns a cancel function that deregisters the listener. +// +// cancel := em.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) { ... }) +// defer cancel() +func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() { + eventID := uint(eventType) + em.mu.Lock() + em.ensureApplicationListeners() + listener := &applicationEventListener{callback: callback} + em.applicationListeners[eventID] = append(em.applicationListeners[eventID], listener) + em.mu.Unlock() + + return func() { + em.mu.Lock() + defer em.mu.Unlock() + slice := em.applicationListeners[eventID] + for i, l := range slice { + if l == listener { + em.applicationListeners[eventID] = append(slice[:i], slice[i+1:]...) + return + } + } + } +} diff --git a/stubs/wails/pkg/application/keybinding.go b/stubs/wails/pkg/application/keybinding.go new file mode 100644 index 0000000..944539d --- /dev/null +++ b/stubs/wails/pkg/application/keybinding.go @@ -0,0 +1,69 @@ +package application + +import "sync" + +// KeyBinding pairs an accelerator string with its callback. +// binding := &KeyBinding{Accelerator: "Ctrl+K", Callback: fn} +type KeyBinding struct { + Accelerator string + Callback func(window Window) +} + +// KeyBindingManager holds all registered global key bindings in memory. +// manager.Add("Ctrl+K", fn) — manager.Remove("Ctrl+K") — manager.GetAll() +type KeyBindingManager struct { + mu sync.RWMutex + bindings map[string]func(window Window) +} + +// NewKeyBindingManager constructs an empty KeyBindingManager. +// manager := NewKeyBindingManager() +func NewKeyBindingManager() *KeyBindingManager { + return &KeyBindingManager{ + bindings: make(map[string]func(window Window)), + } +} + +// Add registers a callback for the given accelerator string. +// manager.Add("Ctrl+Shift+P", func(w Window) { w.Focus() }) +func (m *KeyBindingManager) Add(accelerator string, callback func(window Window)) { + m.mu.Lock() + m.bindings[accelerator] = callback + m.mu.Unlock() +} + +// Remove deletes the binding for the given accelerator. +// manager.Remove("Ctrl+Shift+P") +func (m *KeyBindingManager) Remove(accelerator string) { + m.mu.Lock() + delete(m.bindings, accelerator) + m.mu.Unlock() +} + +// Process fires the callback for accelerator if registered, returning true when handled. +// handled := manager.Process("Ctrl+K", window) +func (m *KeyBindingManager) Process(accelerator string, window Window) bool { + m.mu.RLock() + callback, exists := m.bindings[accelerator] + m.mu.RUnlock() + if exists && callback != nil { + callback(window) + return true + } + return false +} + +// GetAll returns a snapshot of all registered bindings. +// for _, b := range manager.GetAll() { fmt.Println(b.Accelerator) } +func (m *KeyBindingManager) GetAll() []*KeyBinding { + m.mu.RLock() + defer m.mu.RUnlock() + result := make([]*KeyBinding, 0, len(m.bindings)) + for accelerator, callback := range m.bindings { + result = append(result, &KeyBinding{ + Accelerator: accelerator, + Callback: callback, + }) + } + return result +} diff --git a/stubs/wails/pkg/application/menuitem.go b/stubs/wails/pkg/application/menuitem.go new file mode 100644 index 0000000..74d55fb --- /dev/null +++ b/stubs/wails/pkg/application/menuitem.go @@ -0,0 +1,379 @@ +package application + +import "sync/atomic" + +// Role identifies a platform-defined menu role. +// +// item := NewRole(Quit) // produces the platform quit item +type Role uint + +const ( + NoRole Role = iota + AppMenu Role = iota + EditMenu Role = iota + ViewMenu Role = iota + WindowMenu Role = iota + ServicesMenu Role = iota + HelpMenu Role = iota + SpeechMenu Role = iota + FileMenu Role = iota + + Hide Role = iota + HideOthers Role = iota + ShowAll Role = iota + BringAllToFront Role = iota + UnHide Role = iota + About Role = iota + Undo Role = iota + Redo Role = iota + Cut Role = iota + Copy Role = iota + Paste Role = iota + PasteAndMatchStyle Role = iota + SelectAll Role = iota + Delete Role = iota + Quit Role = iota + CloseWindow Role = iota + Reload Role = iota + ForceReload Role = iota + OpenDevTools Role = iota + ResetZoom Role = iota + ZoomIn Role = iota + ZoomOut Role = iota + ToggleFullscreen Role = iota + Minimise Role = iota + Zoom Role = iota + FullScreen Role = iota + NewFile Role = iota + Open Role = iota + Save Role = iota + SaveAs Role = iota + StartSpeaking Role = iota + StopSpeaking Role = iota + Revert Role = iota + Print Role = iota + PageLayout Role = iota + Find Role = iota + FindAndReplace Role = iota + FindNext Role = iota + FindPrevious Role = iota + Front Role = iota + Help Role = iota +) + +// menuItemType classifies what kind of item a MenuItem is. +type menuItemType int + +const ( + menuItemTypeText menuItemType = iota + menuItemTypeSeparator menuItemType = iota + menuItemTypeCheckbox menuItemType = iota + menuItemTypeRadio menuItemType = iota + menuItemTypeSubmenu menuItemType = iota +) + +var globalMenuItemID uintptr + +// MenuItem is a node in a menu tree. +// +// item := NewMenuItem("Preferences"). +// SetAccelerator("CmdOrCtrl+,"). +// OnClick(func(ctx *Context) { openPrefs() }) +type MenuItem struct { + id uint + label string + tooltip string + disabled bool + checked bool + hidden bool + bitmap []byte + submenu *Menu + callback func(*Context) + itemType menuItemType + acceleratorStr string + role Role + contextMenuData *ContextMenuData + + radioGroupMembers []*MenuItem +} + +func nextMenuItemID() uint { + return uint(atomic.AddUintptr(&globalMenuItemID, 1)) +} + +// NewMenuItem creates a standard clickable menu item with the given label. +// +// item := NewMenuItem("Save").OnClick(func(ctx *Context) { save() }) +func NewMenuItem(label string) *MenuItem { + return &MenuItem{ + id: nextMenuItemID(), + label: label, + itemType: menuItemTypeText, + disabled: false, + } +} + +// NewMenuItemSeparator creates a horizontal separator. +// +// menu.AppendItem(NewMenuItemSeparator()) +func NewMenuItemSeparator() *MenuItem { + return &MenuItem{ + id: nextMenuItemID(), + itemType: menuItemTypeSeparator, + } +} + +// NewMenuItemCheckbox creates a checkable menu item. +// +// item := NewMenuItemCheckbox("Show Toolbar", true) +func NewMenuItemCheckbox(label string, checked bool) *MenuItem { + return &MenuItem{ + id: nextMenuItemID(), + label: label, + checked: checked, + itemType: menuItemTypeCheckbox, + } +} + +// NewMenuItemRadio creates a radio-group menu item. +// +// light := NewMenuItemRadio("Light Theme", true) +func NewMenuItemRadio(label string, checked bool) *MenuItem { + return &MenuItem{ + id: nextMenuItemID(), + label: label, + checked: checked, + itemType: menuItemTypeRadio, + } +} + +// NewSubMenuItem creates an item that reveals a child menu on hover. +// +// sub := NewSubMenuItem("Recent Files") +// sub.GetSubmenu().Add("report.pdf") +func NewSubMenuItem(label string) *MenuItem { + return &MenuItem{ + id: nextMenuItemID(), + label: label, + itemType: menuItemTypeSubmenu, + submenu: &Menu{label: label}, + } +} + +// NewRole creates a platform-managed menu item for the given role. +// +// menu.AppendItem(NewRole(Quit)) +func NewRole(role Role) *MenuItem { + item := &MenuItem{ + id: nextMenuItemID(), + label: roleLabel(role), + itemType: menuItemTypeText, + role: role, + } + return item +} + +func roleLabel(role Role) string { + switch role { + case AppMenu: + return "Application" + case EditMenu: + return "Edit" + case ViewMenu: + return "View" + case WindowMenu: + return "Window" + case ServicesMenu: + return "Services" + case HelpMenu: + return "Help" + case SpeechMenu: + return "Speech" + case FileMenu: + return "File" + case Hide: + return "Hide" + case HideOthers: + return "Hide Others" + case ShowAll: + return "Show All" + case BringAllToFront: + return "Bring All to Front" + case UnHide: + return "Unhide" + case About: + return "About" + case Undo: + return "Undo" + case Redo: + return "Redo" + case Cut: + return "Cut" + case Copy: + return "Copy" + case Paste: + return "Paste" + case PasteAndMatchStyle: + return "Paste and Match Style" + case SelectAll: + return "Select All" + case Delete: + return "Delete" + case Quit: + return "Quit" + case CloseWindow: + return "Close Window" + case Reload: + return "Reload" + case ForceReload: + return "Force Reload" + case OpenDevTools: + return "Open Dev Tools" + case ResetZoom: + return "Reset Zoom" + case ZoomIn: + return "Zoom In" + case ZoomOut: + return "Zoom Out" + case ToggleFullscreen: + return "Toggle Fullscreen" + case Minimise: + return "Minimise" + case Zoom: + return "Zoom" + case FullScreen: + return "Fullscreen" + case NewFile: + return "New" + case Open: + return "Open" + case Save: + return "Save" + case SaveAs: + return "Save As" + case StartSpeaking: + return "Start Speaking" + case StopSpeaking: + return "Stop Speaking" + case Revert: + return "Revert" + case Print: + return "Print" + case PageLayout: + return "Page Layout" + case Find: + return "Find" + case FindAndReplace: + return "Find and Replace" + case FindNext: + return "Find Next" + case FindPrevious: + return "Find Previous" + case Front: + return "Bring All to Front" + case Help: + return "Help" + default: + return "" + } +} + +// Fluent setters — all return *MenuItem for chaining. + +// SetLabel updates the visible label. +func (m *MenuItem) SetLabel(s string) *MenuItem { m.label = s; return m } + +// SetAccelerator sets the keyboard shortcut string (e.g. "CmdOrCtrl+S"). +func (m *MenuItem) SetAccelerator(shortcut string) *MenuItem { + m.acceleratorStr = shortcut + return m +} + +// GetAccelerator returns the raw accelerator string. +func (m *MenuItem) GetAccelerator() string { return m.acceleratorStr } + +// RemoveAccelerator clears the keyboard shortcut. +func (m *MenuItem) RemoveAccelerator() { m.acceleratorStr = "" } + +// SetTooltip sets the hover tooltip. +func (m *MenuItem) SetTooltip(s string) *MenuItem { m.tooltip = s; return m } + +// SetRole assigns a platform role to the item. +func (m *MenuItem) SetRole(role Role) *MenuItem { m.role = role; return m } + +// SetEnabled enables or disables the item. +func (m *MenuItem) SetEnabled(enabled bool) *MenuItem { m.disabled = !enabled; return m } + +// SetBitmap sets a raw-bytes icon for the item (Windows). +func (m *MenuItem) SetBitmap(bitmap []byte) *MenuItem { + m.bitmap = append([]byte(nil), bitmap...) + return m +} + +// SetChecked sets the checked state for checkbox/radio items. +func (m *MenuItem) SetChecked(checked bool) *MenuItem { m.checked = checked; return m } + +// SetHidden hides or shows the item without removing it. +func (m *MenuItem) SetHidden(hidden bool) *MenuItem { m.hidden = hidden; return m } + +// OnClick registers the callback invoked when the item is activated. +func (m *MenuItem) OnClick(f func(*Context)) *MenuItem { m.callback = f; return m } + +// Getters + +// Label returns the item's display label. +func (m *MenuItem) Label() string { return m.label } + +// Tooltip returns the hover tooltip. +func (m *MenuItem) Tooltip() string { return m.tooltip } + +// Enabled reports whether the item is interactive. +func (m *MenuItem) Enabled() bool { return !m.disabled } + +// Checked reports whether the item is checked. +func (m *MenuItem) Checked() bool { return m.checked } + +// Hidden reports whether the item is hidden. +func (m *MenuItem) Hidden() bool { return m.hidden } + +// IsSeparator reports whether this is a separator item. +func (m *MenuItem) IsSeparator() bool { return m.itemType == menuItemTypeSeparator } + +// IsSubmenu reports whether this item contains a child menu. +func (m *MenuItem) IsSubmenu() bool { return m.itemType == menuItemTypeSubmenu } + +// IsCheckbox reports whether this is a checkbox item. +func (m *MenuItem) IsCheckbox() bool { return m.itemType == menuItemTypeCheckbox } + +// IsRadio reports whether this is a radio item. +func (m *MenuItem) IsRadio() bool { return m.itemType == menuItemTypeRadio } + +// GetSubmenu returns the submenu, or nil if this is not a submenu item. +func (m *MenuItem) GetSubmenu() *Menu { return m.submenu } + +// Clone returns a shallow copy of the MenuItem. +func (m *MenuItem) Clone() *MenuItem { + cloned := *m + if m.submenu != nil { + cloned.submenu = m.submenu.Clone() + } + if m.bitmap != nil { + cloned.bitmap = append([]byte(nil), m.bitmap...) + } + if m.contextMenuData != nil { + cloned.contextMenuData = m.contextMenuData.clone() + } + cloned.radioGroupMembers = nil + return &cloned +} + +// Destroy frees resources held by the item and its submenu. +func (m *MenuItem) Destroy() { + if m.submenu != nil { + m.submenu.Destroy() + m.submenu = nil + } + m.callback = nil + m.radioGroupMembers = nil + m.contextMenuData = nil +} diff --git a/stubs/wails/pkg/application/screen.go b/stubs/wails/pkg/application/screen.go new file mode 100644 index 0000000..142abd4 --- /dev/null +++ b/stubs/wails/pkg/application/screen.go @@ -0,0 +1,202 @@ +package application + +import ( + "math" + "sync" +) + +// Rect describes an axis-aligned rectangle. +// r := Rect{X: 0, Y: 0, Width: 1920, Height: 1080} +type Rect struct { + X int + Y int + Width int + Height int +} + +// Point is a 2D integer coordinate. +// p := Point{X: 100, Y: 200} +type Point struct { + X int + Y int +} + +// Size holds a width/height pair. +// s := Size{Width: 1920, Height: 1080} +type Size struct { + Width int + Height int +} + +// Origin returns the top-left corner of the rect as a Point. +// origin := r.Origin() +func (r Rect) Origin() Point { return Point{X: r.X, Y: r.Y} } + +// Corner returns the exclusive bottom-right corner of the rect. +// corner := r.Corner() +func (r Rect) Corner() Point { return Point{X: r.X + r.Width, Y: r.Y + r.Height} } + +// InsideCorner returns the last pixel inside the rect. +// inside := r.InsideCorner() +func (r Rect) InsideCorner() Point { return Point{X: r.X + r.Width - 1, Y: r.Y + r.Height - 1} } + +// RectSize returns the size of the rect. +// s := r.RectSize() +func (r Rect) RectSize() Size { return Size{Width: r.Width, Height: r.Height} } + +// IsEmpty reports whether the rect has no area. +// if r.IsEmpty() { return } +func (r Rect) IsEmpty() bool { return r.Width <= 0 || r.Height <= 0 } + +// Contains reports whether the point lies inside the rect. +// if r.Contains(Point{X: 100, Y: 200}) { ... } +func (r Rect) Contains(point Point) bool { + return point.X >= r.X && point.X < r.X+r.Width && + point.Y >= r.Y && point.Y < r.Y+r.Height +} + +// Intersect returns the overlapping rectangle of r and other. +// overlap := r.Intersect(otherRect) +func (r Rect) Intersect(other Rect) Rect { + if r.IsEmpty() || other.IsEmpty() { + return Rect{} + } + maxLeft := max(r.X, other.X) + maxTop := max(r.Y, other.Y) + minRight := min(r.X+r.Width, other.X+other.Width) + minBottom := min(r.Y+r.Height, other.Y+other.Height) + if minRight > maxLeft && minBottom > maxTop { + return Rect{X: maxLeft, Y: maxTop, Width: minRight - maxLeft, Height: minBottom - maxTop} + } + return Rect{} +} + +// Screen describes a physical display. +// primary := manager.GetPrimary() +type Screen struct { + ID string + Name string + ScaleFactor float32 + X int + Y int + Size Size + Bounds Rect + PhysicalBounds Rect + WorkArea Rect + PhysicalWorkArea Rect + IsPrimary bool + Rotation float32 +} + +// Origin returns the logical origin of the screen. +// origin := screen.Origin() +func (s Screen) Origin() Point { return Point{X: s.X, Y: s.Y} } + +// scale converts a value between physical and DIP coordinates for this screen. +func (s Screen) scale(value int, toDIP bool) int { + if toDIP { + return int(math.Ceil(float64(value) / float64(s.ScaleFactor))) + } + return int(math.Floor(float64(value) * float64(s.ScaleFactor))) +} + +// ScreenManager holds the list of known screens in memory. +// screens := manager.GetAll() +type ScreenManager struct { + mu sync.RWMutex + screens []*Screen + primaryScreen *Screen +} + +// NewScreenManager constructs an empty ScreenManager. +// manager := NewScreenManager() +func NewScreenManager() *ScreenManager { + return &ScreenManager{} +} + +// SetScreens replaces the tracked screen list; the first screen with IsPrimary == true becomes primary. +// manager.SetScreens([]*Screen{primary, secondary}) +func (m *ScreenManager) SetScreens(screens []*Screen) { + m.mu.Lock() + defer m.mu.Unlock() + m.screens = screens + m.primaryScreen = nil + for _, screen := range screens { + if screen.IsPrimary { + m.primaryScreen = screen + break + } + } +} + +// GetAll returns all tracked screens. +// for _, s := range manager.GetAll() { fmt.Println(s.Name) } +func (m *ScreenManager) GetAll() []*Screen { + m.mu.RLock() + defer m.mu.RUnlock() + result := make([]*Screen, len(m.screens)) + copy(result, m.screens) + return result +} + +// GetPrimary returns the primary screen, or nil if none is set. +// primary := manager.GetPrimary() +func (m *ScreenManager) GetPrimary() *Screen { + m.mu.RLock() + defer m.mu.RUnlock() + return m.primaryScreen +} + +// GetCurrent returns the screen nearest to the given DIP point. +// current := manager.GetCurrent(Point{X: 500, Y: 300}) +func (m *ScreenManager) GetCurrent(dipPoint Point) *Screen { + m.mu.RLock() + defer m.mu.RUnlock() + for _, screen := range m.screens { + if screen.Bounds.Contains(dipPoint) { + return screen + } + } + return m.primaryScreen +} + +// DipToPhysicalPoint converts a DIP point to physical pixels using the nearest screen. +// physical := manager.DipToPhysicalPoint(Point{X: 100, Y: 200}) +func (m *ScreenManager) DipToPhysicalPoint(dipPoint Point) Point { + screen := m.GetCurrent(dipPoint) + if screen == nil { + return dipPoint + } + relativeX := dipPoint.X - screen.Bounds.X + relativeY := dipPoint.Y - screen.Bounds.Y + return Point{ + X: screen.PhysicalBounds.X + screen.scale(relativeX, false), + Y: screen.PhysicalBounds.Y + screen.scale(relativeY, false), + } +} + +// PhysicalToDipPoint converts physical pixel coordinates to DIP using the nearest screen. +// dip := manager.PhysicalToDipPoint(Point{X: 200, Y: 400}) +func (m *ScreenManager) PhysicalToDipPoint(physicalPoint Point) Point { + m.mu.RLock() + var nearest *Screen + for _, screen := range m.screens { + if screen.PhysicalBounds.Contains(physicalPoint) { + nearest = screen + break + } + } + if nearest == nil { + nearest = m.primaryScreen + } + m.mu.RUnlock() + if nearest == nil { + return physicalPoint + } + relativeX := physicalPoint.X - nearest.PhysicalBounds.X + relativeY := physicalPoint.Y - nearest.PhysicalBounds.Y + return Point{ + X: nearest.Bounds.X + nearest.scale(relativeX, true), + Y: nearest.Bounds.Y + nearest.scale(relativeY, true), + } +} diff --git a/stubs/wails/pkg/application/services.go b/stubs/wails/pkg/application/services.go new file mode 100644 index 0000000..e354670 --- /dev/null +++ b/stubs/wails/pkg/application/services.go @@ -0,0 +1,76 @@ +package application + +import "context" + +// Service wraps a bound type instance for registration with the application. +// svc := NewService(&MyService{}) +type Service struct { + instance any + options ServiceOptions +} + +// ServiceOptions provides optional configuration for a Service. +// opts := ServiceOptions{Name: "my-service", Route: "/api/my"} +type ServiceOptions struct { + // Name overrides the service name used in logging and debugging. + // Defaults to the value from ServiceName interface, or the type name. + Name string + + // Route mounts the service on the internal asset server at this path + // when the instance implements http.Handler. + Route string + + // MarshalError serialises errors returned by service methods to JSON. + // Return nil to fall back to the globally configured error handler. + MarshalError func(error) []byte +} + +// DefaultServiceOptions is the default service options used by NewService. +var DefaultServiceOptions = ServiceOptions{} + +// NewService wraps instance as a Service. +// svc := NewService(&Calculator{}) +func NewService[T any](instance *T) Service { + return Service{instance: instance, options: DefaultServiceOptions} +} + +// NewServiceWithOptions wraps instance as a Service with explicit options. +// svc := NewServiceWithOptions(&Calculator{}, ServiceOptions{Name: "calculator"}) +func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service { + service := NewService(instance) + service.options = options + return service +} + +// Instance returns the underlying pointer passed to NewService. +// raw := svc.Instance().(*Calculator) +func (s Service) Instance() any { + return s.instance +} + +// Options returns the ServiceOptions for this service. +// opts := svc.Options() +func (s Service) Options() ServiceOptions { + return s.options +} + +// ServiceName is an optional interface that service instances may implement +// to provide a human-readable name for logging and debugging. +// func (s *MyService) ServiceName() string { return "my-service" } +type ServiceName interface { + ServiceName() string +} + +// ServiceStartup is an optional interface for services that need initialisation. +// Called in registration order during application startup. +// func (s *MyService) ServiceStartup(ctx context.Context, opts ServiceOptions) error { ... } +type ServiceStartup interface { + ServiceStartup(ctx context.Context, options ServiceOptions) error +} + +// ServiceShutdown is an optional interface for services that need cleanup. +// Called in reverse registration order during application shutdown. +// func (s *MyService) ServiceShutdown() error { ... } +type ServiceShutdown interface { + ServiceShutdown() error +} diff --git a/stubs/wails/pkg/application/webview_window_options.go b/stubs/wails/pkg/application/webview_window_options.go new file mode 100644 index 0000000..eb76973 --- /dev/null +++ b/stubs/wails/pkg/application/webview_window_options.go @@ -0,0 +1,471 @@ +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +// WindowState represents the starting visual state of a window. +type WindowState int + +const ( + WindowStateNormal WindowState = iota + WindowStateMinimised WindowState = iota + WindowStateMaximised WindowState = iota + WindowStateFullscreen WindowState = iota +) + +// WindowStartPosition determines how the initial X/Y coordinates are interpreted. +type WindowStartPosition int + +const ( + // WindowCentered places the window in the centre of the screen on first show. + WindowCentered WindowStartPosition = 0 + // WindowXY places the window at the explicit X/Y coordinates. + WindowXY WindowStartPosition = 1 +) + +// BackgroundType controls window transparency. +type BackgroundType int + +const ( + BackgroundTypeSolid BackgroundType = iota + BackgroundTypeTransparent BackgroundType = iota + BackgroundTypeTranslucent BackgroundType = iota +) + +// WebviewWindowOptions holds all configuration for a webview window. +// +// opts := WebviewWindowOptions{ +// Name: "main", +// Title: "My App", +// Width: 1280, +// Height: 800, +// Mac: MacWindow{TitleBar: MacTitleBarHiddenInset}, +// } +type WebviewWindowOptions struct { + // Name is a unique identifier for the window. + Name string + // Title is shown in the title bar. + Title string + // Width is the initial width in logical pixels. + Width int + // Height is the initial height in logical pixels. + Height int + // AlwaysOnTop makes the window float above others. + AlwaysOnTop bool + // URL is the URL to load on creation. + URL string + // HTML is inline HTML to load (alternative to URL). + HTML string + // JS is inline JavaScript to inject. + JS string + // CSS is inline CSS to inject. + CSS string + // DisableResize prevents the user from resizing the window. + DisableResize bool + // Frameless removes the OS window frame. + Frameless bool + // MinWidth is the minimum allowed width. + MinWidth int + // MinHeight is the minimum allowed height. + MinHeight int + // MaxWidth is the maximum allowed width (0 = unlimited). + MaxWidth int + // MaxHeight is the maximum allowed height (0 = unlimited). + MaxHeight int + // StartState sets the visual state when first shown. + StartState WindowState + // BackgroundType controls transparency. + BackgroundType BackgroundType + // BackgroundColour is the solid background fill colour. + BackgroundColour RGBA + // InitialPosition controls how X/Y are interpreted. + InitialPosition WindowStartPosition + // X is the initial horizontal position. + X int + // Y is the initial vertical position. + Y int + // Hidden creates the window without showing it. + Hidden bool + // Zoom sets the initial zoom magnification (0 = default = 1.0). + Zoom float64 + // ZoomControlEnabled allows the user to change zoom. + ZoomControlEnabled bool + // EnableFileDrop enables drag-and-drop of files onto the window. + EnableFileDrop bool + // OpenInspectorOnStartup opens the web inspector when first shown. + OpenInspectorOnStartup bool + // DevToolsEnabled exposes the developer tools (default true in debug builds). + DevToolsEnabled bool + // DefaultContextMenuDisabled disables the built-in right-click menu. + DefaultContextMenuDisabled bool + // KeyBindings is a map of accelerator strings to window callbacks. + KeyBindings map[string]func(window Window) + // IgnoreMouseEvents passes all mouse events through (Windows + macOS only). + IgnoreMouseEvents bool + // ContentProtectionEnabled prevents screen capture (Windows + macOS only). + ContentProtectionEnabled bool + // HideOnFocusLost hides the window when it loses focus. + HideOnFocusLost bool + // HideOnEscape hides the window when Escape is pressed. + HideOnEscape bool + // UseApplicationMenu uses the application-level menu for this window. + UseApplicationMenu bool + // MinimiseButtonState controls the minimise button state. + MinimiseButtonState ButtonState + // MaximiseButtonState controls the maximise/zoom button state. + MaximiseButtonState ButtonState + // CloseButtonState controls the close button state. + CloseButtonState ButtonState + // Mac contains macOS-specific window options. + Mac MacWindow + // Windows contains Windows-specific window options. + Windows WindowsWindow + // Linux contains Linux-specific window options. + Linux LinuxWindow +} + +// ------------------------- +// macOS-specific types +// ------------------------- + +// MacBackdrop is the backdrop material for a macOS window. +type MacBackdrop int + +const ( + // MacBackdropNormal uses an opaque background. + MacBackdropNormal MacBackdrop = iota + // MacBackdropTransparent shows the desktop behind the window. + MacBackdropTransparent MacBackdrop = iota + // MacBackdropTranslucent applies a frosted-glass vibrancy effect. + MacBackdropTranslucent MacBackdrop = iota + // MacBackdropLiquidGlass uses the Apple Liquid Glass effect (macOS 15+). + MacBackdropLiquidGlass MacBackdrop = iota +) + +// MacToolbarStyle controls toolbar layout relative to the title bar. +type MacToolbarStyle int + +const ( + MacToolbarStyleAutomatic MacToolbarStyle = iota + MacToolbarStyleExpanded MacToolbarStyle = iota + MacToolbarStylePreference MacToolbarStyle = iota + MacToolbarStyleUnified MacToolbarStyle = iota + MacToolbarStyleUnifiedCompact MacToolbarStyle = iota +) + +// MacLiquidGlassStyle defines the tint of the Liquid Glass effect. +type MacLiquidGlassStyle int + +const ( + LiquidGlassStyleAutomatic MacLiquidGlassStyle = iota + LiquidGlassStyleLight MacLiquidGlassStyle = iota + LiquidGlassStyleDark MacLiquidGlassStyle = iota + LiquidGlassStyleVibrant MacLiquidGlassStyle = iota +) + +// NSVisualEffectMaterial maps to the NSVisualEffectMaterial macOS enum. +type NSVisualEffectMaterial int + +const ( + NSVisualEffectMaterialAppearanceBased NSVisualEffectMaterial = 0 + NSVisualEffectMaterialLight NSVisualEffectMaterial = 1 + NSVisualEffectMaterialDark NSVisualEffectMaterial = 2 + NSVisualEffectMaterialTitlebar NSVisualEffectMaterial = 3 + NSVisualEffectMaterialSelection NSVisualEffectMaterial = 4 + NSVisualEffectMaterialMenu NSVisualEffectMaterial = 5 + NSVisualEffectMaterialPopover NSVisualEffectMaterial = 6 + NSVisualEffectMaterialSidebar NSVisualEffectMaterial = 7 + NSVisualEffectMaterialHeaderView NSVisualEffectMaterial = 10 + NSVisualEffectMaterialSheet NSVisualEffectMaterial = 11 + NSVisualEffectMaterialWindowBackground NSVisualEffectMaterial = 12 + NSVisualEffectMaterialHUDWindow NSVisualEffectMaterial = 13 + NSVisualEffectMaterialFullScreenUI NSVisualEffectMaterial = 15 + NSVisualEffectMaterialToolTip NSVisualEffectMaterial = 17 + NSVisualEffectMaterialContentBackground NSVisualEffectMaterial = 18 + NSVisualEffectMaterialUnderWindowBackground NSVisualEffectMaterial = 21 + NSVisualEffectMaterialUnderPageBackground NSVisualEffectMaterial = 22 + NSVisualEffectMaterialAuto NSVisualEffectMaterial = -1 +) + +// MacLiquidGlass configures the Liquid Glass compositor effect. +type MacLiquidGlass struct { + Style MacLiquidGlassStyle + Material NSVisualEffectMaterial + CornerRadius float64 + TintColor *RGBA + GroupID string + GroupSpacing float64 +} + +// MacAppearanceType is the NSAppearance name string for a macOS window. +type MacAppearanceType string + +const ( + DefaultAppearance MacAppearanceType = "" + NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua" + NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua" + NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight" + NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua" + NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua" + NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight" + NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark" +) + +// MacWindowLevel controls Z-ordering relative to other windows. +type MacWindowLevel string + +const ( + MacWindowLevelNormal MacWindowLevel = "normal" + MacWindowLevelFloating MacWindowLevel = "floating" + MacWindowLevelTornOffMenu MacWindowLevel = "tornOffMenu" + MacWindowLevelModalPanel MacWindowLevel = "modalPanel" + MacWindowLevelMainMenu MacWindowLevel = "mainMenu" + MacWindowLevelStatus MacWindowLevel = "status" + MacWindowLevelPopUpMenu MacWindowLevel = "popUpMenu" + MacWindowLevelScreenSaver MacWindowLevel = "screenSaver" +) + +// MacWindowCollectionBehavior is a bitmask controlling Spaces and fullscreen behaviour. +type MacWindowCollectionBehavior int + +const ( + MacWindowCollectionBehaviorDefault MacWindowCollectionBehavior = 0 + MacWindowCollectionBehaviorCanJoinAllSpaces MacWindowCollectionBehavior = 1 << 0 + MacWindowCollectionBehaviorMoveToActiveSpace MacWindowCollectionBehavior = 1 << 1 + MacWindowCollectionBehaviorManaged MacWindowCollectionBehavior = 1 << 2 + MacWindowCollectionBehaviorTransient MacWindowCollectionBehavior = 1 << 3 + MacWindowCollectionBehaviorStationary MacWindowCollectionBehavior = 1 << 4 + MacWindowCollectionBehaviorParticipatesInCycle MacWindowCollectionBehavior = 1 << 5 + MacWindowCollectionBehaviorIgnoresCycle MacWindowCollectionBehavior = 1 << 6 + MacWindowCollectionBehaviorFullScreenPrimary MacWindowCollectionBehavior = 1 << 7 + MacWindowCollectionBehaviorFullScreenAuxiliary MacWindowCollectionBehavior = 1 << 8 + MacWindowCollectionBehaviorFullScreenNone MacWindowCollectionBehavior = 1 << 9 + MacWindowCollectionBehaviorFullScreenAllowsTiling MacWindowCollectionBehavior = 1 << 11 + MacWindowCollectionBehaviorFullScreenDisallowsTiling MacWindowCollectionBehavior = 1 << 12 +) + +// MacWebviewPreferences holds WKWebView preference flags for macOS. +// Use integer tristate: 0 = unset, 1 = true, 2 = false. +type MacWebviewPreferences struct { + TabFocusesLinks int + TextInteractionEnabled int + FullscreenEnabled int + AllowsBackForwardNavigationGestures int +} + +// MacTitleBar configures the macOS title bar appearance. +type MacTitleBar struct { + // AppearsTransparent removes the title bar background. + AppearsTransparent bool + // Hide removes the title bar entirely. + Hide bool + // HideTitle hides only the text title. + HideTitle bool + // FullSizeContent extends window content into the title bar area. + FullSizeContent bool + // UseToolbar replaces the title bar with an NSToolbar. + UseToolbar bool + // HideToolbarSeparator removes the line between toolbar and content. + HideToolbarSeparator bool + // ShowToolbarWhenFullscreen keeps the toolbar visible in fullscreen. + ShowToolbarWhenFullscreen bool + // ToolbarStyle selects the toolbar layout style. + ToolbarStyle MacToolbarStyle +} + +// MacWindow contains macOS-specific window options. +type MacWindow struct { + Backdrop MacBackdrop + DisableShadow bool + TitleBar MacTitleBar + Appearance MacAppearanceType + InvisibleTitleBarHeight int + EventMapping map[events.WindowEventType]events.WindowEventType + EnableFraudulentWebsiteWarnings bool + WebviewPreferences MacWebviewPreferences + WindowLevel MacWindowLevel + CollectionBehavior MacWindowCollectionBehavior + LiquidGlass MacLiquidGlass +} + +// Pre-built MacTitleBar configurations — use directly in MacWindow.TitleBar. + +// MacTitleBarDefault produces the standard macOS title bar. +// +// Mac: MacWindow{TitleBar: MacTitleBarDefault} +var MacTitleBarDefault = MacTitleBar{} + +// MacTitleBarHidden hides the title text while keeping the traffic-light buttons. +// +// Mac: MacWindow{TitleBar: MacTitleBarHidden} +var MacTitleBarHidden = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, +} + +// MacTitleBarHiddenInset keeps traffic lights slightly inset from the window edge. +// +// Mac: MacWindow{TitleBar: MacTitleBarHiddenInset} +var MacTitleBarHiddenInset = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, +} + +// MacTitleBarHiddenInsetUnified uses the unified toolbar style for a more compact look. +// +// Mac: MacWindow{TitleBar: MacTitleBarHiddenInsetUnified} +var MacTitleBarHiddenInsetUnified = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, + ToolbarStyle: MacToolbarStyleUnified, +} + +// ------------------------- +// Windows-specific types +// ------------------------- + +// BackdropType selects the Windows 11 compositor effect. +type BackdropType int32 + +const ( + Auto BackdropType = 0 + None BackdropType = 1 + Mica BackdropType = 2 + Acrylic BackdropType = 3 + Tabbed BackdropType = 4 +) + +// Theme selects dark or light mode on Windows. +type Theme int + +const ( + SystemDefault Theme = 0 + Dark Theme = 1 + Light Theme = 2 +) + +// WindowTheme holds custom title-bar colours for a Windows window. +type WindowTheme struct { + BorderColour *uint32 + TitleBarColour *uint32 + TitleTextColour *uint32 +} + +// TextTheme holds foreground/background colour pair for menu text. +type TextTheme struct { + Text *uint32 + Background *uint32 +} + +// MenuBarTheme holds per-state text themes for the Windows menu bar. +type MenuBarTheme struct { + Default *TextTheme + Hover *TextTheme + Selected *TextTheme +} + +// ThemeSettings holds custom colours for dark/light mode on Windows. +// Colours use 0x00BBGGRR encoding. +type ThemeSettings struct { + DarkModeActive *WindowTheme + DarkModeInactive *WindowTheme + LightModeActive *WindowTheme + LightModeInactive *WindowTheme + DarkModeMenuBar *MenuBarTheme + LightModeMenuBar *MenuBarTheme +} + +// CoreWebView2PermissionKind identifies a WebView2 permission category. +type CoreWebView2PermissionKind uint32 + +const ( + CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota + CoreWebView2PermissionKindMicrophone + CoreWebView2PermissionKindCamera + CoreWebView2PermissionKindGeolocation + CoreWebView2PermissionKindNotifications + CoreWebView2PermissionKindOtherSensors + CoreWebView2PermissionKindClipboardRead +) + +// CoreWebView2PermissionState sets whether a permission is granted. +type CoreWebView2PermissionState uint32 + +const ( + CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota + CoreWebView2PermissionStateAllow + CoreWebView2PermissionStateDeny +) + +// WindowsWindow contains Windows-specific window options. +type WindowsWindow struct { + BackdropType BackdropType + DisableIcon bool + Theme Theme + CustomTheme ThemeSettings + DisableFramelessWindowDecorations bool + WindowMask []byte + WindowMaskDraggable bool + ResizeDebounceMS uint16 + WindowDidMoveDebounceMS uint16 + EventMapping map[events.WindowEventType]events.WindowEventType + HiddenOnTaskbar bool + EnableSwipeGestures bool + Menu *Menu + Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState + ExStyle int + GeneralAutofillEnabled bool + PasswordAutosaveEnabled bool +} + +// ------------------------- +// Linux-specific types +// ------------------------- + +// WebviewGpuPolicy controls GPU acceleration for the Linux webview. +type WebviewGpuPolicy int + +const ( + WebviewGpuPolicyAlways WebviewGpuPolicy = iota + WebviewGpuPolicyOnDemand WebviewGpuPolicy = iota + WebviewGpuPolicyNever WebviewGpuPolicy = iota +) + +// LinuxMenuStyle selects how the application menu is rendered on Linux. +type LinuxMenuStyle int + +const ( + LinuxMenuStyleMenuBar LinuxMenuStyle = iota + LinuxMenuStylePrimaryMenu LinuxMenuStyle = iota +) + +// LinuxWindow contains Linux-specific window options. +type LinuxWindow struct { + Icon []byte + WindowIsTranslucent bool + WebviewGpuPolicy WebviewGpuPolicy + WindowDidMoveDebounceMS uint16 + Menu *Menu + MenuStyle LinuxMenuStyle +} + +// NewRGB constructs an RGBA value with full opacity from RGB components. +// +// colour := NewRGB(255, 128, 0) +func NewRGB(red, green, blue uint8) RGBA { + return RGBA{Red: red, Green: green, Blue: blue, Alpha: 255} +} + +// NewRGBPtr encodes RGB as a packed uint32 pointer (0x00BBGGRR) for ThemeSettings. +// +// theme.BorderColour = NewRGBPtr(255, 0, 0) +func NewRGBPtr(red, green, blue uint8) *uint32 { + result := uint32(red) | uint32(green)<<8 | uint32(blue)<<16 + return &result +} diff --git a/stubs/wails/pkg/application/window.go b/stubs/wails/pkg/application/window.go new file mode 100644 index 0000000..13bb641 --- /dev/null +++ b/stubs/wails/pkg/application/window.go @@ -0,0 +1,160 @@ +package application + +import ( + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Window is the interface satisfied by all window types in the application. +// Fluent mutating methods return Window so callers can chain calls: +// +// app.Window.NewWithOptions(opts).SetTitle("Main").Show() +type Window interface { + // Identity + ID() uint + Name() string + + // Visibility + Show() Window + Hide() Window + IsVisible() bool + + // Lifecycle + Close() + Focus() + Run() + + // Geometry + Center() + Position() (x int, y int) + RelativePosition() (x int, y int) + Size() (width int, height int) + Width() int + Height() int + Bounds() Rect + SetPosition(x, y int) + SetRelativePosition(x, y int) Window + SetSize(width, height int) Window + SetBounds(bounds Rect) + SetMaxSize(maxWidth, maxHeight int) Window + SetMinSize(minWidth, minHeight int) Window + EnableSizeConstraints() + DisableSizeConstraints() + Resizable() bool + SetResizable(b bool) Window + + // State + Maximise() Window + UnMaximise() + ToggleMaximise() + IsMaximised() bool + Minimise() Window + UnMinimise() + IsMinimised() bool + Fullscreen() Window + UnFullscreen() + ToggleFullscreen() + IsFullscreen() bool + Restore() + SnapAssist() + + // Title and content + SetTitle(title string) Window + SetURL(s string) Window + SetHTML(html string) Window + + // Titlebar buttons (macOS / Windows) + SetMinimiseButtonState(state ButtonState) Window + SetMaximiseButtonState(state ButtonState) Window + SetCloseButtonState(state ButtonState) Window + + // Menu bar + SetMenu(menu *Menu) + ShowMenuBar() + HideMenuBar() + ToggleMenuBar() + + // Appearance + SetBackgroundColour(colour RGBA) Window + SetAlwaysOnTop(b bool) Window + SetFrameless(frameless bool) Window + ToggleFrameless() + SetIgnoreMouseEvents(ignore bool) Window + IsIgnoreMouseEvents() bool + SetContentProtection(protection bool) Window + + // Zoom + GetZoom() float64 + SetZoom(magnification float64) Window + Zoom() + ZoomIn() + ZoomOut() + ZoomReset() Window + + // Border sizes (Windows) + GetBorderSizes() *LRTB + + // Screen + GetScreen() (*Screen, error) + + // JavaScript / events + ExecJS(js string) + EmitEvent(name string, data ...any) bool + DispatchWailsEvent(event *CustomEvent) + OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + + // Drag-and-drop (internal message bus) + handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) + InitiateFrontendDropProcessing(filenames []string, x int, y int) + + // Message handling (internal) + HandleMessage(message string) + HandleWindowEvent(id uint) + HandleKeyEvent(acceleratorString string) + + // Context menu + OpenContextMenu(data *ContextMenuData) + + // Modal + AttachModal(modalWindow Window) + + // DevTools + OpenDevTools() + + // Print + Print() error + + // Flash (Windows taskbar flash) + Flash(enabled bool) + + // Focus tracking + IsFocused() bool + + // Native handle (platform-specific, use with care) + NativeWindow() unsafe.Pointer + + // Enabled state + SetEnabled(enabled bool) + + // Reload + Reload() + ForceReload() + + // Logging (routed to the application logger) + Info(message string, args ...any) + Error(message string, args ...any) + + // Internal hooks + shouldUnconditionallyClose() bool + + // Editing operations (routed to focused element) + cut() + copy() + paste() + undo() + redo() + delete() + selectAll() +} diff --git a/stubs/wails/pkg/events/events.go b/stubs/wails/pkg/events/events.go index 3f3204d..80d67b1 100644 --- a/stubs/wails/pkg/events/events.go +++ b/stubs/wails/pkg/events/events.go @@ -1,5 +1,11 @@ package events +// ApplicationEventType identifies a platform-level application event. +// Matches the type used by the real Wails v3 package. +// +// em.OnApplicationEvent(events.Common.ThemeChanged, handler) +type ApplicationEventType uint + // WindowEventType identifies a window event emitted by the application layer. type WindowEventType int @@ -14,17 +20,25 @@ const ( // Common matches the event namespace used by the real Wails package. var Common = struct { - WindowFocus WindowEventType - WindowLostFocus WindowEventType - WindowDidMove WindowEventType - WindowDidResize WindowEventType - WindowClosing WindowEventType - WindowFilesDropped WindowEventType + ApplicationOpenedWithFile ApplicationEventType + ApplicationStarted ApplicationEventType + ApplicationLaunchedWithUrl ApplicationEventType + ThemeChanged ApplicationEventType + WindowFocus WindowEventType + WindowLostFocus WindowEventType + WindowDidMove WindowEventType + WindowDidResize WindowEventType + WindowClosing WindowEventType + WindowFilesDropped WindowEventType }{ - WindowFocus: WindowFocus, - WindowLostFocus: WindowLostFocus, - WindowDidMove: WindowDidMove, - WindowDidResize: WindowDidResize, - WindowClosing: WindowClosing, - WindowFilesDropped: WindowFilesDropped, + ApplicationOpenedWithFile: 1024, + ApplicationStarted: 1025, + ApplicationLaunchedWithUrl: 1026, + ThemeChanged: 1027, + WindowFocus: WindowFocus, + WindowLostFocus: WindowLostFocus, + WindowDidMove: WindowDidMove, + WindowDidResize: WindowDidResize, + WindowClosing: WindowClosing, + WindowFilesDropped: WindowFilesDropped, }