diff --git a/stubs/wails/pkg/application/application.go b/stubs/wails/pkg/application/application.go index 211611c..9f3937e 100644 --- a/stubs/wails/pkg/application/application.go +++ b/stubs/wails/pkg/application/application.go @@ -2,6 +2,7 @@ package application import ( "sync" + "unsafe" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -238,10 +239,11 @@ func (w *WebviewWindow) IsFocused() bool { return w.focused } -func (w *WebviewWindow) SetTitle(title string) { +func (w *WebviewWindow) SetTitle(title string) Window { w.mu.Lock() w.title = title w.mu.Unlock() + return w } func (w *WebviewWindow) SetPosition(x, y int) { @@ -251,25 +253,28 @@ func (w *WebviewWindow) SetPosition(x, y int) { w.mu.Unlock() } -func (w *WebviewWindow) SetSize(width, height int) { +func (w *WebviewWindow) SetSize(width, height int) Window { w.mu.Lock() w.width = width w.height = height w.mu.Unlock() + return w } -func (w *WebviewWindow) SetBackgroundColour(colour RGBA) {} +func (w *WebviewWindow) SetBackgroundColour(colour RGBA) Window { return w } -func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) { +func (w *WebviewWindow) SetAlwaysOnTop(alwaysOnTop bool) Window { w.mu.Lock() w.alwaysOnTop = alwaysOnTop w.mu.Unlock() + return w } -func (w *WebviewWindow) Maximise() { +func (w *WebviewWindow) Maximise() Window { w.mu.Lock() w.maximised = true w.mu.Unlock() + return w } func (w *WebviewWindow) Restore() { @@ -279,7 +284,7 @@ func (w *WebviewWindow) Restore() { w.mu.Unlock() } -func (w *WebviewWindow) Minimise() {} +func (w *WebviewWindow) Minimise() Window { return w } func (w *WebviewWindow) Focus() { w.mu.Lock() @@ -293,22 +298,25 @@ func (w *WebviewWindow) Close() { w.mu.Unlock() } -func (w *WebviewWindow) Show() { +func (w *WebviewWindow) Show() Window { w.mu.Lock() w.visible = true w.mu.Unlock() + return w } -func (w *WebviewWindow) Hide() { +func (w *WebviewWindow) Hide() Window { w.mu.Lock() w.visible = false w.mu.Unlock() + return w } -func (w *WebviewWindow) Fullscreen() { +func (w *WebviewWindow) Fullscreen() Window { w.mu.Lock() w.fullscreen = true w.mu.Unlock() + return w } func (w *WebviewWindow) UnFullscreen() { @@ -324,6 +332,376 @@ func (w *WebviewWindow) OnWindowEvent(eventType events.WindowEventType, callback return func() {} } +// ID returns a stable numeric identifier for this window. +// +// id := w.ID() +func (w *WebviewWindow) ID() uint { return 0 } + +// ClientID returns the client identifier (empty for native windows). +// +// cid := w.ClientID() +func (w *WebviewWindow) ClientID() string { return "" } + +// Width returns the current window width in logical pixels. +// +// px := w.Width() +func (w *WebviewWindow) Width() int { + w.mu.RLock() + defer w.mu.RUnlock() + return w.width +} + +// Height returns the current window height in logical pixels. +// +// px := w.Height() +func (w *WebviewWindow) Height() int { + w.mu.RLock() + defer w.mu.RUnlock() + return w.height +} + +// IsVisible reports whether the window is currently shown. +// +// if w.IsVisible() { ... } +func (w *WebviewWindow) IsVisible() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.visible +} + +// IsFullscreen reports whether the window is in fullscreen mode. +// +// if w.IsFullscreen() { ... } +func (w *WebviewWindow) IsFullscreen() bool { + w.mu.RLock() + defer w.mu.RUnlock() + return w.fullscreen +} + +// IsMinimised reports whether the window is minimised. +// +// if w.IsMinimised() { ... } +func (w *WebviewWindow) IsMinimised() bool { return false } + +// IsIgnoreMouseEvents reports whether mouse events are being suppressed. +// +// if w.IsIgnoreMouseEvents() { ... } +func (w *WebviewWindow) IsIgnoreMouseEvents() bool { return false } + +// Resizable reports whether the window can be resized by the user. +// +// if w.Resizable() { ... } +func (w *WebviewWindow) Resizable() bool { return true } + +// Bounds returns the current position and size as a Rect. +// +// r := w.Bounds() +func (w *WebviewWindow) Bounds() Rect { + w.mu.RLock() + defer w.mu.RUnlock() + return Rect{X: w.x, Y: w.y, Width: w.width, Height: w.height} +} + +// SetBounds sets position and size simultaneously. +// +// w.SetBounds(Rect{X: 100, Y: 100, Width: 1280, Height: 800}) +func (w *WebviewWindow) SetBounds(bounds Rect) { + w.mu.Lock() + w.x, w.y, w.width, w.height = bounds.X, bounds.Y, bounds.Width, bounds.Height + w.mu.Unlock() +} + +// RelativePosition returns the position relative to the screen origin. +// +// rx, ry := w.RelativePosition() +func (w *WebviewWindow) RelativePosition() (int, int) { + w.mu.RLock() + defer w.mu.RUnlock() + return w.x, w.y +} + +// SetRelativePosition sets the position relative to the screen. +// +// w.SetRelativePosition(0, 0) +func (w *WebviewWindow) SetRelativePosition(x, y int) Window { + w.mu.Lock() + w.x = x + w.y = y + w.mu.Unlock() + return w +} + +// SetMinSize sets the minimum window dimensions. +// +// w.SetMinSize(640, 480) +func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) Window { return w } + +// SetMaxSize sets the maximum window dimensions. +// +// w.SetMaxSize(3840, 2160) +func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { return w } + +// Center positions the window at the centre of the screen. +// +// w.Center() +func (w *WebviewWindow) Center() {} + +// SetURL navigates the webview to the given URL. +// +// w.SetURL("https://example.com") +func (w *WebviewWindow) SetURL(url string) Window { return w } + +// SetHTML replaces the webview content with the given HTML string. +// +// w.SetHTML("

Hello

") +func (w *WebviewWindow) SetHTML(html string) Window { return w } + +// SetFrameless toggles the window frame. +// +// w.SetFrameless(true) +func (w *WebviewWindow) SetFrameless(frameless bool) Window { return w } + +// SetResizable controls whether the user can resize the window. +// +// w.SetResizable(false) +func (w *WebviewWindow) SetResizable(b bool) Window { return w } + +// SetIgnoreMouseEvents suppresses or restores mouse event delivery. +// +// w.SetIgnoreMouseEvents(true) +func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window { return w } + +// SetMinimiseButtonState controls the minimise button appearance. +// +// w.SetMinimiseButtonState(ButtonHidden) +func (w *WebviewWindow) SetMinimiseButtonState(state ButtonState) Window { return w } + +// SetMaximiseButtonState controls the maximise button appearance. +// +// w.SetMaximiseButtonState(ButtonDisabled) +func (w *WebviewWindow) SetMaximiseButtonState(state ButtonState) Window { return w } + +// SetCloseButtonState controls the close button appearance. +// +// w.SetCloseButtonState(ButtonEnabled) +func (w *WebviewWindow) SetCloseButtonState(state ButtonState) Window { return w } + +// SetEnabled enables or disables user interaction with the window. +// +// w.SetEnabled(false) +func (w *WebviewWindow) SetEnabled(enabled bool) {} + +// SetContentProtection prevents the window contents from being captured. +// +// w.SetContentProtection(true) +func (w *WebviewWindow) SetContentProtection(protection bool) Window { return w } + +// SetMenu attaches a menu to the window. +// +// w.SetMenu(myMenu) +func (w *WebviewWindow) SetMenu(menu *Menu) {} + +// ShowMenuBar makes the menu bar visible. +// +// w.ShowMenuBar() +func (w *WebviewWindow) ShowMenuBar() {} + +// HideMenuBar hides the menu bar. +// +// w.HideMenuBar() +func (w *WebviewWindow) HideMenuBar() {} + +// ToggleMenuBar toggles menu bar visibility. +// +// w.ToggleMenuBar() +func (w *WebviewWindow) ToggleMenuBar() {} + +// ToggleFrameless toggles the window frame. +// +// w.ToggleFrameless() +func (w *WebviewWindow) ToggleFrameless() {} + +// ExecJS executes a JavaScript string in the webview. +// +// w.ExecJS("document.title = 'Ready'") +func (w *WebviewWindow) ExecJS(js string) {} + +// Reload reloads the current page. +// +// w.Reload() +func (w *WebviewWindow) Reload() {} + +// ForceReload bypasses the cache and reloads. +// +// w.ForceReload() +func (w *WebviewWindow) ForceReload() {} + +// OpenDevTools opens the browser developer tools panel. +// +// w.OpenDevTools() +func (w *WebviewWindow) OpenDevTools() {} + +// OpenContextMenu triggers a named context menu at the given position. +// +// w.OpenContextMenu(&ContextMenuData{Name: "edit", X: 100, Y: 200}) +func (w *WebviewWindow) OpenContextMenu(data *ContextMenuData) {} + +// Zoom applies the default zoom level. +// +// w.Zoom() +func (w *WebviewWindow) Zoom() {} + +// ZoomIn increases the zoom level by one step. +// +// w.ZoomIn() +func (w *WebviewWindow) ZoomIn() {} + +// ZoomOut decreases the zoom level by one step. +// +// w.ZoomOut() +func (w *WebviewWindow) ZoomOut() {} + +// ZoomReset returns the zoom level to 1.0. +// +// w.ZoomReset() +func (w *WebviewWindow) ZoomReset() Window { return w } + +// GetZoom returns the current zoom magnification factor. +// +// z := w.GetZoom() +func (w *WebviewWindow) GetZoom() float64 { return 1.0 } + +// SetZoom sets the zoom magnification factor. +// +// w.SetZoom(1.5) +func (w *WebviewWindow) SetZoom(magnification float64) Window { return w } + +// RegisterHook registers a pre-event hook for the given window event type. +// +// cancel := w.RegisterHook(events.Common.WindowClose, func(e *WindowEvent) { saveState() }) +// defer cancel() +func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return func() {} +} + +// EmitEvent fires a named event from this window. +// +// w.EmitEvent("user:login", payload) +func (w *WebviewWindow) EmitEvent(name string, data ...any) bool { return false } + +// DispatchWailsEvent sends a custom event through the Wails event bus. +// +// w.DispatchWailsEvent(&CustomEvent{Name: "init"}) +func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) {} + +// GetScreen returns the screen on which this window is currently displayed. +// +// screen, err := w.GetScreen() +func (w *WebviewWindow) GetScreen() (*Screen, error) { return nil, nil } + +// GetBorderSizes returns the platform-specific window border dimensions. +// +// borders := w.GetBorderSizes() +func (w *WebviewWindow) GetBorderSizes() *LRTB { return nil } + +// EnableSizeConstraints activates the min/max size limits. +// +// w.EnableSizeConstraints() +func (w *WebviewWindow) EnableSizeConstraints() {} + +// DisableSizeConstraints removes the min/max size limits. +// +// w.DisableSizeConstraints() +func (w *WebviewWindow) DisableSizeConstraints() {} + +// AttachModal registers a modal window that blocks this window. +// +// w.AttachModal(confirmDialog) +func (w *WebviewWindow) AttachModal(modalWindow Window) {} + +// Flash requests the window manager to flash or bounce this window. +// +// w.Flash(true) +func (w *WebviewWindow) Flash(enabled bool) {} + +// Print opens the system print dialog for the webview contents. +// +// err := w.Print() +func (w *WebviewWindow) Print() error { return nil } + +// Error logs an error-level message on behalf of this window. +// +// w.Error("load failed: %s", err) +func (w *WebviewWindow) Error(message string, args ...any) {} + +// Info logs an info-level message on behalf of this window. +// +// w.Info("window ready") +func (w *WebviewWindow) Info(message string, args ...any) {} + +// NativeWindow returns the platform-specific window handle (nil in stub). +// +// ptr := w.NativeWindow() +func (w *WebviewWindow) NativeWindow() unsafe.Pointer { return nil } + +// Run starts the window event loop. +// +// w.Run() +func (w *WebviewWindow) Run() {} + +// UnMaximise restores the window from maximised state. +// +// w.UnMaximise() +func (w *WebviewWindow) UnMaximise() { + w.mu.Lock() + w.maximised = false + w.mu.Unlock() +} + +// UnMinimise restores the window from minimised state. +// +// w.UnMinimise() +func (w *WebviewWindow) UnMinimise() {} + +// ToggleFullscreen switches between fullscreen and windowed mode. +// +// w.ToggleFullscreen() +func (w *WebviewWindow) ToggleFullscreen() { + w.mu.Lock() + w.fullscreen = !w.fullscreen + w.mu.Unlock() +} + +// ToggleMaximise switches between maximised and restored state. +// +// w.ToggleMaximise() +func (w *WebviewWindow) ToggleMaximise() { + w.mu.Lock() + w.maximised = !w.maximised + w.mu.Unlock() +} + +// SnapAssist triggers the platform snap-assist feature. +// +// w.SnapAssist() +func (w *WebviewWindow) SnapAssist() {} + +// Internal platform hooks — no-ops in the stub. + +func (w *WebviewWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) {} +func (w *WebviewWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) {} +func (w *WebviewWindow) HandleMessage(message string) {} +func (w *WebviewWindow) HandleWindowEvent(id uint) {} +func (w *WebviewWindow) HandleKeyEvent(acceleratorString string) {} +func (w *WebviewWindow) shouldUnconditionallyClose() bool { return false } +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() {} + // WindowManager manages in-memory windows. type WindowManager struct { mu sync.RWMutex @@ -338,10 +716,13 @@ func (wm *WindowManager) NewWithOptions(options WebviewWindowOptions) *WebviewWi return window } -func (wm *WindowManager) GetAll() []any { +// GetAll returns all windows managed by this manager. +// +// for _, w := range wm.GetAll() { w.Show() } +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) } @@ -349,11 +730,23 @@ func (wm *WindowManager) GetAll() []any { } // App is the top-level application object used by the GUI packages. +// +// app := &application.App{} +// app.Dialog.Info().SetTitle("Done").SetMessage("Saved.").Show() +// app.Event.Emit("user:login", payload) type App struct { - Logger Logger - Window WindowManager - Menu MenuManager - SystemTray SystemTrayManager + Logger Logger + Window WindowManager + Menu MenuManager + SystemTray SystemTrayManager + Dialog DialogManager + Event EventManager + Browser BrowserManager + Clipboard ClipboardManager + ContextMenu ContextMenuManager + Environment EnvironmentManager + Screen ScreenManager + KeyBinding KeyBindingManager } func (a *App) Quit() {} diff --git a/stubs/wails/pkg/application/application_options.go b/stubs/wails/pkg/application/application_options.go new file mode 100644 index 0000000..72f3ec4 --- /dev/null +++ b/stubs/wails/pkg/application/application_options.go @@ -0,0 +1,382 @@ +package application + +// Handler is a stub for net/http.Handler. +// In the real Wails runtime this is http.Handler; the stub replaces it with +// an interface so the application package compiles without importing net/http. +// +// var h Handler = myHTTPHandler +type Handler interface { + ServeHTTP(w ResponseWriter, r *Request) +} + +// ResponseWriter is a minimal stub for http.ResponseWriter. +type ResponseWriter interface { + Header() map[string][]string + Write([]byte) (int, error) + WriteHeader(statusCode int) +} + +// Request is a minimal stub for *http.Request. +type Request struct { + Method string + URL string + Header map[string][]string + Body []byte +} + +// FS is a stub for fs.FS (filesystem abstraction). +// In the real Wails runtime this is io/fs.FS. +type FS interface { + Open(name string) (interface{ Read([]byte) (int, error) }, error) +} + +// Duration is a stub for time.Duration (nanoseconds). +type Duration = int64 + +// LogLevel is a stub for slog.Level. +type LogLevel = int + +// Logger is a stub for *slog.Logger. +// In production this is the standard library structured logger. +type SlogLogger struct{} + +// Middleware defines HTTP middleware applied to the AssetServer. +// The handler passed as next is the next handler in the chain. +// +// Middleware: func(next application.Handler) application.Handler { return myHandler } +type Middleware func(next Handler) Handler + +// ChainMiddleware chains multiple middlewares into one. +// +// chained := application.ChainMiddleware(auth, logging, cors) +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h Handler) Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} + +// AssetFileServerFS returns a handler serving assets from an FS. +// In the stub this returns nil — no real file serving occurs. +// +// opts.Assets.Handler = application.AssetFileServerFS(embedFS) +func AssetFileServerFS(assets FS) Handler { return nil } + +// BundledAssetFileServer returns a handler serving bundled assets. +// In the stub this returns nil — no real file serving occurs. +// +// opts.Assets.Handler = application.BundledAssetFileServer(embedFS) +func BundledAssetFileServer(assets FS) Handler { return nil } + +// ActivationPolicy controls the macOS application activation policy. +// +// Mac: MacOptions{ActivationPolicy: ActivationPolicyAccessory} +type ActivationPolicy int + +const ( + // ActivationPolicyRegular is for applications with a user interface. + ActivationPolicyRegular ActivationPolicy = iota + // ActivationPolicyAccessory is for menu-bar or background applications. + ActivationPolicyAccessory + // ActivationPolicyProhibited disables activation entirely. + ActivationPolicyProhibited +) + +// NativeTabIcon is an SF Symbols name string used for iOS tab bar icons. +// +// NativeTabsItems: []NativeTabItem{{Title: "Home", SystemImage: NativeTabIconHouse}} +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" +) + +// NativeTabItem describes a single iOS UITabBar item. +// +// NativeTabsItems: []NativeTabItem{{Title: "Settings", SystemImage: NativeTabIconGear}} +type NativeTabItem struct { + Title string `json:"Title"` + SystemImage NativeTabIcon `json:"SystemImage"` +} + +// PanicDetails carries the information delivered to the panic handler. +// +// opts.PanicHandler = func(details *application.PanicDetails) { log(details.Message) } +type PanicDetails struct { + // Message is the string form of the recovered panic value. + Message string + // Stack is the formatted goroutine stack trace. + Stack string +} + +// Transport is the interface for custom IPC transport layers. +// Implement this to replace the default HTTP fetch + js.Exec transport. +// +// opts.Transport = myWebSocketTransport +type Transport interface{} + +// SingleInstanceOptions configures the single-instance lock behaviour. +// +// opts.SingleInstance = &application.SingleInstanceOptions{UniqueID: "com.example.myapp"} +type SingleInstanceOptions struct { + // UniqueID is the identifier used to detect duplicate instances. + UniqueID string + // OnSecondInstanceLaunch is called in the first instance when a second + // one starts. Receives the arguments passed to the second instance. + OnSecondInstanceLaunch func(secondInstanceData SecondInstanceData) +} + +// SecondInstanceData carries data from a second application instance launch. +// +// func handler(data application.SecondInstanceData) { openFile(data.Args[0]) } +type SecondInstanceData struct { + // Args are the command-line arguments of the second instance. + Args []string + // WorkingDirectory is the cwd of the second instance. + WorkingDirectory string +} + +// OriginInfo carries the origin details of a frontend message. +// +// opts.RawMessageHandler = func(w Window, msg string, info *application.OriginInfo) {} +type OriginInfo struct { + // Origin is the origin of the frame that sent the message. + Origin string + // TopOrigin is the origin of the top-level frame. + TopOrigin string + // IsMainFrame is true when the message came from the main frame. + IsMainFrame bool +} + +// AssetOptions configures the embedded asset server. +// +// opts.Assets = application.AssetOptions{Handler: application.AssetFileServerFS(embedFS)} +type AssetOptions struct { + // Handler serves all content to the WebView. + Handler Handler + + // Middleware hooks into the AssetServer request chain. + // Multiple middlewares can be composed with ChainMiddleware. + Middleware Middleware + + // DisableLogging suppresses per-request AssetServer log output. + DisableLogging bool +} + +// 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 +} + +// ServerOptions configures the HTTP server used in headless (server) mode. +// Enable server mode by building with: go build -tags server +// +// opts.Server = application.ServerOptions{Host: "0.0.0.0", Port: 8080} +type ServerOptions struct { + // Host is the address to bind to. Defaults to "localhost". + Host string + // Port is the port to listen on. Defaults to 8080. + Port int + // ReadTimeout is the maximum duration for reading a request (nanoseconds). + ReadTimeout Duration + // WriteTimeout is the maximum duration for writing a response (nanoseconds). + WriteTimeout Duration + // IdleTimeout is the maximum idle connection duration (nanoseconds). + IdleTimeout Duration + // ShutdownTimeout is the maximum time to wait for graceful shutdown (nanoseconds). + ShutdownTimeout Duration + // TLS configures HTTPS. If nil, plain HTTP is used. + TLS *TLSOptions +} + +// MacOptions contains macOS-specific application configuration. +// +// opts.Mac = application.MacOptions{ActivationPolicy: application.ActivationPolicyRegular} +type MacOptions struct { + // ActivationPolicy controls how the app interacts with the Dock and menu bar. + ActivationPolicy ActivationPolicy + // ApplicationShouldTerminateAfterLastWindowClosed quits the app when the + // last window closes (matches NSApplicationDelegate behaviour). + ApplicationShouldTerminateAfterLastWindowClosed bool +} + +// WindowsOptions contains Windows-specific application configuration. +// +// opts.Windows = application.WindowsOptions{WndClass: "MyAppWindow"} +type WindowsOptions struct { + // WndClass is the Win32 window class name. Default: WailsWebviewWindow. + WndClass string + // WndProcInterceptor intercepts all Win32 messages for the application. + 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 is the directory for WebView2 user data. + WebviewUserDataPath string + // WebviewBrowserPath is the directory containing WebView2 executables. + WebviewBrowserPath string + // EnabledFeatures lists WebView2 feature flags to enable. + EnabledFeatures []string + // DisabledFeatures lists WebView2 feature flags to disable. + DisabledFeatures []string + // AdditionalBrowserArgs are extra browser arguments (must include "--" prefix). + AdditionalBrowserArgs []string +} + +// LinuxOptions contains Linux-specific application configuration. +// +// opts.Linux = application.LinuxOptions{ProgramName: "myapp"} +type LinuxOptions struct { + // DisableQuitOnLastWindowClosed prevents auto-quit when the last window closes. + DisableQuitOnLastWindowClosed bool + // ProgramName sets g_set_prgname() for the window manager. + ProgramName string +} + +// IOSOptions contains iOS-specific application configuration. +// +// opts.IOS = application.IOSOptions{EnableInlineMediaPlayback: true} +type IOSOptions struct { + // DisableInputAccessoryView hides the iOS keyboard accessory bar. + DisableInputAccessoryView bool + // DisableScroll disables WebView scrolling. + DisableScroll bool + // DisableBounce disables the WebView bounce effect. + DisableBounce bool + // DisableScrollIndicators hides scroll indicators. + DisableScrollIndicators bool + // EnableBackForwardNavigationGestures enables swipe navigation. + EnableBackForwardNavigationGestures bool + // DisableLinkPreview disables link long-press previews. + DisableLinkPreview bool + // EnableInlineMediaPlayback allows media to play inline. + EnableInlineMediaPlayback bool + // EnableAutoplayWithoutUserAction allows media autoplay without a gesture. + EnableAutoplayWithoutUserAction bool + // DisableInspectable disables the Safari Web Inspector. + DisableInspectable bool + // UserAgent overrides the WebView user agent string. + UserAgent string + // ApplicationNameForUserAgent is appended to the user agent. Default: "wails.io". + ApplicationNameForUserAgent string + // AppBackgroundColourSet enables the custom BackgroundColour below. + AppBackgroundColourSet bool + // BackgroundColour is the app window background before WebView creation. + BackgroundColour RGBA + // EnableNativeTabs shows a native iOS UITabBar. + EnableNativeTabs bool + // NativeTabsItems configures the UITabBar items. Auto-enables tabs when non-empty. + NativeTabsItems []NativeTabItem +} + +// AndroidOptions contains Android-specific application configuration. +// +// opts.Android = application.AndroidOptions{EnableZoom: true} +type AndroidOptions struct { + // DisableScroll disables WebView scrolling. + DisableScroll bool + // DisableOverscroll disables the overscroll bounce effect. + DisableOverscroll bool + // EnableZoom allows pinch-to-zoom in the WebView. + EnableZoom bool + // UserAgent sets a custom user agent string. + UserAgent string + // BackgroundColour sets the WebView background colour. + BackgroundColour RGBA + // DisableHardwareAcceleration disables hardware acceleration. + DisableHardwareAcceleration bool +} + +// Options is the top-level application configuration passed to New(). +// +// app := application.New(application.Options{ +// Name: "MyApp", +// Assets: application.AssetOptions{Handler: application.AssetFileServerFS(embedFS)}, +// Services: []application.Service{application.NewService(&myService{})}, +// }) +type Options struct { + // Name is the application name shown in the default about box. + Name string + // Description is shown in the default about box. + Description string + // Icon is the application icon bytes used in the about box. + Icon []byte + + // Mac is the macOS-specific configuration. + Mac MacOptions + // Windows is the Windows-specific configuration. + Windows WindowsOptions + // Linux is the Linux-specific configuration. + Linux LinuxOptions + // IOS is the iOS-specific configuration. + IOS IOSOptions + // Android is the Android-specific configuration. + Android AndroidOptions + + // Services are the bound Go service instances exposed to the frontend. + Services []Service + // MarshalError serialises error values from service methods to JSON. + // A nil return falls 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 + + // Logger is the structured logger for Wails system messages. + // If nil, a default logger is used. + Logger *SlogLogger + // LogLevel sets the log level for the Wails system logger. + LogLevel LogLevel + + // Assets configures the embedded asset server. + Assets AssetOptions + // Flags are key-value pairs exposed to the frontend. + Flags map[string]any + + // PanicHandler is called when a panic occurs in a service method. + PanicHandler func(*PanicDetails) + // DisableDefaultSignalHandler disables the built-in SIGINT/SIGTERM handler. + DisableDefaultSignalHandler bool + + // KeyBindings maps accelerator strings to window callbacks. + KeyBindings map[string]func(window Window) + // OnShutdown is called before the application terminates. + OnShutdown func() + // PostShutdown is called after shutdown completes, just before process exit. + PostShutdown func() + // ShouldQuit is called when the user requests quit. Return false to cancel. + ShouldQuit func() bool + // RawMessageHandler handles raw messages sent 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 an error occurs. + ErrorHandler func(err error) + + // FileAssociations lists file extensions associated with this application. + // Example: []string{".txt", ".md"} — the leading dot is required. + FileAssociations []string + // SingleInstance configures single-instance enforcement. + SingleInstance *SingleInstanceOptions + + // Transport provides a custom IPC transport layer. + // When nil, the default HTTP fetch transport is used. + Transport Transport + + // Server configures the headless HTTP server (requires -tags server). + Server ServerOptions +} diff --git a/stubs/wails/pkg/application/browser_manager.go b/stubs/wails/pkg/application/browser_manager.go new file mode 100644 index 0000000..7e014af --- /dev/null +++ b/stubs/wails/pkg/application/browser_manager.go @@ -0,0 +1,36 @@ +package application + +import "sync" + +// BrowserManager manages browser-related operations in-memory. +// +// manager := &BrowserManager{} +// manager.OpenURL("https://example.com") +// last := manager.LastURL // "https://example.com" +type BrowserManager struct { + mu sync.RWMutex + LastURL string + LastFile string +} + +// OpenURL stores the URL as the last opened URL. +// +// manager.OpenURL("https://lthn.io") +// _ = manager.LastURL // "https://lthn.io" +func (bm *BrowserManager) OpenURL(url string) error { + bm.mu.Lock() + bm.LastURL = url + bm.mu.Unlock() + return nil +} + +// OpenFile stores the path as the last opened file. +// +// manager.OpenFile("/home/user/report.pdf") +// _ = manager.LastFile // "/home/user/report.pdf" +func (bm *BrowserManager) OpenFile(path string) error { + bm.mu.Lock() + bm.LastFile = path + bm.mu.Unlock() + 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..8bef2a4 --- /dev/null +++ b/stubs/wails/pkg/application/browser_window.go @@ -0,0 +1,192 @@ +package application + +import ( + "sync" + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// ButtonState controls window button appearance. +type ButtonState int + +const ( + ButtonEnabled ButtonState = 0 + ButtonDisabled ButtonState = 1 + ButtonHidden ButtonState = 2 +) + +// LRTB represents Left, Right, Top, Bottom border sizes. +type LRTB struct { + Left, Right, Top, Bottom int +} + +// ContextMenuData carries context menu trigger details. +type ContextMenuData struct { + Name string + X, Y int + Data any +} + +// BrowserWindow represents a browser client connection in server mode. +// Implements the Window interface — most methods are no-ops since browser +// clients are controlled via WebSocket, not native APIs. +// +// browserWindow := application.NewBrowserWindow(1, "client-abc123") +type BrowserWindow struct { + mu sync.RWMutex + id uint + name string + clientID string +} + +// NewBrowserWindow creates a browser window with the given ID and client ID. +// +// browserWindow := application.NewBrowserWindow(1, "nanoid-abc123") +func NewBrowserWindow(id uint, clientID string) *BrowserWindow { + return &BrowserWindow{ + id: id, + name: "browser-" + string(rune('0'+id%10)), + clientID: clientID, + } +} + +func (browserWindow *BrowserWindow) ID() uint { return browserWindow.id } +func (browserWindow *BrowserWindow) Name() string { return browserWindow.name } +func (browserWindow *BrowserWindow) ClientID() string { return browserWindow.clientID } + +func (browserWindow *BrowserWindow) DispatchWailsEvent(event *CustomEvent) {} +func (browserWindow *BrowserWindow) EmitEvent(name string, data ...any) bool { + return true +} + +func (browserWindow *BrowserWindow) Error(message string, arguments ...any) {} +func (browserWindow *BrowserWindow) Info(message string, arguments ...any) {} + +// No-op methods — browser windows are controlled via WebSocket, not native APIs. +func (browserWindow *BrowserWindow) Center() {} +func (browserWindow *BrowserWindow) Close() {} +func (browserWindow *BrowserWindow) DisableSizeConstraints() {} +func (browserWindow *BrowserWindow) EnableSizeConstraints() {} +func (browserWindow *BrowserWindow) ExecJS(javascript string) {} +func (browserWindow *BrowserWindow) Focus() {} +func (browserWindow *BrowserWindow) ForceReload() {} +func (browserWindow *BrowserWindow) Fullscreen() Window { return browserWindow } +func (browserWindow *BrowserWindow) GetBorderSizes() *LRTB { return nil } +func (browserWindow *BrowserWindow) GetScreen() (*Screen, error) { + return nil, nil +} +func (browserWindow *BrowserWindow) GetZoom() float64 { return 1.0 } +func (browserWindow *BrowserWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) { +} +func (browserWindow *BrowserWindow) HandleMessage(message string) {} +func (browserWindow *BrowserWindow) HandleWindowEvent(identifier uint) {} +func (browserWindow *BrowserWindow) Height() int { return 0 } +func (browserWindow *BrowserWindow) Hide() Window { return browserWindow } +func (browserWindow *BrowserWindow) HideMenuBar() {} +func (browserWindow *BrowserWindow) IsFocused() bool { return false } +func (browserWindow *BrowserWindow) IsFullscreen() bool { return false } +func (browserWindow *BrowserWindow) IsIgnoreMouseEvents() bool { return false } +func (browserWindow *BrowserWindow) IsMaximised() bool { return false } +func (browserWindow *BrowserWindow) IsMinimised() bool { return false } +func (browserWindow *BrowserWindow) HandleKeyEvent(accelerator string) {} +func (browserWindow *BrowserWindow) Maximise() Window { return browserWindow } +func (browserWindow *BrowserWindow) Minimise() Window { return browserWindow } +func (browserWindow *BrowserWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return func() {} +} +func (browserWindow *BrowserWindow) OpenContextMenu(data *ContextMenuData) {} +func (browserWindow *BrowserWindow) Position() (int, int) { return 0, 0 } +func (browserWindow *BrowserWindow) RelativePosition() (int, int) { return 0, 0 } +func (browserWindow *BrowserWindow) Reload() {} +func (browserWindow *BrowserWindow) Resizable() bool { return false } +func (browserWindow *BrowserWindow) Restore() {} +func (browserWindow *BrowserWindow) Run() {} +func (browserWindow *BrowserWindow) SetPosition(x, y int) {} +func (browserWindow *BrowserWindow) SetAlwaysOnTop(alwaysOnTop bool) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetBackgroundColour(colour RGBA) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetFrameless(frameless bool) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetHTML(html string) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetMinimiseButtonState(state ButtonState) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetMaximiseButtonState(state ButtonState) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetCloseButtonState(state ButtonState) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetMaxSize(maxWidth, maxHeight int) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetMinSize(minWidth, minHeight int) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetRelativePosition(x, y int) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetResizable(resizable bool) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetIgnoreMouseEvents(ignore bool) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetSize(width, height int) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetTitle(title string) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetURL(url string) Window { return browserWindow } +func (browserWindow *BrowserWindow) SetZoom(magnification float64) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) Show() Window { return browserWindow } +func (browserWindow *BrowserWindow) ShowMenuBar() {} +func (browserWindow *BrowserWindow) Size() (int, int) { return 0, 0 } +func (browserWindow *BrowserWindow) OpenDevTools() {} +func (browserWindow *BrowserWindow) ToggleFullscreen() {} +func (browserWindow *BrowserWindow) ToggleMaximise() {} +func (browserWindow *BrowserWindow) ToggleMenuBar() {} +func (browserWindow *BrowserWindow) ToggleFrameless() {} +func (browserWindow *BrowserWindow) UnFullscreen() {} +func (browserWindow *BrowserWindow) UnMaximise() {} +func (browserWindow *BrowserWindow) UnMinimise() {} +func (browserWindow *BrowserWindow) Width() int { return 0 } +func (browserWindow *BrowserWindow) IsVisible() bool { return true } +func (browserWindow *BrowserWindow) Bounds() Rect { return Rect{} } +func (browserWindow *BrowserWindow) SetBounds(bounds Rect) {} +func (browserWindow *BrowserWindow) Zoom() {} +func (browserWindow *BrowserWindow) ZoomIn() {} +func (browserWindow *BrowserWindow) ZoomOut() {} +func (browserWindow *BrowserWindow) ZoomReset() Window { return browserWindow } +func (browserWindow *BrowserWindow) SetMenu(menu *Menu) {} +func (browserWindow *BrowserWindow) SnapAssist() {} +func (browserWindow *BrowserWindow) SetContentProtection(protection bool) Window { + return browserWindow +} +func (browserWindow *BrowserWindow) SetEnabled(enabled bool) {} +func (browserWindow *BrowserWindow) Flash(enabled bool) {} +func (browserWindow *BrowserWindow) Print() error { return nil } +func (browserWindow *BrowserWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() { + return func() {} +} + +// Internal platform hooks — no-ops for browser windows. + +func (browserWindow *BrowserWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) { +} +func (browserWindow *BrowserWindow) shouldUnconditionallyClose() bool { return false } +func (browserWindow *BrowserWindow) cut() {} +func (browserWindow *BrowserWindow) copy() {} +func (browserWindow *BrowserWindow) paste() {} +func (browserWindow *BrowserWindow) undo() {} +func (browserWindow *BrowserWindow) redo() {} +func (browserWindow *BrowserWindow) delete() {} +func (browserWindow *BrowserWindow) selectAll() {} + +// NativeWindow returns nil — browser windows have no native handle. +// +// ptr := w.NativeWindow() +func (browserWindow *BrowserWindow) NativeWindow() unsafe.Pointer { return nil } + +// AttachModal registers a modal window that blocks this window. +// +// w.AttachModal(confirmDialog) +func (browserWindow *BrowserWindow) AttachModal(modalWindow Window) {} diff --git a/stubs/wails/pkg/application/clipboard.go b/stubs/wails/pkg/application/clipboard.go new file mode 100644 index 0000000..db85f35 --- /dev/null +++ b/stubs/wails/pkg/application/clipboard.go @@ -0,0 +1,69 @@ +package application + +import "sync" + +// Clipboard stores and retrieves text in-memory. +// +// cb := &Clipboard{} +// cb.SetText("hello") +// text, ok := cb.Text() // "hello", true +type Clipboard struct { + mu sync.RWMutex + text string + set bool +} + +// SetText stores the given text in the in-memory clipboard. +// +// cb.SetText("copied content") +func (c *Clipboard) SetText(text string) bool { + c.mu.Lock() + c.text = text + c.set = true + c.mu.Unlock() + return true +} + +// Text returns the stored clipboard text and whether any text has been set. +// +// text, ok := cb.Text() +// if !ok { text = "" } +func (c *Clipboard) Text() (string, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c.text, c.set +} + +// ClipboardManager manages clipboard operations via a lazily created Clipboard. +// +// manager := &ClipboardManager{} +// manager.SetText("hello") +// text, ok := manager.Text() // "hello", true +type ClipboardManager struct { + mu sync.Mutex + clipboard *Clipboard +} + +// SetText sets text in the clipboard. +// +// manager.SetText("some text") +func (cm *ClipboardManager) SetText(text string) bool { + return cm.getClipboard().SetText(text) +} + +// Text gets text from the clipboard. +// +// text, ok := manager.Text() +func (cm *ClipboardManager) Text() (string, bool) { + return cm.getClipboard().Text() +} + +// getClipboard returns the clipboard instance, creating it if needed. +func (cm *ClipboardManager) getClipboard() *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..1ac45de --- /dev/null +++ b/stubs/wails/pkg/application/context_menu.go @@ -0,0 +1,84 @@ +package application + +import "sync" + +// ContextMenu is a named menu used for right-click context menus. +// +// menu := &ContextMenu{name: "file-list", Menu: NewMenu()} +// menu.Add("Open") +type ContextMenu struct { + *Menu + name string +} + +// ContextMenuManager manages all context menu operations in-memory. +// +// manager := &ContextMenuManager{} +// menu := manager.New() +// manager.Add("file-list", menu) +// retrieved, ok := manager.Get("file-list") +type ContextMenuManager struct { + mu sync.RWMutex + contextMenus map[string]*ContextMenu +} + +// New creates a new context menu. +// +// menu := manager.New() +// menu.Add("Delete") +func (cmm *ContextMenuManager) New() *ContextMenu { + return &ContextMenu{ + Menu: NewMenu(), + } +} + +// Add registers a context menu under the given name. +// +// manager.Add("item-actions", menu) +func (cmm *ContextMenuManager) Add(name string, menu *ContextMenu) { + cmm.mu.Lock() + defer cmm.mu.Unlock() + if cmm.contextMenus == nil { + cmm.contextMenus = make(map[string]*ContextMenu) + } + cmm.contextMenus[name] = menu +} + +// Remove removes a context menu by name. +// +// manager.Remove("item-actions") +func (cmm *ContextMenuManager) Remove(name string) { + cmm.mu.Lock() + defer cmm.mu.Unlock() + if cmm.contextMenus != nil { + delete(cmm.contextMenus, name) + } +} + +// Get retrieves a context menu by name. +// +// menu, ok := manager.Get("item-actions") +// if !ok { return nil } +func (cmm *ContextMenuManager) Get(name string) (*ContextMenu, bool) { + cmm.mu.RLock() + defer cmm.mu.RUnlock() + if cmm.contextMenus == nil { + return nil, false + } + menu, exists := cmm.contextMenus[name] + return menu, exists +} + +// GetAll returns all registered context menus as a slice. +// +// menus := manager.GetAll() +// for _, m := range menus { _ = m } +func (cmm *ContextMenuManager) GetAll() []*ContextMenu { + cmm.mu.RLock() + defer cmm.mu.RUnlock() + result := make([]*ContextMenu, 0, len(cmm.contextMenus)) + for _, menu := range cmm.contextMenus { + 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..8613911 --- /dev/null +++ b/stubs/wails/pkg/application/dialog.go @@ -0,0 +1,421 @@ +package application + +import "sync" + +// DialogType identifies the visual style of a message dialog. +type DialogType int + +const ( + InfoDialogType DialogType = iota + QuestionDialogType + WarningDialogType + ErrorDialogType +) + +// FileFilter restricts which files are shown in a file dialog. +// +// filter := FileFilter{DisplayName: "Images", Pattern: "*.png;*.jpg"} +type FileFilter struct { + DisplayName string + Pattern string +} + +// OpenFileDialogOptions configures an open-file dialog. +// +// opts := &OpenFileDialogOptions{Title: "Choose file", AllowsMultipleSelection: true} +type OpenFileDialogOptions struct { + Title string + Directory string + Filters []FileFilter + AllowsMultipleSelection bool + CanChooseDirectories bool + CanChooseFiles bool + ShowHiddenFiles bool +} + +// SaveFileDialogOptions configures a save-file dialog. +// +// opts := &SaveFileDialogOptions{Title: "Save report", Directory: "/tmp"} +type SaveFileDialogOptions struct { + Title string + Directory string + Filename string + Filters []FileFilter + ShowHiddenFiles bool +} + +// OpenFileDialogStruct is an in-memory open-file dialog. +// +// dialog := &OpenFileDialogStruct{} +// dialog.SetTitle("Pick a file") +// path, _ := dialog.PromptForSingleSelection() +type OpenFileDialogStruct struct { + mu sync.RWMutex + title string + directory string + filters []FileFilter + multipleAllowed bool + canChooseDirs bool + canChooseFiles bool + showHidden bool + selectedFiles []string +} + +func newOpenFileDialog() *OpenFileDialogStruct { + return &OpenFileDialogStruct{canChooseFiles: true} +} + +// SetOptions applies OpenFileDialogOptions to the dialog. +// +// dialog.SetOptions(&OpenFileDialogOptions{Title: "Open log", ShowHiddenFiles: true}) +func (d *OpenFileDialogStruct) SetOptions(options *OpenFileDialogOptions) { + if options == nil { + return + } + d.mu.Lock() + d.title = options.Title + d.directory = options.Directory + d.filters = append([]FileFilter(nil), options.Filters...) + d.multipleAllowed = options.AllowsMultipleSelection + d.canChooseDirs = options.CanChooseDirectories + d.canChooseFiles = options.CanChooseFiles + d.showHidden = options.ShowHiddenFiles + d.mu.Unlock() +} + +// SetTitle sets the dialog window title. +// +// dialog.SetTitle("Select configuration file") +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct { + d.mu.Lock() + d.title = title + d.mu.Unlock() + return d +} + +// SetDirectory sets the initial directory shown in the dialog. +// +// dialog.SetDirectory("/home/user/documents") +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct { + d.mu.Lock() + d.directory = directory + d.mu.Unlock() + return d +} + +// AddFilter appends a file filter to the dialog. +// +// dialog.AddFilter("Go source", "*.go") +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct { + d.mu.Lock() + d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern}) + d.mu.Unlock() + return d +} + +// SetAllowsMultipleSelection controls whether multiple files can be selected. +// +// dialog.SetAllowsMultipleSelection(true) +func (d *OpenFileDialogStruct) SetAllowsMultipleSelection(allow bool) *OpenFileDialogStruct { + d.mu.Lock() + d.multipleAllowed = allow + d.mu.Unlock() + return d +} + +// SetSelectedFiles injects pre-selected files for stub testing. +// +// dialog.SetSelectedFiles([]string{"/tmp/a.txt", "/tmp/b.txt"}) +func (d *OpenFileDialogStruct) SetSelectedFiles(paths []string) { + d.mu.Lock() + d.selectedFiles = append([]string(nil), paths...) + d.mu.Unlock() +} + +// PromptForSingleSelection returns the first injected file path, or "" if none. +// +// path, err := dialog.PromptForSingleSelection() +// if err != nil { return err } +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) { + d.mu.RLock() + defer d.mu.RUnlock() + if len(d.selectedFiles) > 0 { + return d.selectedFiles[0], nil + } + return "", nil +} + +// PromptForMultipleSelection returns all injected file paths. +// +// paths, err := dialog.PromptForMultipleSelection() +// for _, p := range paths { process(p) } +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) { + d.mu.RLock() + defer d.mu.RUnlock() + return append([]string(nil), d.selectedFiles...), nil +} + +// SaveFileDialogStruct is an in-memory save-file dialog. +// +// dialog := &SaveFileDialogStruct{} +// dialog.SetFilename("report.pdf") +// path, _ := dialog.PromptForSingleSelection() +type SaveFileDialogStruct struct { + mu sync.RWMutex + title string + directory string + filename string + filters []FileFilter + showHidden bool + selectedPath string +} + +func newSaveFileDialog() *SaveFileDialogStruct { + return &SaveFileDialogStruct{} +} + +// SetOptions applies SaveFileDialogOptions to the dialog. +// +// dialog.SetOptions(&SaveFileDialogOptions{Title: "Export", Filename: "data.json"}) +func (d *SaveFileDialogStruct) SetOptions(options *SaveFileDialogOptions) { + if options == nil { + return + } + d.mu.Lock() + d.title = options.Title + d.directory = options.Directory + d.filename = options.Filename + d.filters = append([]FileFilter(nil), options.Filters...) + d.showHidden = options.ShowHiddenFiles + d.mu.Unlock() +} + +// SetTitle sets the dialog window title. +// +// dialog.SetTitle("Export configuration") +func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct { + d.mu.Lock() + d.title = title + d.mu.Unlock() + return d +} + +// SetDirectory sets the initial directory shown in the dialog. +// +// dialog.SetDirectory("/home/user/exports") +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct { + d.mu.Lock() + d.directory = directory + d.mu.Unlock() + return d +} + +// SetFilename sets the default filename shown in the dialog. +// +// dialog.SetFilename("backup-2026.tar.gz") +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct { + d.mu.Lock() + d.filename = filename + d.mu.Unlock() + return d +} + +// AddFilter appends a file filter to the dialog. +// +// dialog.AddFilter("JSON files", "*.json") +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct { + d.mu.Lock() + d.filters = append(d.filters, FileFilter{DisplayName: displayName, Pattern: pattern}) + d.mu.Unlock() + return d +} + +// SetSelectedPath injects the path returned by PromptForSingleSelection for stub testing. +// +// dialog.SetSelectedPath("/tmp/output.csv") +func (d *SaveFileDialogStruct) SetSelectedPath(path string) { + d.mu.Lock() + d.selectedPath = path + d.mu.Unlock() +} + +// PromptForSingleSelection returns the injected save path, or "" if none. +// +// path, err := dialog.PromptForSingleSelection() +// if err != nil { return err } +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) { + d.mu.RLock() + defer d.mu.RUnlock() + return d.selectedPath, nil +} + +// MessageButton represents a button in a message dialog. +type MessageButton struct { + Label string + IsDefault bool + IsCancel bool +} + +// MessageDialog is an in-memory message dialog (info, question, warning, error). +// +// dialog := &MessageDialog{dialogType: InfoDialogType} +// dialog.SetTitle("Done").SetMessage("File saved successfully.") +// _ = dialog.Show() +type MessageDialog struct { + mu sync.RWMutex + dialogType DialogType + title string + message string + buttons []MessageButton + clickedButton string +} + +func newMessageDialog(dialogType DialogType) *MessageDialog { + return &MessageDialog{dialogType: dialogType} +} + +// SetTitle sets the dialog window title. +// +// dialog.SetTitle("Confirm deletion") +func (d *MessageDialog) SetTitle(title string) *MessageDialog { + d.mu.Lock() + d.title = title + d.mu.Unlock() + return d +} + +// SetMessage sets the body text of the dialog. +// +// dialog.SetMessage("Are you sure you want to delete this file?") +func (d *MessageDialog) SetMessage(message string) *MessageDialog { + d.mu.Lock() + d.message = message + d.mu.Unlock() + return d +} + +// AddButton appends a button to the dialog. +// +// dialog.AddButton("Yes").AddButton("No") +func (d *MessageDialog) AddButton(label string) *MessageDialog { + d.mu.Lock() + d.buttons = append(d.buttons, MessageButton{Label: label}) + d.mu.Unlock() + return d +} + +// SetDefaultButton marks the named button as the default action. +// +// dialog.SetDefaultButton("OK") +func (d *MessageDialog) SetDefaultButton(label string) *MessageDialog { + d.mu.Lock() + for index := range d.buttons { + d.buttons[index].IsDefault = d.buttons[index].Label == label + } + d.mu.Unlock() + return d +} + +// SetCancelButton marks the named button as the cancel action. +// +// dialog.SetCancelButton("Cancel") +func (d *MessageDialog) SetCancelButton(label string) *MessageDialog { + d.mu.Lock() + for index := range d.buttons { + d.buttons[index].IsCancel = d.buttons[index].Label == label + } + d.mu.Unlock() + return d +} + +// SetButtonClickedForStub injects which button was clicked for stub testing. +// +// dialog.SetButtonClickedForStub("Yes") +// result, _ := dialog.Show() +func (d *MessageDialog) SetButtonClickedForStub(label string) { + d.mu.Lock() + d.clickedButton = label + d.mu.Unlock() +} + +// Show displays the dialog and returns the label of the clicked button. +// +// clicked, err := dialog.Show() +// if clicked == "Yes" { deleteFile() } +func (d *MessageDialog) Show() (string, error) { + d.mu.RLock() + defer d.mu.RUnlock() + return d.clickedButton, nil +} + +// DialogManager manages dialog operations in-memory. +// +// manager := &DialogManager{} +// dialog := manager.Info().SetTitle("Done").SetMessage("Saved.") +type DialogManager struct { + mu sync.RWMutex +} + +// OpenFile creates an open-file dialog. +// +// dialog := manager.OpenFile() +// dialog.SetTitle("Choose config") +// path, _ := dialog.PromptForSingleSelection() +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct { + return newOpenFileDialog() +} + +// OpenFileWithOptions creates an open-file dialog pre-configured from options. +// +// dialog := manager.OpenFileWithOptions(&OpenFileDialogOptions{Title: "Select log"}) +func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { + dialog := newOpenFileDialog() + dialog.SetOptions(options) + return dialog +} + +// SaveFile creates a save-file dialog. +// +// dialog := manager.SaveFile() +// dialog.SetFilename("export.csv") +// path, _ := dialog.PromptForSingleSelection() +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct { + return newSaveFileDialog() +} + +// SaveFileWithOptions creates a save-file dialog pre-configured from options. +// +// dialog := manager.SaveFileWithOptions(&SaveFileDialogOptions{Title: "Export data"}) +func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct { + dialog := newSaveFileDialog() + dialog.SetOptions(options) + return dialog +} + +// Info creates an information message dialog. +// +// manager.Info().SetTitle("Done").SetMessage("File saved.").Show() +func (dm *DialogManager) Info() *MessageDialog { + return newMessageDialog(InfoDialogType) +} + +// Question creates a question message dialog. +// +// manager.Question().SetTitle("Confirm").SetMessage("Delete file?").AddButton("Yes").AddButton("No").Show() +func (dm *DialogManager) Question() *MessageDialog { + return newMessageDialog(QuestionDialogType) +} + +// Warning creates a warning message dialog. +// +// manager.Warning().SetTitle("Warning").SetMessage("Disk almost full.").Show() +func (dm *DialogManager) Warning() *MessageDialog { + return newMessageDialog(WarningDialogType) +} + +// Error creates an error message dialog. +// +// manager.Error().SetTitle("Error").SetMessage("Operation 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..98651c9 --- /dev/null +++ b/stubs/wails/pkg/application/environment.go @@ -0,0 +1,85 @@ +package application + +import "sync" + +// EnvironmentInfo holds information about the host environment. +// +// info := manager.Info() +// if info.IsDarkMode { applyDarkTheme() } +type EnvironmentInfo struct { + OS string + Arch string + Debug bool + IsDarkMode bool + AccentColour string + PlatformInfo map[string]any +} + +// EnvironmentManager tracks environment state in-memory. +// +// manager := &EnvironmentManager{} +// manager.SetDarkMode(true) +// dark := manager.IsDarkMode() // true +type EnvironmentManager struct { + mu sync.RWMutex + darkMode bool + accentColour string + operatingSystem string + architecture string + debugMode bool +} + +// SetDarkMode sets the dark mode state used by IsDarkMode. +// +// manager.SetDarkMode(true) +func (em *EnvironmentManager) SetDarkMode(darkMode bool) { + em.mu.Lock() + em.darkMode = darkMode + em.mu.Unlock() +} + +// IsDarkMode returns true when the environment is in dark mode. +// +// if manager.IsDarkMode() { applyDarkTheme() } +func (em *EnvironmentManager) IsDarkMode() bool { + em.mu.RLock() + defer em.mu.RUnlock() + return em.darkMode +} + +// SetAccentColour sets the accent colour returned by GetAccentColor. +// +// manager.SetAccentColour("rgb(0,122,255)") +func (em *EnvironmentManager) SetAccentColour(colour string) { + em.mu.Lock() + em.accentColour = colour + em.mu.Unlock() +} + +// GetAccentColor returns the stored accent colour, or the default blue if unset. +// +// colour := manager.GetAccentColor() // "rgb(0,122,255)" +func (em *EnvironmentManager) GetAccentColor() string { + em.mu.RLock() + defer em.mu.RUnlock() + if em.accentColour == "" { + return "rgb(0,122,255)" + } + return em.accentColour +} + +// Info returns a snapshot of the current environment state. +// +// info := manager.Info() +// _ = info.OS // e.g. "linux" +func (em *EnvironmentManager) Info() EnvironmentInfo { + em.mu.RLock() + defer em.mu.RUnlock() + return EnvironmentInfo{ + OS: em.operatingSystem, + Arch: em.architecture, + Debug: em.debugMode, + IsDarkMode: em.darkMode, + AccentColour: em.accentColour, + } +} diff --git a/stubs/wails/pkg/application/events.go b/stubs/wails/pkg/application/events.go new file mode 100644 index 0000000..e063871 --- /dev/null +++ b/stubs/wails/pkg/application/events.go @@ -0,0 +1,212 @@ +package application + +import ( + "sync" + "sync/atomic" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// ApplicationEventContext carries optional context for an application event. +// +// ctx := event.Context() +// _ = ctx // reserved for future platform-specific fields +type ApplicationEventContext struct{} + +func newApplicationEventContext() *ApplicationEventContext { + return &ApplicationEventContext{} +} + +// ApplicationEvent is emitted by the application layer for system-level events. +// +// manager.OnApplicationEvent(events.Mac.ApplicationShouldTerminate, func(e *ApplicationEvent) { +// e.Cancel() +// }) +type ApplicationEvent struct { + Id uint + ctx *ApplicationEventContext + cancelled atomic.Bool +} + +// Context returns the context attached to this application event. +// +// ctx := event.Context() +func (e *ApplicationEvent) Context() *ApplicationEventContext { + return e.ctx +} + +// Cancel prevents further processing of this application event. +// +// event.Cancel() +func (e *ApplicationEvent) Cancel() { + e.cancelled.Store(true) +} + +// IsCancelled reports whether Cancel has been called on this event. +// +// if event.IsCancelled() { return } +func (e *ApplicationEvent) IsCancelled() bool { + return e.cancelled.Load() +} + +// CustomEvent is a named application-level event carrying arbitrary data. +// +// manager.Emit("user:login", userPayload) +type CustomEvent struct { + Name string `json:"name"` + Data any `json:"data"` + Sender string `json:"sender,omitempty"` + cancelled atomic.Bool +} + +// Cancel prevents further processing of this custom event. +// +// event.Cancel() +func (e *CustomEvent) Cancel() { + e.cancelled.Store(true) +} + +// IsCancelled reports whether Cancel has been called on this event. +// +// if event.IsCancelled() { return } +func (e *CustomEvent) IsCancelled() bool { + return e.cancelled.Load() +} + +// customEventListener holds a callback and its remaining invocation count. +type customEventListener struct { + callback func(*CustomEvent) + counter int +} + +// applicationEventListener holds a callback for an application event. +type applicationEventListener struct { + callback func(*ApplicationEvent) +} + +// EventManager manages custom and application events in-memory. +// +// manager := &EventManager{} +// cancel := manager.On("data:ready", func(e *CustomEvent) { process(e.Data) }) +// defer cancel() +type EventManager struct { + mu sync.RWMutex + customListeners map[string][]*customEventListener + appListeners map[uint][]*applicationEventListener +} + +func newEventManager() *EventManager { + return &EventManager{ + customListeners: make(map[string][]*customEventListener), + appListeners: make(map[uint][]*applicationEventListener), + } +} + +// Emit fires a named custom event with optional data to all registered listeners. +// Returns true if the event was cancelled by a listener. +// +// cancelled := manager.Emit("file:saved", "/home/user/doc.txt") +// if cancelled { log("event cancelled") } +func (em *EventManager) Emit(name string, data ...any) bool { + event := &CustomEvent{Name: name} + switch len(data) { + case 0: + // no data + case 1: + event.Data = data[0] + default: + event.Data = data + } + + em.mu.Lock() + listeners := append([]*customEventListener(nil), em.customListeners[name]...) + remaining := em.customListeners[name][:0] + for _, listener := range em.customListeners[name] { + if listener.counter < 0 { + remaining = append(remaining, listener) + } else { + listener.counter-- + if listener.counter > 0 { + remaining = append(remaining, listener) + } + } + } + em.customListeners[name] = remaining + em.mu.Unlock() + + for _, listener := range listeners { + if event.IsCancelled() { + break + } + listener.callback(event) + } + return event.IsCancelled() +} + +// On registers a persistent listener for the named custom event. +// Returns a cancellation function that removes the listener. +// +// cancel := manager.On("theme:changed", func(e *CustomEvent) { applyTheme(e.Data) }) +// defer cancel() +func (em *EventManager) On(name string, callback func(*CustomEvent)) func() { + listener := &customEventListener{callback: callback, counter: -1} + em.mu.Lock() + em.customListeners[name] = append(em.customListeners[name], listener) + em.mu.Unlock() + return func() { + em.mu.Lock() + defer em.mu.Unlock() + updated := em.customListeners[name][:0] + for _, existing := range em.customListeners[name] { + if existing != listener { + updated = append(updated, existing) + } + } + em.customListeners[name] = updated + } +} + +// Off removes all listeners for the named custom event. +// +// manager.Off("theme:changed") +func (em *EventManager) Off(name string) { + em.mu.Lock() + delete(em.customListeners, name) + em.mu.Unlock() +} + +// OnMultiple registers a listener for the named custom event that fires at most counter times. +// +// manager.OnMultiple("startup:phase", onPhase, 3) +func (em *EventManager) OnMultiple(name string, callback func(*CustomEvent), counter int) { + listener := &customEventListener{callback: callback, counter: counter} + em.mu.Lock() + em.customListeners[name] = append(em.customListeners[name], listener) + em.mu.Unlock() +} + +// OnApplicationEvent registers a listener for application-level events. +// Returns a cancellation function that removes the listener. +// +// cancel := manager.OnApplicationEvent(events.Mac.ApplicationShouldTerminate, func(e *ApplicationEvent) { +// saveState() +// }) +// defer cancel() +func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(*ApplicationEvent)) func() { + eventID := uint(eventType) + listener := &applicationEventListener{callback: callback} + em.mu.Lock() + em.appListeners[eventID] = append(em.appListeners[eventID], listener) + em.mu.Unlock() + return func() { + em.mu.Lock() + defer em.mu.Unlock() + updated := em.appListeners[eventID][:0] + for _, existing := range em.appListeners[eventID] { + if existing != listener { + updated = append(updated, existing) + } + } + em.appListeners[eventID] = updated + } +} diff --git a/stubs/wails/pkg/application/keybinding.go b/stubs/wails/pkg/application/keybinding.go new file mode 100644 index 0000000..c190bf0 --- /dev/null +++ b/stubs/wails/pkg/application/keybinding.go @@ -0,0 +1,71 @@ +package application + +import "sync" + +// KeyBinding pairs an accelerator string with its registered callback. +// +// binding := &KeyBinding{Accelerator: "CmdOrCtrl+K", Callback: handler} +type KeyBinding struct { + Accelerator string + Callback func(window Window) +} + +// KeyBindingManager stores and dispatches global key bindings. +// +// manager.Add("CmdOrCtrl+K", func(w Window) { w.Focus() }) +// handled := manager.Process("CmdOrCtrl+K", currentWindow) +type KeyBindingManager struct { + mu sync.RWMutex + bindings map[string]func(window Window) +} + +// Add registers a callback for the given accelerator string. +// +// manager.Add("CmdOrCtrl+Shift+P", func(w Window) { launchCommandPalette(w) }) +func (m *KeyBindingManager) Add(accelerator string, callback func(window Window)) { + m.mu.Lock() + defer m.mu.Unlock() + if m.bindings == nil { + m.bindings = make(map[string]func(window Window)) + } + m.bindings[accelerator] = callback +} + +// Remove deregisters the callback for the given accelerator string. +// +// manager.Remove("CmdOrCtrl+Shift+P") +func (m *KeyBindingManager) Remove(accelerator string) { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.bindings, accelerator) +} + +// Process fires the callback for the given accelerator and returns true if handled. +// +// if manager.Process("CmdOrCtrl+K", window) { return } +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 key bindings. +// +// for _, kb := range manager.GetAll() { log(kb.Accelerator) } +func (m *KeyBindingManager) GetAll() []*KeyBinding { + m.mu.RLock() + defer m.mu.RUnlock() + bindings := make([]*KeyBinding, 0, len(m.bindings)) + for accelerator, callback := range m.bindings { + bindings = append(bindings, &KeyBinding{ + Accelerator: accelerator, + Callback: callback, + }) + } + return bindings +} diff --git a/stubs/wails/pkg/application/menuitem.go b/stubs/wails/pkg/application/menuitem.go new file mode 100644 index 0000000..4c60f16 --- /dev/null +++ b/stubs/wails/pkg/application/menuitem.go @@ -0,0 +1,304 @@ +package application + +// Role identifies a platform-specific menu role. +// It is the same underlying type as MenuRole so existing constants (AppMenu, +// FileMenu, EditMenu, ViewMenu, WindowMenu, HelpMenu) are valid Role values. +// +// quitItem := application.NewRole(application.Quit) +type Role = MenuRole + +const ( + // NoRole indicates no special platform role. + NoRole MenuRole = iota + 100 + + // ServicesMenu is the macOS Services sub-menu. + ServicesMenu + + // SpeechMenu is the macOS Speech sub-menu. + SpeechMenu + + // Hide hides the current application. + Hide + + // HideOthers hides all other applications. + HideOthers + + // UnHide shows all hidden applications. + UnHide + + // Front brings all windows to front. + Front + + // Undo triggers the standard Undo action. + Undo + + // Redo triggers the standard Redo action. + Redo + + // Cut triggers the standard Cut action. + Cut + + // Copy triggers the standard Copy action. + Copy + + // Paste triggers the standard Paste action. + Paste + + // PasteAndMatchStyle pastes without source formatting. + PasteAndMatchStyle + + // SelectAll triggers the standard Select All action. + SelectAll + + // Delete triggers the standard Delete action. + Delete + + // Quit quits the application. + Quit + + // CloseWindow closes the focused window. + CloseWindow + + // About opens the About panel. + About + + // Reload reloads the current webview. + Reload + + // ForceReload force-reloads the current webview. + ForceReload + + // ToggleFullscreen toggles fullscreen mode. + ToggleFullscreen + + // OpenDevTools opens the developer tools panel. + OpenDevTools + + // ResetZoom resets the webview zoom level. + ResetZoom + + // ZoomIn increases the webview zoom level. + ZoomIn + + // ZoomOut decreases the webview zoom level. + ZoomOut + + // Minimise minimises the focused window. + Minimise + + // Zoom zooms the focused window (macOS). + Zoom + + // FullScreen enters fullscreen (macOS). + FullScreen + + // Print opens the print dialog. + Print + + // PageLayout opens the page layout dialog. + PageLayout + + // ShowAll shows all windows. + ShowAll + + // BringAllToFront brings all windows to front. + BringAllToFront + + // NewFile triggers the New File action. + NewFile + + // Open triggers the Open action. + Open + + // Save triggers the Save action. + Save + + // SaveAs triggers the Save As action. + SaveAs + + // StartSpeaking starts text-to-speech on selected text. + StartSpeaking + + // StopSpeaking stops text-to-speech. + StopSpeaking + + // Revert triggers the Revert action. + Revert + + // Find triggers the Find action. + Find + + // FindAndReplace triggers the Find and Replace action. + FindAndReplace + + // FindNext finds the next match. + FindNext + + // FindPrevious finds the previous match. + FindPrevious + + // Help opens the application help. + Help +) + +// NewMenuItem creates a new text menu item with the given label. +// +// item := application.NewMenuItem("Open File").SetAccelerator("CmdOrCtrl+O") +func NewMenuItem(label string) *MenuItem { + return &MenuItem{Label: label, Enabled: true} +} + +// NewMenuItemSeparator creates a horizontal separator for use in menus. +// +// menu.Items = append(menu.Items, application.NewMenuItemSeparator()) +func NewMenuItemSeparator() *MenuItem { + return &MenuItem{Label: "---"} +} + +// NewMenuItemCheckbox creates a checkable menu item. +// +// item := application.NewMenuItemCheckbox("Show Toolbar", true) +func NewMenuItemCheckbox(label string, checked bool) *MenuItem { + return &MenuItem{Label: label, Checked: checked, Enabled: true} +} + +// NewMenuItemRadio creates a radio-group menu item. +// +// item := application.NewMenuItemRadio("Small", false) +func NewMenuItemRadio(label string, checked bool) *MenuItem { + return &MenuItem{Label: label, Checked: checked, Enabled: true} +} + +// NewSubMenuItem creates a menu item that opens a sub-menu. +// +// sub := application.NewSubMenuItem("Recent Files") +func NewSubMenuItem(label string) *MenuItem { + return &MenuItem{Label: label, Enabled: true} +} + +// NewRole creates a menu item pre-configured for a platform role. +// +// quitItem := application.NewRole(application.Quit) +func NewRole(role Role) *MenuItem { + return &MenuItem{Label: roleLabel(role), Enabled: true} +} + +// NewServicesMenu creates the macOS Services sub-menu item. +// +// servicesMenu := application.NewServicesMenu() +func NewServicesMenu() *MenuItem { + return NewSubMenuItem("Services") +} + +// GetAccelerator returns the accelerator string currently set on the item. +// +// accel := item.GetAccelerator() // e.g. "CmdOrCtrl+S" +func (mi *MenuItem) GetAccelerator() string { + return mi.Accelerator +} + +// roleLabel maps a Role constant to a human-readable label. +func roleLabel(role Role) string { + switch role { + case AppMenu: + return "App" + case EditMenu: + return "Edit" + case FileMenu: + return "File" + case ViewMenu: + return "View" + case ServicesMenu: + return "Services" + case SpeechMenu: + return "Speech" + case WindowMenu: + return "Window" + case HelpMenu: + return "Help" + case Hide: + return "Hide" + case HideOthers: + return "Hide Others" + case UnHide: + return "Show All" + case Front: + return "Bring All to Front" + 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 About: + return "About" + case Reload: + return "Reload" + case ForceReload: + return "Force Reload" + case ToggleFullscreen: + return "Toggle Full Screen" + case OpenDevTools: + return "Open Developer Tools" + case ResetZoom: + return "Reset Zoom" + case ZoomIn: + return "Zoom In" + case ZoomOut: + return "Zoom Out" + case Minimise: + return "Minimise" + case Zoom: + return "Zoom" + case FullScreen: + return "Full Screen" + case Print: + return "Print" + case PageLayout: + return "Page Layout" + case ShowAll: + return "Show All" + case BringAllToFront: + return "Bring All to Front" + 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 Find: + return "Find" + case FindAndReplace: + return "Find and Replace" + case FindNext: + return "Find Next" + case FindPrevious: + return "Find Previous" + case Help: + return "Help" + default: + return "" + } +} diff --git a/stubs/wails/pkg/application/screen.go b/stubs/wails/pkg/application/screen.go new file mode 100644 index 0000000..6bf8d7b --- /dev/null +++ b/stubs/wails/pkg/application/screen.go @@ -0,0 +1,146 @@ +package application + +import "sync" + +// Screen describes a physical or logical display. +// +// primary := manager.GetPrimary() +// if primary != nil { useSize(primary.Size) } +type Screen struct { + ID string // A unique identifier for the display + Name string // The name of the display + ScaleFactor float32 // The scale factor of the display (DPI/96) + X int // The x-coordinate of the top-left corner of the display + Y int // The y-coordinate of the top-left corner of the display + Size Size // The logical size of the display + Bounds Rect // The logical bounds of the display + PhysicalBounds Rect // The physical bounds before scaling + WorkArea Rect // The work area (excluding taskbars etc.) + PhysicalWorkArea Rect // The physical work area before scaling + IsPrimary bool // Whether this is the primary display + Rotation float32 // The rotation of the display in degrees +} + +// Rect is an axis-aligned rectangle in either logical or physical pixels. +// +// if rect.Contains(Point{X: 100, Y: 200}) { ... } +type Rect struct { + X int + Y int + Width int + Height int +} + +// Point is a two-dimensional coordinate. +// +// centre := Point{X: bounds.X + bounds.Width/2, Y: bounds.Y + bounds.Height/2} +type Point struct { + X int + Y int +} + +// Size holds the width and height dimensions of a display or region. +// +// if size.Width > 1920 { useHiResLayout() } +type Size struct { + Width int + Height int +} + +// Origin returns the top-left corner of the rectangle. +func (r Rect) Origin() Point { + return Point{X: r.X, Y: r.Y} +} + +// Corner returns the exclusive bottom-right corner (X+Width, Y+Height). +func (r Rect) Corner() Point { + return Point{X: r.X + r.Width, Y: r.Y + r.Height} +} + +// IsEmpty reports whether the rectangle has non-positive area. +func (r Rect) IsEmpty() bool { + return r.Width <= 0 || r.Height <= 0 +} + +// Contains reports whether point pt lies within the rectangle. +// +// if bounds.Contains(cursorPoint) { highlightWindow() } +func (r Rect) Contains(pt Point) bool { + return pt.X >= r.X && pt.X < r.X+r.Width && pt.Y >= r.Y && pt.Y < r.Y+r.Height +} + +// RectSize returns the dimensions of the rectangle as a Size value. +func (r Rect) RectSize() Size { + return Size{Width: r.Width, Height: r.Height} +} + +// ScreenManager tracks connected screens and the active screen. +// +// manager.SetScreens(detectedScreens) +// primary := manager.GetPrimary() +type ScreenManager struct { + mu sync.RWMutex + screens []*Screen + current *Screen + primary *Screen +} + +// SetScreens replaces the full list of known screens and recomputes primary. +// +// manager.SetScreens(platformDetectedScreens) +func (m *ScreenManager) SetScreens(screens []*Screen) { + m.mu.Lock() + defer m.mu.Unlock() + m.screens = screens + m.primary = nil + for _, screen := range screens { + if screen.IsPrimary { + m.primary = screen + break + } + } + if m.current == nil && m.primary != nil { + m.current = m.primary + } +} + +// SetCurrent marks the given screen as the currently active one. +// +// manager.SetCurrent(screenUnderPointer) +func (m *ScreenManager) SetCurrent(screen *Screen) { + m.mu.Lock() + m.current = screen + m.mu.Unlock() +} + +// GetAll returns all registered screens. +// +// for _, s := range manager.GetAll() { renderMonitorPreview(s) } +func (m *ScreenManager) GetAll() []*Screen { + m.mu.RLock() + defer m.mu.RUnlock() + out := make([]*Screen, len(m.screens)) + copy(out, m.screens) + return out +} + +// GetPrimary returns the primary screen, or nil if none has been registered. +// +// primary := manager.GetPrimary() +func (m *ScreenManager) GetPrimary() *Screen { + m.mu.RLock() + defer m.mu.RUnlock() + return m.primary +} + +// GetCurrent returns the most recently active screen, or the primary if unset. +// +// current := manager.GetCurrent() +func (m *ScreenManager) GetCurrent() *Screen { + m.mu.RLock() + defer m.mu.RUnlock() + if m.current != nil { + return m.current + } + return m.primary +} diff --git a/stubs/wails/pkg/application/services.go b/stubs/wails/pkg/application/services.go new file mode 100644 index 0000000..e4b3f57 --- /dev/null +++ b/stubs/wails/pkg/application/services.go @@ -0,0 +1,105 @@ +package application + +import ( + "context" + "reflect" +) + +// ServiceOptions provides optional parameters when constructing a Service. +// +// svc := NewServiceWithOptions(&myImpl, ServiceOptions{Name: "MyService", Route: "/api"}) +type ServiceOptions struct { + // Name overrides the service name used for logging and debugging. + // When empty the name is derived from the ServiceName interface or the type name. + Name string + + // Route mounts the service on the internal asset server at this prefix + // if the service instance implements http.Handler. + Route string + + // MarshalError serialises error values returned by service methods to JSON. + // A nil return falls back to the globally configured error handler. + MarshalError func(error) []byte +} + +// DefaultServiceOptions holds the default values used when no ServiceOptions +// are passed to NewService. +var DefaultServiceOptions = ServiceOptions{} + +// Service wraps a bound type instance for registration with the application. +// The zero value is invalid; obtain valid values via NewService or NewServiceWithOptions. +// +// svc := NewService(&myStruct) +// app.RegisterService(svc) +type Service struct { + instance any + options ServiceOptions +} + +// NewService wraps instance in a Service using DefaultServiceOptions. +// +// svc := NewService(&MyController{}) +func NewService[T any](instance *T) Service { + return Service{instance: instance, options: DefaultServiceOptions} +} + +// NewServiceWithOptions wraps instance in a Service with explicit options. +// +// svc := NewServiceWithOptions(&MyController{}, ServiceOptions{Name: "ctrl", Route: "/ctrl"}) +func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service { + service := NewService(instance) + service.options = options + return service +} + +// Instance returns the underlying pointer originally passed to NewService. +// +// ctrl := svc.Instance().(*MyController) +func (s Service) Instance() any { + return s.instance +} + +// Options returns the ServiceOptions associated with this service. +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 "MyService" } +type ServiceName interface { + ServiceName() string +} + +// ServiceStartup is an optional interface for service initialisation. +// Called during application startup in registration order. +// A non-nil return aborts startup and surfaces the error to the caller. +// +// func (s *MyService) ServiceStartup(ctx context.Context, opts ServiceOptions) error { +// return s.connect(ctx) +// } +type ServiceStartup interface { + ServiceStartup(ctx context.Context, options ServiceOptions) error +} + +// ServiceShutdown is an optional interface for service cleanup. +// Called during application shutdown in reverse registration order, +// after all user-provided shutdown hooks have run. +// +// func (s *MyService) ServiceShutdown() error { return s.db.Close() } +type ServiceShutdown interface { + ServiceShutdown() error +} + +// getServiceName resolves the display name for a service using, in order: +// the explicit options name, the ServiceName interface, and the reflect type name. +func getServiceName(service Service) string { + if service.options.Name != "" { + return service.options.Name + } + if named, ok := service.Instance().(ServiceName); ok { + return named.ServiceName() + } + return reflect.TypeOf(service.Instance()).Elem().String() +} 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..a8b48bb --- /dev/null +++ b/stubs/wails/pkg/application/webview_window_options.go @@ -0,0 +1,574 @@ +package application + +import "github.com/wailsapp/wails/v3/pkg/events" + +// WindowState represents the visible state of a window. +// +// opts := application.WebviewWindowOptions{StartState: application.WindowStateMaximised} +type WindowState int + +const ( + // WindowStateNormal is the default windowed state. + WindowStateNormal WindowState = iota + // WindowStateMinimised is the minimised (iconified) state. + WindowStateMinimised + // WindowStateMaximised fills the available work area. + WindowStateMaximised + // WindowStateFullscreen occupies the full screen. + WindowStateFullscreen +) + +// WindowStartPosition controls where a window first appears. +// +// opts := application.WebviewWindowOptions{InitialPosition: application.WindowCentered} +type WindowStartPosition int + +const ( + // WindowCentered places the window at the centre of the screen. + WindowCentered WindowStartPosition = 0 + // WindowXY places the window at the coordinates given by X and Y. + WindowXY WindowStartPosition = 1 +) + +// BackgroundType determines how the window background is rendered. +// +// opts := application.WebviewWindowOptions{BackgroundType: application.BackgroundTypeTranslucent} +type BackgroundType int + +const ( + // BackgroundTypeSolid renders a solid opaque background. + BackgroundTypeSolid BackgroundType = iota + // BackgroundTypeTransparent renders a fully transparent background. + BackgroundTypeTransparent + // BackgroundTypeTranslucent renders a frosted/blur translucent background. + BackgroundTypeTranslucent +) + +// NewRGB constructs an opaque RGBA value from red, green, blue components. +// +// colour := application.NewRGB(0xff, 0x00, 0x00) // red +func NewRGB(red, green, blue uint8) RGBA { + return RGBA{Red: red, Green: green, Blue: blue, Alpha: 255} +} + +// NewRGBPtr encodes red, green, blue as a packed *uint32 in 0x00BBGGRR order. +// +// ptr := application.NewRGBPtr(0xff, 0x80, 0x00) +func NewRGBPtr(red, green, blue uint8) *uint32 { + value := uint32(red) | uint32(green)<<8 | uint32(blue)<<16 + return &value +} + +/******* Windows Options *******/ + +// BackdropType selects the translucent backdrop style on Windows 11. +// +// opts.Windows = application.WindowsWindow{BackdropType: application.Mica} +type BackdropType int32 + +const ( + // Auto lets the system choose the best backdrop. + Auto BackdropType = 0 + // None disables the translucent backdrop. + None BackdropType = 1 + // Mica applies the Mica material (Windows 11 22H2+). + Mica BackdropType = 2 + // Acrylic applies the Acrylic blur-behind material. + Acrylic BackdropType = 3 + // Tabbed applies the Tabbed/MICA-Alt material. + Tabbed BackdropType = 4 +) + +// CoreWebView2PermissionKind enumerates the types of WebView2 permissions. +type CoreWebView2PermissionKind uint32 + +const ( + CoreWebView2PermissionKindUnknownPermission CoreWebView2PermissionKind = iota + CoreWebView2PermissionKindMicrophone + CoreWebView2PermissionKindCamera + CoreWebView2PermissionKindGeolocation + CoreWebView2PermissionKindNotifications + CoreWebView2PermissionKindOtherSensors + CoreWebView2PermissionKindClipboardRead +) + +// CoreWebView2PermissionState enumerates the allowed states for a WebView2 permission. +type CoreWebView2PermissionState uint32 + +const ( + CoreWebView2PermissionStateDefault CoreWebView2PermissionState = iota + CoreWebView2PermissionStateAllow + CoreWebView2PermissionStateDeny +) + +// Theme selects between the system default, dark, and light UI themes on Windows. +// +// opts.Windows = application.WindowsWindow{Theme: application.Dark} +type Theme int + +const ( + // SystemDefault follows the OS theme and reacts to changes. + SystemDefault Theme = 0 + // Dark forces the dark theme. + Dark Theme = 1 + // Light forces the light theme. + Light Theme = 2 +) + +// WindowTheme defines colour overrides for a single window activity state. +// +// wt := &application.WindowTheme{TitleBarColour: application.NewRGBPtr(0x1e, 0x1e, 0x1e)} +type WindowTheme struct { + // BorderColour is the colour of the window border (0x00BBGGRR). + BorderColour *uint32 + // TitleBarColour is the colour of the title bar (0x00BBGGRR). + TitleBarColour *uint32 + // TitleTextColour is the colour of the title text (0x00BBGGRR). + TitleTextColour *uint32 +} + +// TextTheme defines foreground and background colours for a text element. +type TextTheme struct { + // Text is the foreground colour. + Text *uint32 + // Background is the background colour. + Background *uint32 +} + +// MenuBarTheme defines colours for a menu bar in default, hovered, and selected states. +type MenuBarTheme struct { + // Default is the theme used when the item is neither hovered nor selected. + Default *TextTheme + // Hover is the theme used when the pointer is over the item. + Hover *TextTheme + // Selected is the theme used when the item is selected. + Selected *TextTheme +} + +// ThemeSettings defines custom colours used in dark or light mode. +// Colour values use packed 0x00BBGGRR encoding — use NewRGBPtr to construct them. +// +// ts := application.ThemeSettings{ +// DarkModeActive: &application.WindowTheme{TitleBarColour: application.NewRGBPtr(0x1e, 0x1e, 0x2e)}, +// } +type ThemeSettings struct { + // DarkModeActive applies when the window is active in dark mode. + DarkModeActive *WindowTheme + // DarkModeInactive applies when the window is inactive in dark mode. + DarkModeInactive *WindowTheme + // LightModeActive applies when the window is active in light mode. + LightModeActive *WindowTheme + // LightModeInactive applies when the window is inactive in light mode. + LightModeInactive *WindowTheme + // DarkModeMenuBar applies to the menu bar in dark mode. + DarkModeMenuBar *MenuBarTheme + // LightModeMenuBar applies to the menu bar in light mode. + LightModeMenuBar *MenuBarTheme +} + +// WindowsWindow contains Windows-specific window configuration. +// +// opts.Windows = application.WindowsWindow{BackdropType: application.Mica, Theme: application.Dark} +type WindowsWindow struct { + // BackdropType selects the translucent material. Requires Windows 11 22621+. + // Only used when BackgroundType is BackgroundTypeTranslucent. + // Default: Auto + BackdropType BackdropType + + // DisableIcon removes the application icon from the title bar. + // Default: false + DisableIcon bool + + // Theme selects between dark, light, or system-default title bar styling. + // Default: SystemDefault + Theme Theme + + // CustomTheme overrides colours for dark/light active/inactive states. + // Default: zero value (no override) + CustomTheme ThemeSettings + + // DisableFramelessWindowDecorations suppresses Aero shadow and rounded corners + // when the window is frameless. Rounded corners require Windows 11. + // Default: false + DisableFramelessWindowDecorations bool + + // WindowMask sets the window shape via a PNG with an alpha channel. + // Default: nil + WindowMask []byte + + // WindowMaskDraggable allows the window to be dragged via the mask area. + // Default: false + WindowMaskDraggable bool + + // ResizeDebounceMS debounces WebView2 redraws during resize. + // Default: 0 + ResizeDebounceMS uint16 + + // WindowDidMoveDebounceMS debounces the WindowDidMove event. + // Default: 0 + WindowDidMoveDebounceMS uint16 + + // EventMapping translates platform window events to common event types. + // Default: nil + EventMapping map[events.WindowEventType]events.WindowEventType + + // HiddenOnTaskbar excludes the window from the taskbar. + // Default: false + HiddenOnTaskbar bool + + // EnableSwipeGestures enables horizontal swipe gestures. + // Default: false + EnableSwipeGestures bool + + // Menu is the window-level menu. + Menu *Menu + + // Permissions configures WebView2 permission grants. + // Default: nil (system defaults apply) + Permissions map[CoreWebView2PermissionKind]CoreWebView2PermissionState + + // ExStyle is the extended window style flags (WS_EX_*). + ExStyle int + + // GeneralAutofillEnabled enables general autofill in WebView2. + GeneralAutofillEnabled bool + + // PasswordAutosaveEnabled enables password autosave in WebView2. + PasswordAutosaveEnabled bool +} + +/****** Mac Options *******/ + +// MacBackdrop controls the translucency of the macOS window background. +// +// opts.Mac = application.MacWindow{Backdrop: application.MacBackdropTranslucent} +type MacBackdrop int + +const ( + // MacBackdropNormal renders a standard opaque background. + MacBackdropNormal MacBackdrop = iota + // MacBackdropTransparent renders a fully transparent background. + MacBackdropTransparent + // MacBackdropTranslucent renders a frosted vibrancy background. + MacBackdropTranslucent + // MacBackdropLiquidGlass applies Apple's Liquid Glass effect (macOS 15+, + // falls back to translucent on earlier releases). + MacBackdropLiquidGlass +) + +// MacToolbarStyle controls the toolbar layout relative to the title bar. +// +// opts.Mac.TitleBar.ToolbarStyle = application.MacToolbarStyleUnified +type MacToolbarStyle int + +const ( + // MacToolbarStyleAutomatic lets the system decide based on configuration. + MacToolbarStyleAutomatic MacToolbarStyle = iota + // MacToolbarStyleExpanded shows the toolbar below the title bar. + MacToolbarStyleExpanded + // MacToolbarStylePreference shows the toolbar below the title bar with + // equal-width items where possible. + MacToolbarStylePreference + // MacToolbarStyleUnified merges the title bar and toolbar into one row. + MacToolbarStyleUnified + // MacToolbarStyleUnifiedCompact is like Unified but with reduced margins. + MacToolbarStyleUnifiedCompact +) + +// MacLiquidGlassStyle defines the appearance of the Liquid Glass effect. +type MacLiquidGlassStyle int + +const ( + // LiquidGlassStyleAutomatic lets the system choose the best style. + LiquidGlassStyleAutomatic MacLiquidGlassStyle = iota + // LiquidGlassStyleLight uses a light glass appearance. + LiquidGlassStyleLight + // LiquidGlassStyleDark uses a dark glass appearance. + LiquidGlassStyleDark + // LiquidGlassStyleVibrant uses an enhanced vibrant glass appearance. + LiquidGlassStyleVibrant +) + +// NSVisualEffectMaterial mirrors NSVisualEffectMaterial from the macOS SDK. +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 selects the material automatically based on Style. + NSVisualEffectMaterialAuto NSVisualEffectMaterial = -1 +) + +// MacLiquidGlass configures the Liquid Glass visual effect (macOS 15+). +// +// opts.Mac.LiquidGlass = application.MacLiquidGlass{Style: application.LiquidGlassStyleDark} +type MacLiquidGlass struct { + // Style of the glass effect. + Style MacLiquidGlassStyle + + // Material for the NSVisualEffectView fallback. + // Use NSVisualEffectMaterialAuto for automatic selection based on Style. + Material NSVisualEffectMaterial + + // CornerRadius specifies the corner radius in points (0 for square corners). + CornerRadius float64 + + // TintColor adds an optional colour tint to the glass (nil for no tint). + TintColor *RGBA + + // GroupID merges multiple glass windows into a single visual group. + GroupID string + + // GroupSpacing is the spacing between grouped glass elements in points. + GroupSpacing float64 +} + +// MacAppearanceType selects a Cocoa NSAppearance for the window. +// +// opts.Mac = application.MacWindow{Appearance: application.NSAppearanceNameDarkAqua} +type MacAppearanceType string + +const ( + // DefaultAppearance follows the system setting. + DefaultAppearance MacAppearanceType = "" + // NSAppearanceNameAqua is the standard light system appearance. + NSAppearanceNameAqua MacAppearanceType = "NSAppearanceNameAqua" + // NSAppearanceNameDarkAqua is the standard dark system appearance. + NSAppearanceNameDarkAqua MacAppearanceType = "NSAppearanceNameDarkAqua" + // NSAppearanceNameVibrantLight is the light vibrant appearance. + NSAppearanceNameVibrantLight MacAppearanceType = "NSAppearanceNameVibrantLight" + // NSAppearanceNameAccessibilityHighContrastAqua is high-contrast light. + NSAppearanceNameAccessibilityHighContrastAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastAqua" + // NSAppearanceNameAccessibilityHighContrastDarkAqua is high-contrast dark. + NSAppearanceNameAccessibilityHighContrastDarkAqua MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastDarkAqua" + // NSAppearanceNameAccessibilityHighContrastVibrantLight is high-contrast light vibrant. + NSAppearanceNameAccessibilityHighContrastVibrantLight MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantLight" + // NSAppearanceNameAccessibilityHighContrastVibrantDark is high-contrast dark vibrant. + NSAppearanceNameAccessibilityHighContrastVibrantDark MacAppearanceType = "NSAppearanceNameAccessibilityHighContrastVibrantDark" +) + +// MacWindowLevel controls the z-order stacking group of the window. +// +// opts.Mac = application.MacWindow{WindowLevel: application.MacWindowLevelFloating} +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 controls how the window participates in macOS +// Spaces and fullscreen. Values correspond to NSWindowCollectionBehavior bits +// and may be combined with bitwise OR. +// +// opts.Mac.CollectionBehavior = application.MacWindowCollectionBehaviorCanJoinAllSpaces | +// application.MacWindowCollectionBehaviorFullScreenAuxiliary +type MacWindowCollectionBehavior int + +const ( + // MacWindowCollectionBehaviorDefault uses FullScreenPrimary for backwards compatibility. + MacWindowCollectionBehaviorDefault MacWindowCollectionBehavior = 0 + // MacWindowCollectionBehaviorCanJoinAllSpaces shows the window on all Spaces. + MacWindowCollectionBehaviorCanJoinAllSpaces MacWindowCollectionBehavior = 1 << 0 + // MacWindowCollectionBehaviorMoveToActiveSpace moves the window to the active Space when shown. + MacWindowCollectionBehaviorMoveToActiveSpace MacWindowCollectionBehavior = 1 << 1 + // MacWindowCollectionBehaviorManaged is the default managed window behaviour. + MacWindowCollectionBehaviorManaged MacWindowCollectionBehavior = 1 << 2 + // MacWindowCollectionBehaviorTransient marks the window as temporary. + MacWindowCollectionBehaviorTransient MacWindowCollectionBehavior = 1 << 3 + // MacWindowCollectionBehaviorStationary keeps the window stationary during Space switches. + MacWindowCollectionBehaviorStationary MacWindowCollectionBehavior = 1 << 4 + // MacWindowCollectionBehaviorParticipatesInCycle includes the window in Cmd+` cycling. + MacWindowCollectionBehaviorParticipatesInCycle MacWindowCollectionBehavior = 1 << 5 + // MacWindowCollectionBehaviorIgnoresCycle excludes the window from Cmd+` cycling. + MacWindowCollectionBehaviorIgnoresCycle MacWindowCollectionBehavior = 1 << 6 + // MacWindowCollectionBehaviorFullScreenPrimary allows the window to enter fullscreen. + MacWindowCollectionBehaviorFullScreenPrimary MacWindowCollectionBehavior = 1 << 7 + // MacWindowCollectionBehaviorFullScreenAuxiliary allows the window to overlay fullscreen apps. + MacWindowCollectionBehaviorFullScreenAuxiliary MacWindowCollectionBehavior = 1 << 8 + // MacWindowCollectionBehaviorFullScreenNone prevents the window from entering fullscreen (10.7+). + MacWindowCollectionBehaviorFullScreenNone MacWindowCollectionBehavior = 1 << 9 + // MacWindowCollectionBehaviorFullScreenAllowsTiling allows side-by-side tiling (10.11+). + MacWindowCollectionBehaviorFullScreenAllowsTiling MacWindowCollectionBehavior = 1 << 11 + // MacWindowCollectionBehaviorFullScreenDisallowsTiling prevents tiling in fullscreen (10.11+). + MacWindowCollectionBehaviorFullScreenDisallowsTiling MacWindowCollectionBehavior = 1 << 12 +) + +// MacWebviewPreferences configures Safari-level webview behaviour on macOS. +type MacWebviewPreferences struct { + // TabFocusesLinks enables keyboard navigation to links via Tab. + TabFocusesLinks bool + // TextInteractionEnabled allows the user to select and interact with text. + TextInteractionEnabled bool + // FullscreenEnabled allows the webview to enter fullscreen. + FullscreenEnabled bool + // AllowsBackForwardNavigationGestures enables horizontal swipe for navigation. + AllowsBackForwardNavigationGestures bool +} + +// MacTitleBar configures the macOS title bar appearance. +// +// opts.Mac = application.MacWindow{TitleBar: application.MacTitleBarHiddenInset} +type MacTitleBar struct { + // AppearsTransparent makes the title bar background transparent. + AppearsTransparent bool + // Hide removes the title bar entirely. + Hide bool + // HideTitle omits the window title text. + HideTitle bool + // FullSizeContent extends the content area behind the title bar. + FullSizeContent bool + // UseToolbar renders a toolbar in place of the standard title bar. + UseToolbar bool + // HideToolbarSeparator removes the separator line below the toolbar. + HideToolbarSeparator bool + // ShowToolbarWhenFullscreen keeps the toolbar visible in fullscreen mode. + ShowToolbarWhenFullscreen bool + // ToolbarStyle controls how the toolbar relates to the title bar. + ToolbarStyle MacToolbarStyle +} + +// MacTitleBarDefault is the stock macOS title bar with all decorations visible. +var MacTitleBarDefault = MacTitleBar{} + +// MacTitleBarHidden hides the title text and extends content behind the title bar, +// while keeping the traffic-light window controls visible. +var MacTitleBarHidden = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, +} + +// MacTitleBarHiddenInset is like MacTitleBarHidden but uses an inset toolbar so the +// traffic lights sit slightly further from the window edge. +var MacTitleBarHiddenInset = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, +} + +// MacTitleBarHiddenInsetUnified is like MacTitleBarHiddenInset but merges the toolbar +// and title bar into a single unified row. +var MacTitleBarHiddenInsetUnified = MacTitleBar{ + AppearsTransparent: true, + HideTitle: true, + FullSizeContent: true, + UseToolbar: true, + HideToolbarSeparator: true, + ToolbarStyle: MacToolbarStyleUnified, +} + +// MacWindow contains macOS-specific window configuration. +// +// opts.Mac = application.MacWindow{ +// Backdrop: application.MacBackdropTranslucent, +// TitleBar: application.MacTitleBarHiddenInset, +// Appearance: application.NSAppearanceNameDarkAqua, +// } +type MacWindow struct { + // Backdrop controls the translucency of the window background. + Backdrop MacBackdrop + // DisableShadow removes the drop shadow cast by the window. + DisableShadow bool + // TitleBar configures the title bar appearance. + TitleBar MacTitleBar + // Appearance sets a specific NSAppearance for the window. + Appearance MacAppearanceType + // InvisibleTitleBarHeight sets the height (in points) of a draggable but + // invisible title bar region at the top of the content area. + InvisibleTitleBarHeight int + // EventMapping translates platform window events to common event types. + EventMapping map[events.WindowEventType]events.WindowEventType + // EnableFraudulentWebsiteWarnings shows browser-level phishing warnings. + // Default: false + EnableFraudulentWebsiteWarnings bool + // WebviewPreferences configures Safari webview-level preferences. + WebviewPreferences MacWebviewPreferences + // WindowLevel controls the z-order stacking group of the window. + WindowLevel MacWindowLevel + // CollectionBehavior controls how the window interacts with Spaces and fullscreen. + CollectionBehavior MacWindowCollectionBehavior + // LiquidGlass configures the Liquid Glass visual effect (macOS 15+). + LiquidGlass MacLiquidGlass +} + +/******** Linux Options ********/ + +// WebviewGpuPolicy controls hardware acceleration for the Linux webview. +// +// opts.Linux = application.LinuxWindow{WebviewGpuPolicy: application.WebviewGpuPolicyAlways} +type WebviewGpuPolicy int + +const ( + // WebviewGpuPolicyAlways always enables hardware acceleration. + WebviewGpuPolicyAlways WebviewGpuPolicy = iota + // WebviewGpuPolicyOnDemand enables acceleration as requested by web content. + WebviewGpuPolicyOnDemand + // WebviewGpuPolicyNever always disables hardware acceleration. + WebviewGpuPolicyNever +) + +// LinuxMenuStyle controls how the application menu is displayed on Linux (GTK4 only). +// On GTK3 builds this option is ignored and MenuBar style is always used. +// +// opts.Linux = application.LinuxWindow{MenuStyle: application.LinuxMenuStylePrimaryMenu} +type LinuxMenuStyle int + +const ( + // LinuxMenuStyleMenuBar shows a traditional menu bar below the title bar. + LinuxMenuStyleMenuBar LinuxMenuStyle = iota + // LinuxMenuStylePrimaryMenu shows a primary menu button in the header bar (GNOME style). + LinuxMenuStylePrimaryMenu +) + +// LinuxWindow contains Linux-specific window configuration. +// +// opts.Linux = application.LinuxWindow{ +// WindowIsTranslucent: true, +// WebviewGpuPolicy: application.WebviewGpuPolicyAlways, +// } +type LinuxWindow struct { + // Icon is the window icon shown when the window is minimised. + // Provide PNG-encoded image data. + Icon []byte + + // WindowIsTranslucent makes the window background transparent. + WindowIsTranslucent bool + + // WebviewGpuPolicy sets the hardware acceleration policy for the webview. + // Defaults to WebviewGpuPolicyNever when LinuxWindow is nil in options. + WebviewGpuPolicy WebviewGpuPolicy + + // WindowDidMoveDebounceMS is the debounce time in milliseconds for the + // WindowDidMove event. + WindowDidMoveDebounceMS uint16 + + // Menu is the window-level menu. + Menu *Menu + + // MenuStyle controls how the menu is displayed (GTK4 only; ignored on GTK3). + MenuStyle LinuxMenuStyle +} diff --git a/stubs/wails/pkg/application/window.go b/stubs/wails/pkg/application/window.go new file mode 100644 index 0000000..db2c3a9 --- /dev/null +++ b/stubs/wails/pkg/application/window.go @@ -0,0 +1,240 @@ +package application + +import ( + "unsafe" + + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Window is the interface satisfied by both WebviewWindow and BrowserWindow. +// All methods mirror the Wails v3 Window interface exactly, including unexported +// platform hooks required for internal dispatch. +// +// var w Window = app.Window.NewWithOptions(opts) +// w.SetTitle("My App").Show() +type Window interface { + // Identity + + // w.ID() → 1 + ID() uint + // w.Name() → "main" + Name() string + + // Lifecycle + + // w.Show().Focus() + Show() Window + // w.Hide() + Hide() Window + // w.Close() + Close() + // w.Focus() + Focus() + // w.Run() + Run() + // w.Restore() + Restore() + + // Geometry + + // x, y := w.Position() + Position() (int, int) + // x, y := w.RelativePosition() + RelativePosition() (int, int) + // w, h := w.Size() + Size() (width int, height int) + // px := w.Width() + Width() int + // px := w.Height() + Height() int + // r := w.Bounds() + Bounds() Rect + // w.SetBounds(Rect{X: 0, Y: 0, Width: 1280, Height: 800}) + SetBounds(bounds Rect) + // w.SetPosition(100, 200) + SetPosition(x, y int) + // w.SetRelativePosition(0, 0) + SetRelativePosition(x, y int) Window + // w.SetSize(1280, 800) + SetSize(width, height int) Window + // w.SetMinSize(640, 480) + SetMinSize(minWidth, minHeight int) Window + // w.SetMaxSize(3840, 2160) + SetMaxSize(maxWidth, maxHeight int) Window + // w.Center() + Center() + + // Title and content + + // t := w.Title() — not in interface, provided by concrete types + // w.SetTitle("My App") + SetTitle(title string) Window + // w.SetURL("https://example.com") + SetURL(url string) Window + // w.SetHTML("

Hello

") + SetHTML(html string) Window + + // Visibility states + + // w.IsVisible() + IsVisible() bool + // w.IsFullscreen() + IsFullscreen() bool + // w.IsMaximised() + IsMaximised() bool + // w.IsMinimised() + IsMinimised() bool + // w.IsFocused() + IsFocused() bool + // w.IsIgnoreMouseEvents() + IsIgnoreMouseEvents() bool + // w.Resizable() + Resizable() bool + + // Window state transitions + + // w.Fullscreen() + Fullscreen() Window + // w.UnFullscreen() + UnFullscreen() + // w.Maximise() + Maximise() Window + // w.UnMaximise() + UnMaximise() + // w.Minimise() + Minimise() Window + // w.UnMinimise() + UnMinimise() + // w.ToggleFullscreen() + ToggleFullscreen() + // w.ToggleMaximise() + ToggleMaximise() + // w.SnapAssist() + SnapAssist() + + // Style + + // w.SetAlwaysOnTop(true) + SetAlwaysOnTop(b bool) Window + // w.SetBackgroundColour(application.NewRGBA(0, 0, 0, 255)) + SetBackgroundColour(colour RGBA) Window + // w.SetFrameless(true) + SetFrameless(frameless bool) Window + // w.SetResizable(false) + SetResizable(b bool) Window + // w.SetIgnoreMouseEvents(true) + SetIgnoreMouseEvents(ignore bool) Window + // w.SetMinimiseButtonState(ButtonHidden) + SetMinimiseButtonState(state ButtonState) Window + // w.SetMaximiseButtonState(ButtonDisabled) + SetMaximiseButtonState(state ButtonState) Window + // w.SetCloseButtonState(ButtonEnabled) + SetCloseButtonState(state ButtonState) Window + // w.SetEnabled(false) + SetEnabled(enabled bool) + // w.SetContentProtection(true) + SetContentProtection(protection bool) Window + + // Menu + + // w.SetMenu(myMenu) + SetMenu(menu *Menu) + // w.ShowMenuBar() + ShowMenuBar() + // w.HideMenuBar() + HideMenuBar() + // w.ToggleMenuBar() + ToggleMenuBar() + // w.ToggleFrameless() + ToggleFrameless() + + // WebView + + // w.ExecJS("document.title = 'Hello'") + ExecJS(js string) + // w.Reload() + Reload() + // w.ForceReload() + ForceReload() + // w.OpenDevTools() + OpenDevTools() + // w.OpenContextMenu(&ContextMenuData{Name: "main"}) + OpenContextMenu(data *ContextMenuData) + + // Zoom + + // w.Zoom() + Zoom() + // w.ZoomIn() + ZoomIn() + // w.ZoomOut() + ZoomOut() + // w.ZoomReset() + ZoomReset() Window + // z := w.GetZoom() + GetZoom() float64 + // w.SetZoom(1.5) + SetZoom(magnification float64) Window + + // Events + + // cancel := w.OnWindowEvent(events.Common.WindowFocus, func(e *WindowEvent) { ... }) + OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + // cancel := w.RegisterHook(events.Common.WindowClose, func(e *WindowEvent) { ... }) + RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() + // w.EmitEvent("user:login", payload) + EmitEvent(name string, data ...any) bool + // w.DispatchWailsEvent(&CustomEvent{Name: "init"}) + DispatchWailsEvent(event *CustomEvent) + + // Screen and display + + // screen, err := w.GetScreen() + GetScreen() (*Screen, error) + // borders := w.GetBorderSizes() + GetBorderSizes() *LRTB + + // Constraints + + // w.EnableSizeConstraints() + EnableSizeConstraints() + // w.DisableSizeConstraints() + DisableSizeConstraints() + + // Modal + + // w.AttachModal(modalWindow) + AttachModal(modalWindow Window) + + // Utilities + + // w.Flash(true) + Flash(enabled bool) + // err := w.Print() + Print() error + // w.Error("something went wrong: %s", details) + Error(message string, args ...any) + // w.Info("window ready") + Info(message string, args ...any) + + // Platform + + // ptr := w.NativeWindow() + NativeWindow() unsafe.Pointer + + // Internal platform hooks — implemented by concrete window types. + + handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) + InitiateFrontendDropProcessing(filenames []string, x int, y int) + HandleMessage(message string) + HandleWindowEvent(id uint) + HandleKeyEvent(acceleratorString string) + shouldUnconditionallyClose() bool + cut() + copy() + paste() + undo() + redo() + delete() + selectAll() +} diff --git a/stubs/wails/pkg/application/window_manager_expanded.go b/stubs/wails/pkg/application/window_manager_expanded.go new file mode 100644 index 0000000..59bcd98 --- /dev/null +++ b/stubs/wails/pkg/application/window_manager_expanded.go @@ -0,0 +1,32 @@ +package application + +// Get returns the first window whose Name() matches the given name, or nil if +// no match is found. +// +// w := app.Window.Get("main") +// if w == nil { panic("main window not registered") } +func (wm *WindowManager) Get(name string) Window { + wm.mu.RLock() + defer wm.mu.RUnlock() + for _, window := range wm.windows { + if window.Name() == name { + return window + } + } + return nil +} + +// GetByID returns the window with the given numeric ID, or nil if not found. +// +// w := app.Window.GetByID(1) +// if w != nil { w.Focus() } +func (wm *WindowManager) GetByID(id uint) Window { + wm.mu.RLock() + defer wm.mu.RUnlock() + for _, window := range wm.windows { + if window.ID() == id { + return window + } + } + return nil +} diff --git a/stubs/wails/pkg/events/events.go b/stubs/wails/pkg/events/events.go index 3f3204d..6b163de 100644 --- a/stubs/wails/pkg/events/events.go +++ b/stubs/wails/pkg/events/events.go @@ -1,5 +1,8 @@ package events +// ApplicationEventType identifies an application-level event. +type ApplicationEventType int + // WindowEventType identifies a window event emitted by the application layer. type WindowEventType int