From 84b9021ea9005037207c3557e5807af8a4eb11e4 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Tue, 16 Dec 2025 06:05:40 +1100 Subject: [PATCH] docs: Update dialogs documentation to match actual v3 API (#4793) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * All the documentation files have been updated. Here's a summary of the changes I made: ## Summary of Documentation Updates ### Key API Corrections Made: 1. **Dialog Creation**: - Changed `app.InfoDialog()` → `application.InfoDialog()` (package-level functions) - Also documented `app.Dialog.Info()` via DialogManager 2. **Question Dialog Buttons**: - Removed non-existent `SetButtons("Save", "Don't Save", "Cancel")` method - Now uses `AddButton("label")` which returns a `*Button` - Button callbacks via `button.OnClick(func() { ... })` 3. **Default/Cancel Buttons**: - Changed `SetDefaultButton("Cancel")` (string) → `SetDefaultButton(cancelBtn)` (`*Button`) - Changed `SetCancelButton("Cancel")` (string) → `SetCancelButton(cancelBtn)` (`*Button`) - Also documented fluent `SetAsDefault()` and `SetAsCancel()` methods on Button 4. **File Dialog Filters**: - Changed `SetFilters([]application.FileFilter{...})` → `AddFilter("Display Name", "*.ext;*.ext2")` 5. **Save Dialog Filename**: - Changed `SetDefaultFilename()` → `SetFilename()` 6. **Folder Selection**: - Removed non-existent `app.SelectFolderDialog()` - Now uses `application.OpenFileDialog().CanChooseDirectories(true).CanChooseFiles(false)` 7. **Show() Return Value**: - Removed incorrect `result, err := dialog.Show()` pattern - `Show()` returns nothing - responses are handled via button callbacks ### Files Modified: - `docs/src/content/docs/features/dialogs/message.mdx` - Complete rewrite of question dialog examples - `docs/src/content/docs/features/dialogs/overview.mdx` - Updated all code examples - `docs/src/content/docs/features/dialogs/file.mdx` - Updated file dialog examples - `v3/UNRELEASED_CHANGELOG.md` - Added changelog entry * All changes are complete. Here's a summary of what was updated: ## Summary I've updated the Wails v3 dialogs documentation to match the actual API. The documentation now consistently uses: ### DialogManager Pattern (Recommended) - `app.Dialog.Info()`, `app.Dialog.Question()`, `app.Dialog.Warning()`, `app.Dialog.Error()` - `app.Dialog.OpenFile()`, `app.Dialog.SaveFile()` This is consistent with other runtime features like `app.Window`, `app.Event`, etc. ### Key API Corrections 1. **Dialog Creation**: Both `app.Dialog.*()` (DialogManager) and `application.*Dialog()` (package-level) are documented 2. **Question Dialog Buttons**: Use `AddButton("label")` which returns `*Button`, then `button.OnClick(callback)` 3. **Default/Cancel Buttons**: `SetDefaultButton(button)` and `SetCancelButton(button)` take `*Button` pointers 4. **File Filters**: Use `AddFilter("Display Name", "*.ext")` instead of `SetFilters()` 5. **Save Dialog Filename**: Use `SetFilename()` instead of `SetDefaultFilename()` 6. **Folder Selection**: Use `app.Dialog.OpenFile().CanChooseDirectories(true).CanChooseFiles(false)` ### Files Modified - `docs/src/content/docs/features/dialogs/overview.mdx` - `docs/src/content/docs/features/dialogs/message.mdx` - `docs/src/content/docs/features/dialogs/file.mdx` - `v3/UNRELEASED_CHANGELOG.md` * Audit complete! All documentation files now consistently use `app.Dialog.*` for all code examples. Here's the summary: ## Audit Complete All dialog documentation has been updated to consistently use the `app.Dialog.*` pattern: ### Changes Made: **message.mdx:** - All examples now use `app.Dialog.Info()`, `app.Dialog.Question()`, `app.Dialog.Warning()`, `app.Dialog.Error()` - Function signatures updated to accept `app *application.App` parameter - Nested dialog calls within callbacks also updated **file.mdx:** - All examples now use `app.Dialog.OpenFile()`, `app.Dialog.SaveFile()` - Function signatures updated to accept `app *application.App` parameter - All error/info dialogs within file operations also updated **overview.mdx:** - Already consistent from earlier edits ### Documentation Structure: Each file now properly documents both approaches in a dedicated section: ```go // Via DialogManager (recommended when you have an app instance): app.Dialog.Info() app.Dialog.OpenFile() // Package-level functions (useful in callbacks or when app isn't in scope): application.InfoDialog() application.OpenFileDialog() ``` But all code examples use the `app.Dialog.*` pattern for consistency with other runtime features like `app.Window`, `app.Event`, etc. * docs: Fix reference/dialogs.mdx and reference/application.mdx API documentation Updated docs to match actual Wails v3 Dialogs API: - reference/dialogs.mdx: Complete rewrite with correct API - Use `app.Dialog.OpenFile()` and `app.Dialog.SaveFile()` instead of `app.OpenFileDialog()` - Use `AddFilter("name", "pattern")` instead of `SetFilters([]FileFilter{...})` - Use `SetFilename()` instead of `SetDefaultFilename()` - Use `SetDirectory()` instead of `SetDefaultDirectory()` - Remove non-existent `SelectFolderDialog()` - use `OpenFile().CanChooseDirectories(true).CanChooseFiles(false)` - Use `AddButton()` with callbacks instead of `SetButtons()` - Use `SetDefaultButton(*Button)` instead of `SetDefaultButton(int)` - Document that `Show()` returns nothing, use callbacks - reference/application.mdx: Fix Dialog Methods section - Use `app.Dialog.*` manager pattern - Show correct Question dialog with button callbacks - Fix file dialog examples with `AddFilter()` - Remove `SelectFolderDialog()` reference 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * docs: Remove package-level dialog function references Remove all references to package-level dialog functions (application.InfoDialog(), application.OpenFileDialog(), etc.) from documentation. Only the app.Dialog manager pattern should be used. Updated files: - reference/dialogs.mdx - features/dialogs/overview.mdx - features/dialogs/message.mdx - features/dialogs/file.mdx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * refactor: Remove package-level dialog functions in favor of app.Dialog manager BREAKING CHANGE: Remove package-level dialog functions. Use app.Dialog manager instead. Removed functions: - application.InfoDialog() - application.QuestionDialog() - application.WarningDialog() - application.ErrorDialog() - application.OpenFileDialog() - application.SaveFileDialog() Use the Dialog manager pattern instead: - app.Dialog.Info() - app.Dialog.Question() - app.Dialog.Warning() - app.Dialog.Error() - app.Dialog.OpenFile() - app.Dialog.SaveFile() This aligns dialogs with other runtime managers like app.Window and app.Event. Updated files: - v3/pkg/application/application.go - Remove exported dialog functions - v3/pkg/application/dialog_manager.go - Use internal newMessageDialog/newOpenFileDialog - v3/pkg/application/messageprocessor_dialog.go - Use internal dialog constructors - v3/examples/* - Update all examples to use app.Dialog pattern - v3/internal/commands/appimage_testfiles/main.go - Update test file - v3/UNRELEASED_CHANGELOG.md - Document breaking change 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude * fix: Use application.Get() in dialogs-basic example and correct filter docs - Update dialogs-basic helper functions to use application.Get() instead of passing app through function parameters - Fix incorrect documentation claiming space/comma delimiters work for filter patterns (only semicolons are supported) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- .../content/docs/features/dialogs/file.mdx | 447 +++++++------- .../content/docs/features/dialogs/message.mdx | 451 +++++++------- .../docs/features/dialogs/overview.mdx | 318 ++++++---- .../content/docs/reference/application.mdx | 116 ++-- docs/src/content/docs/reference/dialogs.mdx | 576 +++++++++--------- v3/UNRELEASED_CHANGELOG.md | 2 + v3/examples/build/main.go | 12 +- v3/examples/clipboard/main.go | 14 +- v3/examples/dialogs-basic/main.go | 37 +- v3/examples/dialogs/main.go | 114 ++-- v3/examples/events-bug/main.go | 6 +- v3/examples/file-association/main.go | 2 +- v3/examples/liquid-glass/main.go | 4 +- v3/examples/panic-handling/main.go | 6 +- v3/examples/systray-menu/main.go | 4 +- v3/examples/window/main.go | 14 +- .../commands/appimage_testfiles/main.go | 14 +- v3/pkg/application/application.go | 24 - v3/pkg/application/dialog_manager.go | 16 +- v3/pkg/application/messageprocessor_dialog.go | 8 +- 20 files changed, 1125 insertions(+), 1060 deletions(-) diff --git a/docs/src/content/docs/features/dialogs/file.mdx b/docs/src/content/docs/features/dialogs/file.mdx index e50e49dee..bba6e3f03 100644 --- a/docs/src/content/docs/features/dialogs/file.mdx +++ b/docs/src/content/docs/features/dialogs/file.mdx @@ -10,20 +10,28 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; ## File dialogs Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations. + +## Creating File Dialogs + +File dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.OpenFile() +app.Dialog.SaveFile() +``` + ## Open File dialog Select files to open: ```go -path, err := app.OpenFileDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.gif"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() -if err != nil { +if err != nil || path == "" { return } @@ -39,15 +47,13 @@ openFile(path) ### Single File Selection ```go -path, err := app.OpenFileDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Open Document"). - SetFilters([]application.FileFilter{ - {DisplayName: "Text Files", Pattern: "*.txt"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() -if err != nil { +if err != nil || path == "" { // User cancelled or error occurred return } @@ -59,11 +65,9 @@ data, _ := os.ReadFile(path) ### Multiple File Selection ```go -paths, err := app.OpenFileDialog(). +paths, err := app.Dialog.OpenFile(). SetTitle("Select Images"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.jpeg;*.gif"}, - }). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). PromptForMultipleSelection() if err != nil { @@ -79,7 +83,7 @@ for _, path := range paths { ### With Default Directory ```go -path, err := app.OpenFileDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Open File"). SetDirectory("/Users/me/Documents"). PromptForSingleSelection() @@ -90,16 +94,13 @@ path, err := app.OpenFileDialog(). Choose where to save: ```go -path, err := app.SaveFileDialog(). - SetTitle("Save Document"). - SetDefaultFilename("document.txt"). - SetFilters([]application.FileFilter{ - {DisplayName: "Text Files", Pattern: "*.txt"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() -if err != nil { +if err != nil || path == "" { return } @@ -115,48 +116,48 @@ saveFile(path, data) ### With Default Filename ```go -path, err := app.SaveFileDialog(). - SetTitle("Export Data"). - SetDefaultFilename("export.csv"). - SetFilters([]application.FileFilter{ - {DisplayName: "CSV Files", Pattern: "*.csv"}, - }). +path, err := app.Dialog.SaveFile(). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). PromptForSingleSelection() ``` ### With Default Directory ```go -path, err := app.SaveFileDialog(). - SetTitle("Save File"). +path, err := app.Dialog.SaveFile(). SetDirectory("/Users/me/Documents"). - SetDefaultFilename("untitled.txt"). + SetFilename("untitled.txt"). PromptForSingleSelection() ``` ### Overwrite Confirmation ```go -path, err := app.SaveFileDialog(). - SetTitle("Save File"). - SetDefaultFilename("document.txt"). +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). PromptForSingleSelection() -if err != nil { +if err != nil || path == "" { return } // Check if file exists if _, err := os.Stat(path); err == nil { - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Confirm Overwrite"). - SetMessage("File already exists. Overwrite?"). - SetButtons("Overwrite", "Cancel"). - Show() - - if result != "Overwrite" { - return - } + SetMessage("File already exists. Overwrite?") + + overwriteBtn := dialog.AddButton("Overwrite") + overwriteBtn.OnClick(func() { + saveFile(path, data) + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() + return } saveFile(path, data) @@ -164,14 +165,16 @@ saveFile(path, data) ## Select Folder dialog -Choose a directory: +Choose a directory using the open file dialog with directory selection enabled: ```go -path, err := app.SelectFolderDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() -if err != nil { +if err != nil || path == "" { return } @@ -187,66 +190,45 @@ exportToFolder(path) ### With Default Directory ```go -path, err := app.SelectFolderDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Folder"). SetDirectory("/Users/me/Documents"). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() ``` ## File Filters +Use the `AddFilter()` method to add file type filters to dialogs. Each call adds a new filter option. + ### Basic Filters ```go -filters := []application.FileFilter{ - {DisplayName: "Text Files", Pattern: "*.txt"}, - {DisplayName: "All Files", Pattern: "*.*"}, -} - -path, _ := app.OpenFileDialog(). - SetFilters(filters). +path, _ := app.Dialog.OpenFile(). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() ``` ### Multiple Extensions +Use semicolons to specify multiple extensions in a single filter: + ```go -filters := []application.FileFilter{ - { - DisplayName: "Images", - Pattern: "*.png;*.jpg;*.jpeg;*.gif;*.bmp", - }, - { - DisplayName: "Documents", - Pattern: "*.txt;*.doc;*.docx;*.pdf", - }, - { - DisplayName: "All Files", - Pattern: "*.*", - }, -} +dialog := app.Dialog.OpenFile(). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). + AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf"). + AddFilter("All Files", "*.*") ``` -### Platform-Specific Patterns +### Pattern Format -**Windows:** -```go -// Semicolon-separated -Pattern: "*.png;*.jpg;*.gif" -``` +Use **semicolons** to separate multiple extensions in a single filter: -**macOS/Linux:** ```go -// Space or comma-separated (both work) -Pattern: "*.png *.jpg *.gif" -// or -Pattern: "*.png,*.jpg,*.gif" -``` - -**Cross-platform (recommended):** -```go -// Use semicolons (works on all platforms) -Pattern: "*.png;*.jpg;*.gif" +// Multiple extensions separated by semicolons +AddFilter("Images", "*.png;*.jpg;*.gif") ``` ## Complete Examples @@ -254,41 +236,40 @@ Pattern: "*.png;*.jpg;*.gif" ### Open Image File ```go -func openImage() (image.Image, error) { - path, err := app.OpenFileDialog(). +func openImage(app *application.App) (image.Image, error) { + path, err := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - { - DisplayName: "Images", - Pattern: "*.png;*.jpg;*.jpeg;*.gif;*.bmp", - }, - }). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp"). PromptForSingleSelection() - + if err != nil { return nil, err } - + + if path == "" { + return nil, errors.New("no file selected") + } + // Open and decode image file, err := os.Open(path) if err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Open Failed"). SetMessage(err.Error()). Show() return nil, err } defer file.Close() - + img, _, err := image.Decode(file) if err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Invalid Image"). SetMessage("Could not decode image file."). Show() return nil, err } - + return img, nil } ``` @@ -296,214 +277,216 @@ func openImage() (image.Image, error) { ### Save Document with Validation ```go -func saveDocument(content string) error { - path, err := app.SaveFileDialog(). - SetTitle("Save Document"). - SetDefaultFilename("document.txt"). - SetFilters([]application.FileFilter{ - {DisplayName: "Text Files", Pattern: "*.txt"}, - {DisplayName: "Markdown Files", Pattern: "*.md"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). +func saveDocument(app *application.App, content string) { + path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("Markdown Files", "*.md"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() - - if err != nil { - return err + + if err != nil || path == "" { + return } - + // Validate extension ext := filepath.Ext(path) if ext != ".txt" && ext != ".md" { - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Confirm Extension"). - SetMessage(fmt.Sprintf("Save as %s file?", ext)). - SetButtons("Save", "Cancel"). - Show() - - if result != "Save" { - return errors.New("cancelled") - } + SetMessage(fmt.Sprintf("Save as %s file?", ext)) + + saveBtn := dialog.AddButton("Save") + saveBtn.OnClick(func() { + doSave(app, path, content) + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() + return } - - // Save file + + doSave(app, path, content) +} + +func doSave(app *application.App, path, content string) { if err := os.WriteFile(path, []byte(content), 0644); err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Save Failed"). SetMessage(err.Error()). Show() - return err + return } - - app.InfoDialog(). + + app.Dialog.Info(). SetTitle("Saved"). SetMessage("Document saved successfully!"). Show() - - return nil } ``` ### Batch File Processing ```go -func processMultipleFiles() { - paths, err := app.OpenFileDialog(). +func processMultipleFiles(app *application.App) { + paths, err := app.Dialog.OpenFile(). SetTitle("Select Files to Process"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForMultipleSelection() - + if err != nil || len(paths) == 0 { return } - + // Confirm processing - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Confirm Processing"). - SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths))). - SetButtons("Process", "Cancel"). - Show() - - if result != "Process" { - return - } - - // Process files - var errors []error - for i, path := range paths { - if err := processFile(path); err != nil { - errors = append(errors, err) + SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths))) + + processBtn := dialog.AddButton("Process") + processBtn.OnClick(func() { + // Process files + var errs []error + for i, path := range paths { + if err := processFile(path); err != nil { + errs = append(errs, err) + } + + // Update progress + // app.Event.Emit("progress", map[string]interface{}{ + // "current": i + 1, + // "total": len(paths), + // }) + _ = i // suppress unused variable warning in example } - - // Update progress - app.EmitEvent("progress", map[string]interface{}{ - "current": i + 1, - "total": len(paths), - }) - } - - // Show results - if len(errors) > 0 { - app.WarningDialog(). - SetTitle("Processing Complete"). - SetMessage(fmt.Sprintf("Processed %d files with %d errors.", - len(paths), len(errors))). - Show() - } else { - app.InfoDialog(). - SetTitle("Success"). - SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))). - Show() - } + + // Show results + if len(errs) > 0 { + app.Dialog.Warning(). + SetTitle("Processing Complete"). + SetMessage(fmt.Sprintf("Processed %d files with %d errors.", + len(paths), len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Success"). + SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))). + Show() + } + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` ### Export with Folder Selection ```go -func exportData(data []byte) error { +func exportData(app *application.App, data []byte) { // Select output folder - folder, err := app.SelectFolderDialog(). + folder, err := app.Dialog.OpenFile(). SetTitle("Select Export Folder"). SetDirectory(getDefaultExportFolder()). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() - - if err != nil { - return err + + if err != nil || folder == "" { + return } - + // Generate filename - filename := fmt.Sprintf("export_%s.csv", + filename := fmt.Sprintf("export_%s.csv", time.Now().Format("2006-01-02_15-04-05")) path := filepath.Join(folder, filename) - + // Save file if err := os.WriteFile(path, data, 0644); err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Export Failed"). SetMessage(err.Error()). Show() - return err + return } - + // Show success with option to open folder - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Export Complete"). - SetMessage(fmt.Sprintf("Exported to %s", filename)). - SetButtons("Open Folder", "OK"). - Show() - - if result == "Open Folder" { + SetMessage(fmt.Sprintf("Exported to %s", filename)) + + openBtn := dialog.AddButton("Open Folder") + openBtn.OnClick(func() { openFolder(folder) - } - - return nil + }) + + dialog.AddButton("OK") + dialog.Show() } ``` ### Import with Validation ```go -func importConfiguration() error { - path, err := app.OpenFileDialog(). +func importConfiguration(app *application.App) { + path, err := app.Dialog.OpenFile(). SetTitle("Import Configuration"). - SetFilters([]application.FileFilter{ - {DisplayName: "JSON Files", Pattern: "*.json"}, - {DisplayName: "YAML Files", Pattern: "*.yaml;*.yml"}, - }). + AddFilter("JSON Files", "*.json"). + AddFilter("YAML Files", "*.yaml;*.yml"). PromptForSingleSelection() - - if err != nil { - return err + + if err != nil || path == "" { + return } - + // Read file data, err := os.ReadFile(path) if err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Read Failed"). SetMessage(err.Error()). Show() - return err + return } - + // Validate configuration config, err := parseConfig(data) if err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Invalid Configuration"). SetMessage("File is not a valid configuration."). Show() - return err + return } - + // Confirm import - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Confirm Import"). - SetMessage("Import this configuration?"). - SetButtons("Import", "Cancel"). - Show() - - if result != "Import" { - return errors.New("cancelled") - } - - // Apply configuration - if err := applyConfig(config); err != nil { - app.ErrorDialog(). - SetTitle("Import Failed"). - SetMessage(err.Error()). + SetMessage("Import this configuration?") + + importBtn := dialog.AddButton("Import") + importBtn.OnClick(func() { + // Apply configuration + if err := applyConfig(config); err != nil { + app.Dialog.Error(). + SetTitle("Import Failed"). + SetMessage(err.Error()). + Show() + return + } + + app.Dialog.Info(). + SetTitle("Success"). + SetMessage("Configuration imported successfully!"). Show() - return err - } - - app.InfoDialog(). - SetTitle("Success"). - SetMessage("Configuration imported successfully!"). - Show() - - return nil + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` diff --git a/docs/src/content/docs/features/dialogs/message.mdx b/docs/src/content/docs/features/dialogs/message.mdx index 5200a34ad..4b718aaa6 100644 --- a/docs/src/content/docs/features/dialogs/message.mdx +++ b/docs/src/content/docs/features/dialogs/message.mdx @@ -10,12 +10,26 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; ## Message dialogs Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default. + +## Creating Dialogs + +Message dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +``` + +All methods return a `*MessageDialog` that can be configured using method chaining. + ## Information dialog Display informational messages: ```go -app.InfoDialog(). +app.Dialog.Info(). SetTitle("Success"). SetMessage("File saved successfully!"). Show() @@ -30,16 +44,16 @@ app.InfoDialog(). **Example - Save confirmation:** ```go -func saveFile(path string, data []byte) error { +func saveFile(app *application.App, path string, data []byte) error { if err := os.WriteFile(path, data, 0644); err != nil { return err } - - app.InfoDialog(). + + app.Dialog.Info(). SetTitle("File Saved"). SetMessage(fmt.Sprintf("Saved to %s", filepath.Base(path))). Show() - + return nil } ``` @@ -49,7 +63,7 @@ func saveFile(path string, data []byte) error { Show warnings: ```go -app.WarningDialog(). +app.Dialog.Warning(). SetTitle("Warning"). SetMessage("This action cannot be undone."). Show() @@ -64,11 +78,11 @@ app.WarningDialog(). **Example - Disk space warning:** ```go -func checkDiskSpace() { +func checkDiskSpace(app *application.App) { available := getDiskSpace() - + if available < 100*1024*1024 { // Less than 100MB - app.WarningDialog(). + app.Dialog.Warning(). SetTitle("Low Disk Space"). SetMessage(fmt.Sprintf("Only %d MB available.", available/(1024*1024))). Show() @@ -81,7 +95,7 @@ func checkDiskSpace() { Display errors: ```go -app.ErrorDialog(). +app.Dialog.Error(). SetTitle("Error"). SetMessage("Failed to connect to server."). Show() @@ -96,41 +110,48 @@ app.ErrorDialog(). **Example - Network error:** ```go -func fetchData(url string) ([]byte, error) { +func fetchData(app *application.App, url string) ([]byte, error) { resp, err := http.Get(url) if err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Network Error"). SetMessage(fmt.Sprintf("Failed to connect: %v", err)). Show() return nil, err } defer resp.Body.Close() - + return io.ReadAll(resp.Body) } ``` ## Question dialog -Ask users questions: +Ask users questions and handle responses via button callbacks: ```go -result, err := app.QuestionDialog(). +dialog := app.Dialog.Question(). SetTitle("Confirm"). - SetMessage("Save changes before closing?"). - SetButtons("Save", "Don't Save", "Cancel"). - SetDefaultButton("Save"). - Show() + SetMessage("Save changes before closing?") -switch result { -case "Save": +save := dialog.AddButton("Save") +save.OnClick(func() { saveChanges() -case "Don't Save": +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { // Continue without saving -case "Cancel": +}) + +cancel := dialog.AddButton("Cancel") +cancel.OnClick(func() { // Don't close -} +}) + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() ``` **Use cases:** @@ -142,32 +163,34 @@ case "Cancel": **Example - Unsaved changes:** ```go -func closeDocument() bool { +func closeDocument(app *application.App) { if !hasUnsavedChanges() { - return true + doClose() + return } - - result, err := app.QuestionDialog(). + + dialog := app.Dialog.Question(). SetTitle("Unsaved Changes"). - SetMessage("Do you want to save your changes?"). - SetButtons("Save", "Don't Save", "Cancel"). - SetDefaultButton("Save"). - Show() - - if err != nil { - return false - } - - switch result { - case "Save": - return saveDocument() - case "Don't Save": - return true - case "Cancel": - return false - } - - return false + SetMessage("Do you want to save your changes?") + + save := dialog.AddButton("Save") + save.OnClick(func() { + if saveDocument() { + doClose() + } + }) + + dontSave := dialog.AddButton("Don't Save") + dontSave.OnClick(func() { + doClose() + }) + + cancel := dialog.AddButton("Cancel") + // Cancel button has no callback - just closes the dialog + + dialog.SetDefaultButton(save) + dialog.SetCancelButton(cancel) + dialog.Show() } ``` @@ -176,7 +199,7 @@ func closeDocument() bool { ### Title and Message ```go -dialog := app.InfoDialog(). +dialog := app.Dialog.Info(). SetTitle("Operation Complete"). SetMessage("All files have been processed successfully.") ``` @@ -190,30 +213,85 @@ dialog := app.InfoDialog(). **Single button (Info/Warning/Error):** +Info, warning, and error dialogs display a default "OK" button: + ```go -// Default "OK" button -app.InfoDialog(). +app.Dialog.Info(). SetMessage("Done!"). Show() ``` -**Multiple buttons (Question):** +You can also add custom buttons: ```go -result, _ := app.QuestionDialog(). - SetMessage("Choose an action"). - SetButtons("Option 1", "Option 2", "Option 3"). - Show() +dialog := app.Dialog.Info(). + SetMessage("Done!") +ok := dialog.AddButton("Got it!") +dialog.SetDefaultButton(ok) +dialog.Show() ``` -**Default button:** +**Multiple buttons (Question):** + +Use `AddButton()` to add buttons, which returns a `*Button` you can configure: ```go -app.QuestionDialog(). - SetMessage("Delete file?"). - SetButtons("Delete", "Cancel"). - SetDefaultButton("Cancel"). // Safe option - Show() +dialog := app.Dialog.Question(). + SetMessage("Choose an action") + +option1 := dialog.AddButton("Option 1") +option1.OnClick(func() { + handleOption1() +}) + +option2 := dialog.AddButton("Option 2") +option2.OnClick(func() { + handleOption2() +}) + +option3 := dialog.AddButton("Option 3") +option3.OnClick(func() { + handleOption3() +}) + +dialog.Show() +``` + +**Default and Cancel buttons:** + +Use `SetDefaultButton()` to specify which button is highlighted and triggered by Enter. +Use `SetCancelButton()` to specify which button is triggered by Escape. + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + performDelete() +}) + +cancelBtn := dialog.AddButton("Cancel") +// No callback needed - just dismisses dialog + +dialog.SetDefaultButton(cancelBtn) // Safe option as default +dialog.SetCancelButton(cancelBtn) // Escape triggers Cancel +dialog.Show() +``` + +You can also use the fluent `SetAsDefault()` and `SetAsCancel()` methods on buttons: + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +dialog.AddButton("Delete").OnClick(func() { + performDelete() +}) + +dialog.AddButton("Cancel").SetAsDefault().SetAsCancel() + +dialog.Show() ``` **Best practices:** @@ -222,16 +300,29 @@ app.QuestionDialog(). - **Safe default:** Non-destructive action - **Order matters:** Most likely action first (except Cancel) +### Custom Icon + +Set a custom icon for the dialog: + +```go +app.Dialog.Info(). + SetTitle("Custom Icon Example"). + SetMessage("Using a custom icon"). + SetIcon(myIconBytes). + Show() +``` + ### Window Attachment Attach to specific window: ```go -dialog := app.QuestionDialog(). +dialog := app.Dialog.Question(). SetMessage("Window-specific question"). AttachToWindow(window) -result, _ := dialog.Show() +dialog.AddButton("OK") +dialog.Show() ``` **Benefits:** @@ -244,163 +335,111 @@ result, _ := dialog.Show() ### Confirm Destructive Action ```go -func deleteFiles(paths []string) error { +func deleteFiles(app *application.App, paths []string) { // Confirm deletion message := fmt.Sprintf("Delete %d file(s)?", len(paths)) if len(paths) == 1 { message = fmt.Sprintf("Delete %s?", filepath.Base(paths[0])) } - - result, err := app.QuestionDialog(). + + dialog := app.Dialog.Question(). SetTitle("Confirm Delete"). - SetMessage(message). - SetButtons("Delete", "Cancel"). - SetDefaultButton("Cancel"). - Show() - - if err != nil || result != "Delete" { - return errors.New("cancelled") - } - - // Perform deletion - var errors []error - for _, path := range paths { - if err := os.Remove(path); err != nil { - errors = append(errors, err) - } - } - - // Show result - if len(errors) > 0 { - app.ErrorDialog(). - SetTitle("Delete Failed"). - SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errors))). - Show() - return fmt.Errorf("%d errors", len(errors)) - } - - app.InfoDialog(). - SetTitle("Delete Complete"). - SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))). - Show() - - return nil -} -``` + SetMessage(message) -### Error Handling with Retry - -```go -func connectToServer(url string) error { - for attempts := 0; attempts < 3; attempts++ { - if err := tryConnect(url); err == nil { - return nil - } - - if attempts < 2 { - result, _ := app.QuestionDialog(). - SetTitle("Connection Failed"). - SetMessage("Failed to connect. Retry?"). - SetButtons("Retry", "Cancel"). - Show() - - if result != "Retry" { - return errors.New("cancelled") + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + // Perform deletion + var errs []error + for _, path := range paths { + if err := os.Remove(path); err != nil { + errs = append(errs, err) } } - } - - app.ErrorDialog(). - SetTitle("Connection Failed"). - SetMessage("Could not connect after 3 attempts."). - Show() - - return errors.New("connection failed") -} -``` -### Multi-Step Workflow - -```go -func exportAndEmail() { - // Step 1: Confirm export - result, _ := app.QuestionDialog(). - SetTitle("Export and Email"). - SetMessage("Export data and send via email?"). - SetButtons("Continue", "Cancel"). - Show() - - if result != "Continue" { - return - } - - // Step 2: Export data - data, err := exportData() - if err != nil { - app.ErrorDialog(). - SetTitle("Export Failed"). - SetMessage(err.Error()). - Show() - return - } - - // Step 3: Send email - if err := sendEmail(data); err != nil { - app.ErrorDialog(). - SetTitle("Email Failed"). - SetMessage(err.Error()). - Show() - return - } - - // Step 4: Success - app.InfoDialog(). - SetTitle("Success"). - SetMessage("Data exported and emailed successfully!"). - Show() -} -``` - -### Validation with Feedback - -```go -func validateAndSave(data string) error { - // Validate - if err := validate(data); err != nil { - app.WarningDialog(). - SetTitle("Validation Warning"). - SetMessage(err.Error()). - Show() - - result, _ := app.QuestionDialog(). - SetMessage("Save anyway?"). - SetButtons("Save", "Cancel"). - SetDefaultButton("Cancel"). - Show() - - if result != "Save" { - return errors.New("cancelled") + // Show result + if len(errs) > 0 { + app.Dialog.Error(). + SetTitle("Delete Failed"). + SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errs))). + Show() + } else { + app.Dialog.Info(). + SetTitle("Delete Complete"). + SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))). + Show() } - } - - // Save - if err := save(data); err != nil { - app.ErrorDialog(). - SetTitle("Save Failed"). - SetMessage(err.Error()). - Show() - return err - } - - app.InfoDialog(). - SetTitle("Saved"). - SetMessage("Data saved successfully!"). - Show() - - return nil + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` +### Quit Confirmation + +```go +func confirmQuit(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Quit"). + SetMessage("You have unsaved work. Are you sure you want to quit?") + + yes := dialog.AddButton("Yes") + yes.OnClick(func() { + app.Quit() + }) + + no := dialog.AddButton("No") + dialog.SetDefaultButton(no) + dialog.Show() +} +``` + +### Update Dialog with Download Option + +```go +func showUpdateDialog(app *application.App) { + dialog := app.Dialog.Question(). + SetTitle("Update"). + SetMessage("A new version is available. The cancel button is selected when pressing escape.") + + download := dialog.AddButton("📥 Download") + download.OnClick(func() { + app.Dialog.Info().SetMessage("Downloading...").Show() + }) + + cancel := dialog.AddButton("Cancel") + + dialog.SetDefaultButton(download) + dialog.SetCancelButton(cancel) + dialog.Show() +} +``` + +### Custom Icon Question + +```go +func showCustomIconQuestion(app *application.App, iconBytes []byte) { + dialog := app.Dialog.Question(). + SetTitle("Custom Icon Example"). + SetMessage("Using a custom icon"). + SetIcon(iconBytes) + + likeIt := dialog.AddButton("I like it!") + likeIt.OnClick(func() { + app.Dialog.Info().SetMessage("Thanks!").Show() + }) + + notKeen := dialog.AddButton("Not so keen...") + notKeen.OnClick(func() { + app.Dialog.Info().SetMessage("Too bad!").Show() + }) + + dialog.SetDefaultButton(likeIt) + dialog.Show() +} + ## Best Practices ### ✅ Do diff --git a/docs/src/content/docs/features/dialogs/overview.mdx b/docs/src/content/docs/features/dialogs/overview.mdx index 4ca82482f..25dcaf901 100644 --- a/docs/src/content/docs/features/dialogs/overview.mdx +++ b/docs/src/content/docs/features/dialogs/overview.mdx @@ -15,33 +15,48 @@ Wails provides **native system dialogs** that work across all platforms: message ```go // Information dialog -app.InfoDialog(). +app.Dialog.Info(). SetTitle("Success"). SetMessage("File saved successfully!"). Show() -// Question dialog -result, _ := app.QuestionDialog(). +// Question dialog with button callbacks +dialog := app.Dialog.Question(). SetTitle("Confirm"). - SetMessage("Delete this file?"). - SetButtons("Delete", "Cancel"). - Show() + SetMessage("Delete this file?") -if result == "Delete" { +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { deleteFile() -} +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) +dialog.SetCancelButton(cancelBtn) +dialog.Show() // File open dialog -path, _ := app.OpenFileDialog(). +path, _ := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForSingleSelection() ``` **That's it!** Native dialogs with minimal code. +## Accessing Dialogs + +Dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +app.Dialog.OpenFile() +app.Dialog.SaveFile() +``` + ## dialog Types ### Information dialog @@ -49,7 +64,7 @@ path, _ := app.OpenFileDialog(). Display simple messages: ```go -app.InfoDialog(). +app.Dialog.Info(). SetTitle("Welcome"). SetMessage("Welcome to our application!"). Show() @@ -65,7 +80,7 @@ app.InfoDialog(). Show warnings: ```go -app.WarningDialog(). +app.Dialog.Warning(). SetTitle("Warning"). SetMessage("This action cannot be undone."). Show() @@ -81,7 +96,7 @@ app.WarningDialog(). Display errors: ```go -app.ErrorDialog(). +app.Dialog.Error(). SetTitle("Error"). SetMessage("Failed to save file: " + err.Error()). Show() @@ -94,19 +109,22 @@ app.ErrorDialog(). ### Question dialog -Ask users questions: +Ask users questions and handle responses via button callbacks: ```go -result, err := app.QuestionDialog(). +dialog := app.Dialog.Question(). SetTitle("Confirm Delete"). - SetMessage("Are you sure you want to delete this file?"). - SetButtons("Delete", "Cancel"). - SetDefaultButton("Cancel"). - Show() + SetMessage("Are you sure you want to delete this file?") -if result == "Delete" { +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { deleteFile() -} +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) +dialog.SetCancelButton(cancelBtn) +dialog.Show() ``` **Use cases:** @@ -121,15 +139,13 @@ if result == "Delete" { Select files to open: ```go -path, err := app.OpenFileDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.gif"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). + AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() -if err == nil { +if err == nil && path != "" { openFile(path) } ``` @@ -137,15 +153,15 @@ if err == nil { **Multiple selection:** ```go -paths, err := app.OpenFileDialog(). +paths, err := app.Dialog.OpenFile(). SetTitle("Select Images"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForMultipleSelection() -for _, path := range paths { - processFile(path) +if err == nil { + for _, path := range paths { + processFile(path) + } } ``` @@ -154,30 +170,29 @@ for _, path := range paths { Choose where to save: ```go -path, err := app.SaveFileDialog(). - SetTitle("Save Document"). - SetDefaultFilename("document.txt"). - SetFilters([]application.FileFilter{ - {DisplayName: "Text Files", Pattern: "*.txt"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). +path, err := app.Dialog.SaveFile(). + SetFilename("document.txt"). + AddFilter("Text Files", "*.txt"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() -if err == nil { +if err == nil && path != "" { saveFile(path) } ``` ### Select Folder dialog -Choose a directory: +Choose a directory using the open file dialog with directory selection enabled: ```go -path, err := app.SelectFolderDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() -if err == nil { +if err == nil && path != "" { exportToFolder(path) } ``` @@ -187,42 +202,67 @@ if err == nil { ### Title and Message ```go -dialog := app.InfoDialog(). +dialog := app.Dialog.Info(). SetTitle("Success"). SetMessage("Operation completed successfully!") ``` ### Buttons -**Predefined buttons:** +**Default button for simple dialogs:** + +Info, warning, and error dialogs display a default "OK" button: ```go -// OK button -app.InfoDialog(). +app.Dialog.Info(). SetMessage("Done!"). Show() - -// Yes/No buttons -result, _ := app.QuestionDialog(). - SetMessage("Continue?"). - SetButtons("Yes", "No"). - Show() - -// Custom buttons -result, _ := app.QuestionDialog(). - SetMessage("Choose action"). - SetButtons("Save", "Don't Save", "Cancel"). - Show() ``` -**Default button:** +**Custom buttons for question dialogs:** + +Use `AddButton()` to add buttons, which returns a `*Button` you can configure with callbacks: ```go -app.QuestionDialog(). - SetMessage("Delete file?"). - SetButtons("Delete", "Cancel"). - SetDefaultButton("Cancel"). // Highlighted by default - Show() +dialog := app.Dialog.Question(). + SetMessage("Choose action") + +save := dialog.AddButton("Save") +save.OnClick(func() { + saveDocument() +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { + discardChanges() +}) + +cancel := dialog.AddButton("Cancel") +// No callback needed - just dismisses dialog + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() +``` + +**Default and Cancel buttons:** + +Use `SetDefaultButton()` to specify which button is highlighted and triggered by Enter. +Use `SetCancelButton()` to specify which button is triggered by Escape. + +```go +dialog := app.Dialog.Question(). + SetMessage("Delete file?") + +deleteBtn := dialog.AddButton("Delete") +deleteBtn.OnClick(func() { + performDelete() +}) + +cancelBtn := dialog.AddButton("Cancel") +dialog.SetDefaultButton(cancelBtn) // Safe option highlighted by default +dialog.SetCancelButton(cancelBtn) // Escape triggers Cancel +dialog.Show() ``` ### Window Attachment @@ -230,7 +270,7 @@ app.QuestionDialog(). Attach dialog to specific window: ```go -dialog := app.InfoDialog(). +dialog := app.Dialog.Info(). SetMessage("Window-specific message"). AttachToWindow(window) @@ -258,10 +298,12 @@ dialog.Show() **Example:** ```go // Appears as sheet on macOS - app.QuestionDialog(). + dialog := app.Dialog.Question(). SetMessage("Save changes?"). - AttachToWindow(window). - Show() + AttachToWindow(window) + dialog.AddButton("Yes") + dialog.AddButton("No") + dialog.Show() ``` @@ -278,7 +320,7 @@ dialog.Show() **Example:** ```go // Modal dialog on Windows - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Error"). SetMessage("Operation failed"). Show() @@ -297,7 +339,7 @@ dialog.Show() **Example:** ```go // GTK dialog on Linux - app.InfoDialog(). + app.Dialog.Info(). SetMessage("Update complete"). Show() ``` @@ -309,35 +351,41 @@ dialog.Show() ### Confirm Before Destructive Action ```go -func deleteFile(path string) error { - result, err := app.QuestionDialog(). +func deleteFile(app *application.App, path string) { + dialog := app.Dialog.Question(). SetTitle("Confirm Delete"). - SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path))). - SetButtons("Delete", "Cancel"). - SetDefaultButton("Cancel"). - Show() - - if err != nil || result != "Delete" { - return errors.New("cancelled") - } - - return os.Remove(path) + SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path))) + + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + if err := os.Remove(path); err != nil { + app.Dialog.Error(). + SetTitle("Delete Failed"). + SetMessage(err.Error()). + Show() + } + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetDefaultButton(cancelBtn) + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` ### Error Handling with dialog ```go -func saveDocument(path string, data []byte) { +func saveDocument(app *application.App, path string, data []byte) { if err := os.WriteFile(path, data, 0644); err != nil { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Save Failed"). SetMessage(fmt.Sprintf("Could not save file: %v", err)). Show() return } - - app.InfoDialog(). + + app.Dialog.Info(). SetTitle("Success"). SetMessage("File saved successfully!"). Show() @@ -347,27 +395,29 @@ func saveDocument(path string, data []byte) { ### File Selection with Validation ```go -func selectImageFile() (string, error) { - path, err := app.OpenFileDialog(). +func selectImageFile(app *application.App) (string, error) { + path, err := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.jpeg;*.gif"}, - }). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). PromptForSingleSelection() - + if err != nil { return "", err } - + + if path == "" { + return "", errors.New("no file selected") + } + // Validate file if !isValidImage(path) { - app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Invalid File"). SetMessage("Selected file is not a valid image."). Show() return "", errors.New("invalid image") } - + return path, nil } ``` @@ -375,45 +425,43 @@ func selectImageFile() (string, error) { ### Multi-Step dialog Flow ```go -func exportData() { +func exportData(app *application.App) { // Step 1: Confirm export - result, _ := app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Export Data"). - SetMessage("Export all data to CSV?"). - SetButtons("Export", "Cancel"). - Show() - - if result != "Export" { - return - } - - // Step 2: Select destination - path, err := app.SaveFileDialog(). - SetTitle("Save Export"). - SetDefaultFilename("export.csv"). - SetFilters([]application.FileFilter{ - {DisplayName: "CSV Files", Pattern: "*.csv"}, - }). - PromptForSingleSelection() - - if err != nil { - return - } - - // Step 3: Perform export - if err := performExport(path); err != nil { - app.ErrorDialog(). - SetTitle("Export Failed"). - SetMessage(err.Error()). + SetMessage("Export all data to CSV?") + + exportBtn := dialog.AddButton("Export") + exportBtn.OnClick(func() { + // Step 2: Select destination + path, err := app.Dialog.SaveFile(). + SetFilename("export.csv"). + AddFilter("CSV Files", "*.csv"). + PromptForSingleSelection() + + if err != nil || path == "" { + return + } + + // Step 3: Perform export + if err := performExport(path); err != nil { + app.Dialog.Error(). + SetTitle("Export Failed"). + SetMessage(err.Error()). + Show() + return + } + + // Step 4: Success + app.Dialog.Info(). + SetTitle("Export Complete"). + SetMessage("Data exported successfully!"). Show() - return - } - - // Step 4: Success - app.InfoDialog(). - SetTitle("Export Complete"). - SetMessage("Data exported successfully!"). - Show() + }) + + cancelBtn := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` diff --git a/docs/src/content/docs/reference/application.mdx b/docs/src/content/docs/reference/application.mdx index 36039b7e8..050578a62 100644 --- a/docs/src/content/docs/reference/application.mdx +++ b/docs/src/content/docs/reference/application.mdx @@ -261,96 +261,78 @@ app.OnApplicationEvent(application.EventApplicationShutdown, func(e *application }) ``` -## dialog Methods +## Dialog Methods -### InfoDialog() +Dialogs are accessed through the `app.Dialog` manager. See [Dialogs API](/reference/dialogs/) for complete reference. -Creates an information dialog. +### Message Dialogs ```go -func (a *App) InfoDialog() InfoDialog -``` - -**Example:** - -```go -app.InfoDialog(). +// Information dialog +app.Dialog.Info(). SetTitle("Success"). SetMessage("Operation completed!"). Show() -``` -### QuestionDialog() - -Creates a question dialog. - -```go -func (a *App) QuestionDialog() QuestionDialog -``` - -**Example:** - -```go -result, _ := app.QuestionDialog(). - SetTitle("Confirm"). - SetMessage("Continue?"). - SetButtons("Yes", "No"). +// Error dialog +app.Dialog.Error(). + SetTitle("Error"). + SetMessage("Something went wrong."). Show() -if result == "Yes" { - // Continue -} +// Warning dialog +app.Dialog.Warning(). + SetTitle("Warning"). + SetMessage("This action cannot be undone."). + Show() ``` -### ErrorDialog() +### Question Dialogs -Creates an error dialog. +Question dialogs use button callbacks to handle user responses: ```go -func (a *App) ErrorDialog() ErrorDialog +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Continue?") + +yes := dialog.AddButton("Yes") +yes.OnClick(func() { + // Handle yes +}) + +no := dialog.AddButton("No") +no.OnClick(func() { + // Handle no +}) + +dialog.SetDefaultButton(yes) +dialog.SetCancelButton(no) +dialog.Show() ``` -### WarningDialog() - -Creates a warning dialog. +### File Dialogs ```go -func (a *App) WarningDialog() WarningDialog -``` - -### OpenFileDialog() - -Creates a file open dialog. - -```go -func (a *App) OpenFileDialog() OpenFileDialog -``` - -**Example:** - -```go -path, err := app.OpenFileDialog(). +// Open file dialog +path, err := app.Dialog.OpenFile(). SetTitle("Select File"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForSingleSelection() -``` -### SaveFileDialog() +// Save file dialog +path, err := app.Dialog.SaveFile(). + SetTitle("Save File"). + SetFilename("document.pdf"). + AddFilter("PDF", "*.pdf"). + PromptForSingleSelection() -Creates a file save dialog. - -```go -func (a *App) SaveFileDialog() SaveFileDialog -``` - -### SelectFolderDialog() - -Creates a folder selection dialog. - -```go -func (a *App) SelectFolderDialog() SelectFolderDialog +// Folder selection (use OpenFile with directory options) +path, err := app.Dialog.OpenFile(). + SetTitle("Select Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() ``` ## Logger diff --git a/docs/src/content/docs/reference/dialogs.mdx b/docs/src/content/docs/reference/dialogs.mdx index 90766b390..73c29edf6 100644 --- a/docs/src/content/docs/reference/dialogs.mdx +++ b/docs/src/content/docs/reference/dialogs.mdx @@ -9,37 +9,50 @@ import { Card, CardGrid } from "@astrojs/starlight/components"; ## Overview -The Dialogs API provides methods to show native file dialogs and message dialogs. +The Dialogs API provides methods to show native file dialogs and message dialogs. Access dialogs through the `app.Dialog` manager. **Dialog Types:** -- **File Dialogs** - Open, Save, and Select Folder dialogs +- **File Dialogs** - Open and Save dialogs - **Message Dialogs** - Info, Error, Warning, and Question dialogs All dialogs are **native OS dialogs** that match the platform's look and feel. +## Accessing Dialogs + +Dialogs are accessed through the `app.Dialog` manager: + +```go +app.Dialog.OpenFile() +app.Dialog.SaveFile() +app.Dialog.Info() +app.Dialog.Question() +app.Dialog.Warning() +app.Dialog.Error() +``` + ## File Dialogs -### OpenFileDialog() +### OpenFile() Creates a file open dialog. ```go -func (a *App) OpenFileDialog() *OpenFileDialog +func (dm *DialogManager) OpenFile() *OpenFileDialogStruct ``` **Example:** ```go -dialog := app.OpenFileDialog() +dialog := app.Dialog.OpenFile() ``` -### OpenFileDialog Methods +### OpenFileDialogStruct Methods #### SetTitle() Sets the dialog title. ```go -func (d *OpenFileDialog) SetTitle(title string) *OpenFileDialog +func (d *OpenFileDialogStruct) SetTitle(title string) *OpenFileDialogStruct ``` **Example:** @@ -47,43 +60,87 @@ func (d *OpenFileDialog) SetTitle(title string) *OpenFileDialog dialog.SetTitle("Select Image") ``` -#### SetFilters() +#### AddFilter() -Sets file type filters. +Adds a file type filter. ```go -func (d *OpenFileDialog) SetFilters(filters []FileFilter) *OpenFileDialog +func (d *OpenFileDialogStruct) AddFilter(displayName, pattern string) *OpenFileDialogStruct ``` -**FileFilter structure:** -```go -type FileFilter struct { - DisplayName string // "Images", "Documents", etc. - Pattern string // "*.png;*.jpg", "*.pdf;*.doc", etc. -} -``` +**Parameters:** +- `displayName` - Filter description shown to user (e.g., "Images", "Documents") +- `pattern` - Semicolon-separated list of extensions (e.g., "*.png;*.jpg") **Example:** ```go -dialog.SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.gif"}, - {DisplayName: "Documents", Pattern: "*.pdf;*.docx"}, - {DisplayName: "All Files", Pattern: "*.*"}, -}) +dialog.AddFilter("Images", "*.png;*.jpg;*.gif"). + AddFilter("Documents", "*.pdf;*.docx"). + AddFilter("All Files", "*.*") ``` -#### SetDefaultDirectory() +#### SetDirectory() Sets the initial directory. ```go -func (d *OpenFileDialog) SetDefaultDirectory(path string) *OpenFileDialog +func (d *OpenFileDialogStruct) SetDirectory(directory string) *OpenFileDialogStruct ``` **Example:** ```go homeDir, _ := os.UserHomeDir() -dialog.SetDefaultDirectory(homeDir) +dialog.SetDirectory(homeDir) +``` + +#### CanChooseDirectories() + +Enables or disables directory selection. + +```go +func (d *OpenFileDialogStruct) CanChooseDirectories(canChooseDirectories bool) *OpenFileDialogStruct +``` + +**Example (folder selection):** +```go +// Select folders instead of files +path, err := app.Dialog.OpenFile(). + SetTitle("Select Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). + PromptForSingleSelection() +``` + +#### CanChooseFiles() + +Enables or disables file selection. + +```go +func (d *OpenFileDialogStruct) CanChooseFiles(canChooseFiles bool) *OpenFileDialogStruct +``` + +#### CanCreateDirectories() + +Enables or disables creating new directories. + +```go +func (d *OpenFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *OpenFileDialogStruct +``` + +#### ShowHiddenFiles() + +Shows or hides hidden files. + +```go +func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *OpenFileDialogStruct) AttachToWindow(window Window) *OpenFileDialogStruct ``` #### PromptForSingleSelection() @@ -91,20 +148,18 @@ dialog.SetDefaultDirectory(homeDir) Shows the dialog and returns the selected file. ```go -func (d *OpenFileDialog) PromptForSingleSelection() (string, error) +func (d *OpenFileDialogStruct) PromptForSingleSelection() (string, error) ``` **Returns:** -- `string` - Selected file path -- `error` - Error if cancelled or failed +- `string` - Selected file path (empty if cancelled) +- `error` - Error if dialog failed **Example:** ```go -path, err := app.OpenFileDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.gif"}, - }). + AddFilter("Images", "*.png;*.jpg;*.gif"). PromptForSingleSelection() if err != nil { @@ -121,20 +176,18 @@ processFile(path) Shows the dialog and returns multiple selected files. ```go -func (d *OpenFileDialog) PromptForMultipleSelection() ([]string, error) +func (d *OpenFileDialogStruct) PromptForMultipleSelection() ([]string, error) ``` **Returns:** - `[]string` - Array of selected file paths -- `error` - Error if cancelled or failed +- `error` - Error if dialog failed **Example:** ```go -paths, err := app.OpenFileDialog(). +paths, err := app.Dialog.OpenFile(). SetTitle("Select Images"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForMultipleSelection() if err != nil { @@ -146,56 +199,70 @@ for _, path := range paths { } ``` -### SaveFileDialog() +### SaveFile() Creates a file save dialog. ```go -func (a *App) SaveFileDialog() *SaveFileDialog +func (dm *DialogManager) SaveFile() *SaveFileDialogStruct ``` **Example:** ```go -dialog := app.SaveFileDialog() +dialog := app.Dialog.SaveFile() ``` -### SaveFileDialog Methods +### SaveFileDialogStruct Methods #### SetTitle() Sets the dialog title. ```go -func (d *SaveFileDialog) SetTitle(title string) *SaveFileDialog +func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct ``` -#### SetDefaultFilename() +#### SetFilename() Sets the default filename. ```go -func (d *SaveFileDialog) SetDefaultFilename(filename string) *SaveFileDialog +func (d *SaveFileDialogStruct) SetFilename(filename string) *SaveFileDialogStruct ``` **Example:** ```go -dialog.SetDefaultFilename("document.pdf") +dialog.SetFilename("document.pdf") ``` -#### SetFilters() +#### AddFilter() -Sets file type filters (same as OpenFileDialog). +Adds a file type filter. ```go -func (d *SaveFileDialog) SetFilters(filters []FileFilter) *SaveFileDialog +func (d *SaveFileDialogStruct) AddFilter(displayName, pattern string) *SaveFileDialogStruct ``` -#### SetDefaultDirectory() +**Example:** +```go +dialog.AddFilter("PDF Document", "*.pdf"). + AddFilter("Text Document", "*.txt") +``` -Sets the initial directory (same as OpenFileDialog). +#### SetDirectory() + +Sets the initial directory. ```go -func (d *SaveFileDialog) SetDefaultDirectory(path string) *SaveFileDialog +func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct ``` #### PromptForSingleSelection() @@ -203,17 +270,15 @@ func (d *SaveFileDialog) SetDefaultDirectory(path string) *SaveFileDialog Shows the dialog and returns the save path. ```go -func (d *SaveFileDialog) PromptForSingleSelection() (string, error) +func (d *SaveFileDialogStruct) PromptForSingleSelection() (string, error) ``` **Example:** ```go -path, err := app.SaveFileDialog(). +path, err := app.Dialog.SaveFile(). SetTitle("Save Document"). - SetDefaultFilename("untitled.pdf"). - SetFilters([]application.FileFilter{ - {DisplayName: "PDF Document", Pattern: "*.pdf"}, - }). + SetFilename("untitled.pdf"). + AddFilter("PDF Document", "*.pdf"). PromptForSingleSelection() if err != nil { @@ -225,49 +290,15 @@ if err != nil { saveDocument(path) ``` -### SelectFolderDialog() +### Folder Selection -Creates a folder selection dialog. +There is no separate `SelectFolderDialog`. Use `OpenFile()` with directory options: ```go -func (a *App) SelectFolderDialog() *SelectFolderDialog -``` - -**Example:** -```go -dialog := app.SelectFolderDialog() -``` - -### SelectFolderDialog Methods - -#### SetTitle() - -Sets the dialog title. - -```go -func (d *SelectFolderDialog) SetTitle(title string) *SelectFolderDialog -``` - -#### SetDefaultDirectory() - -Sets the initial directory. - -```go -func (d *SelectFolderDialog) SetDefaultDirectory(path string) *SelectFolderDialog -``` - -#### PromptForSingleSelection() - -Shows the dialog and returns the selected folder. - -```go -func (d *SelectFolderDialog) PromptForSingleSelection() (string, error) -``` - -**Example:** -```go -path, err := app.SelectFolderDialog(). +path, err := app.Dialog.OpenFile(). SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() if err != nil { @@ -281,105 +312,98 @@ outputDir = path ## Message Dialogs -### InfoDialog() +All message dialogs return `*MessageDialog` and share the same methods. + +### Info() Creates an information dialog. ```go -func (a *App) InfoDialog() *InfoDialog +func (dm *DialogManager) Info() *MessageDialog ``` **Example:** ```go -dialog := app.InfoDialog() -``` - -### InfoDialog Methods - -#### SetTitle() - -Sets the dialog title. - -```go -func (d *InfoDialog) SetTitle(title string) *InfoDialog -``` - -#### SetMessage() - -Sets the dialog message. - -```go -func (d *InfoDialog) SetMessage(message string) *InfoDialog -``` - -#### Show() - -Shows the dialog. - -```go -func (d *InfoDialog) Show() -``` - -**Example:** -```go -app.InfoDialog(). +app.Dialog.Info(). SetTitle("Success"). SetMessage("File saved successfully!"). Show() ``` -### ErrorDialog() +### Error() Creates an error dialog. ```go -func (a *App) ErrorDialog() *ErrorDialog +func (dm *DialogManager) Error() *MessageDialog ``` -**Methods:** Same as InfoDialog (SetTitle, SetMessage, Show) - **Example:** ```go -app.ErrorDialog(). +app.Dialog.Error(). SetTitle("Error"). SetMessage("Failed to save file: " + err.Error()). Show() ``` -### WarningDialog() +### Warning() Creates a warning dialog. ```go -func (a *App) WarningDialog() *WarningDialog +func (dm *DialogManager) Warning() *MessageDialog ``` -**Methods:** Same as InfoDialog (SetTitle, SetMessage, Show) - **Example:** ```go -app.WarningDialog(). +app.Dialog.Warning(). SetTitle("Warning"). SetMessage("This action cannot be undone."). Show() ``` -### QuestionDialog() +### Question() Creates a question dialog with custom buttons. ```go -func (a *App) QuestionDialog() *QuestionDialog +func (dm *DialogManager) Question() *MessageDialog ``` -### QuestionDialog Methods +**Example:** +```go +dialog := app.Dialog.Question(). + SetTitle("Confirm"). + SetMessage("Do you want to save changes?") + +save := dialog.AddButton("Save") +save.OnClick(func() { + saveDocument() +}) + +dontSave := dialog.AddButton("Don't Save") +dontSave.OnClick(func() { + // Continue without saving +}) + +cancel := dialog.AddButton("Cancel") +cancel.OnClick(func() { + // Do nothing +}) + +dialog.SetDefaultButton(save) +dialog.SetCancelButton(cancel) +dialog.Show() +``` + +### MessageDialog Methods #### SetTitle() Sets the dialog title. ```go -func (d *QuestionDialog) SetTitle(title string) *QuestionDialog +func (d *MessageDialog) SetTitle(title string) *MessageDialog ``` #### SetMessage() @@ -387,24 +411,33 @@ func (d *QuestionDialog) SetTitle(title string) *QuestionDialog Sets the dialog message. ```go -func (d *QuestionDialog) SetMessage(message string) *QuestionDialog +func (d *MessageDialog) SetMessage(message string) *MessageDialog ``` -#### SetButtons() +#### SetIcon() -Sets the button labels. +Sets a custom icon for the dialog. ```go -func (d *QuestionDialog) SetButtons(buttons ...string) *QuestionDialog +func (d *MessageDialog) SetIcon(icon []byte) *MessageDialog ``` -**Parameters:** -- `buttons` - Variable number of button labels +#### AddButton() + +Adds a button to the dialog and returns the button for configuration. + +```go +func (d *MessageDialog) AddButton(label string) *Button +``` + +**Returns:** `*Button` - The button instance for further configuration **Example:** ```go -dialog.SetButtons("Yes", "No") -dialog.SetButtons("Save", "Don't Save", "Cancel") +button := dialog.AddButton("OK") +button.OnClick(func() { + // Handle click +}) ``` #### SetDefaultButton() @@ -412,13 +445,14 @@ dialog.SetButtons("Save", "Don't Save", "Cancel") Sets which button is the default (activated by pressing Enter). ```go -func (d *QuestionDialog) SetDefaultButton(index int) *QuestionDialog +func (d *MessageDialog) SetDefaultButton(button *Button) *MessageDialog ``` **Example:** ```go -// Make "Yes" the default button -dialog.SetButtons("Yes", "No").SetDefaultButton(0) +yes := dialog.AddButton("Yes") +no := dialog.AddButton("No") +dialog.SetDefaultButton(yes) ``` #### SetCancelButton() @@ -426,49 +460,58 @@ dialog.SetButtons("Yes", "No").SetDefaultButton(0) Sets which button is the cancel button (activated by pressing Escape). ```go -func (d *QuestionDialog) SetCancelButton(index int) *QuestionDialog +func (d *MessageDialog) SetCancelButton(button *Button) *MessageDialog ``` **Example:** ```go -// Make "Cancel" the cancel button -dialog.SetButtons("Save", "Don't Save", "Cancel").SetCancelButton(2) +ok := dialog.AddButton("OK") +cancel := dialog.AddButton("Cancel") +dialog.SetCancelButton(cancel) +``` + +#### AttachToWindow() + +Attaches the dialog to a specific window. + +```go +func (d *MessageDialog) AttachToWindow(window Window) *MessageDialog ``` #### Show() -Shows the dialog and returns the selected button. +Shows the dialog. Button callbacks handle user responses. ```go -func (d *QuestionDialog) Show() (string, error) +func (d *MessageDialog) Show() ``` -**Returns:** -- `string` - The label of the button that was clicked -- `error` - Error if dialog failed to show +**Note:** `Show()` does not return a value. Use button callbacks to handle user responses. + +### Button Methods + +#### OnClick() + +Sets the callback function for when the button is clicked. -**Example:** ```go -result, err := app.QuestionDialog(). - SetTitle("Confirm"). - SetMessage("Do you want to save changes?"). - SetButtons("Save", "Don't Save", "Cancel"). - SetDefaultButton(0). - SetCancelButton(2). - Show() +func (b *Button) OnClick(callback func()) *Button +``` -if err != nil { - return -} +#### SetAsDefault() -switch result { -case "Save": - saveDocument() -case "Don't Save": - // Continue without saving -case "Cancel": - return -} +Marks this button as the default button. + +```go +func (b *Button) SetAsDefault() *Button +``` + +#### SetAsCancel() + +Marks this button as the cancel button. + +```go +func (b *Button) SetAsCancel() *Button ``` ## Complete Examples @@ -477,16 +520,14 @@ case "Cancel": ```go type FileService struct { - app *application.Application + app *application.App } func (s *FileService) OpenImage() (string, error) { - path, err := s.app.OpenFileDialog(). + path, err := s.app.Dialog.OpenFile(). SetTitle("Select Image"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg;*.jpeg;*.gif"}, - {DisplayName: "All Files", Pattern: "*.*"}, - }). + AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif"). + AddFilter("All Files", "*.*"). PromptForSingleSelection() if err != nil { @@ -497,13 +538,11 @@ func (s *FileService) OpenImage() (string, error) { } func (s *FileService) SaveDocument(defaultName string) (string, error) { - path, err := s.app.SaveFileDialog(). + path, err := s.app.Dialog.SaveFile(). SetTitle("Save Document"). - SetDefaultFilename(defaultName). - SetFilters([]application.FileFilter{ - {DisplayName: "PDF Document", Pattern: "*.pdf"}, - {DisplayName: "Text Document", Pattern: "*.txt"}, - }). + SetFilename(defaultName). + AddFilter("PDF Document", "*.pdf"). + AddFilter("Text Document", "*.txt"). PromptForSingleSelection() if err != nil { @@ -514,8 +553,10 @@ func (s *FileService) SaveDocument(defaultName string) (string, error) { } func (s *FileService) SelectOutputFolder() (string, error) { - path, err := s.app.SelectFolderDialog(). + path, err := s.app.Dialog.OpenFile(). SetTitle("Select Output Folder"). + CanChooseDirectories(true). + CanChooseFiles(false). PromptForSingleSelection() if err != nil { @@ -529,64 +570,61 @@ func (s *FileService) SelectOutputFolder() (string, error) { ### Confirmation Dialog Example ```go -func (s *Service) DeleteItem(id string) error { - // Confirm before deleting - result, err := s.app.QuestionDialog(). +func (s *Service) DeleteItem(app *application.App, id string) { + dialog := app.Dialog.Question(). SetTitle("Confirm Delete"). - SetMessage("Are you sure you want to delete this item?"). - SetButtons("Delete", "Cancel"). - SetDefaultButton(1). // Default to Cancel - SetCancelButton(1). - Show() + SetMessage("Are you sure you want to delete this item?") - if err != nil || result == "Cancel" { - return nil // User cancelled - } + deleteBtn := dialog.AddButton("Delete") + deleteBtn.OnClick(func() { + deleteFromDatabase(id) + }) - // Perform deletion - return deleteFromDatabase(id) + cancelBtn := dialog.AddButton("Cancel") + // Cancel does nothing + + dialog.SetDefaultButton(cancelBtn) // Default to Cancel for safety + dialog.SetCancelButton(cancelBtn) + dialog.Show() } ``` ### Save Changes Dialog ```go -func (s *Editor) PromptSaveChanges() (bool, error) { - result, err := s.app.QuestionDialog(). +func (s *Editor) PromptSaveChanges(app *application.App) { + dialog := app.Dialog.Question(). SetTitle("Unsaved Changes"). - SetMessage("Do you want to save your changes before closing?"). - SetButtons("Save", "Don't Save", "Cancel"). - SetDefaultButton(0). - SetCancelButton(2). - Show() + SetMessage("Do you want to save your changes before closing?") - if err != nil { - return false, err - } + save := dialog.AddButton("Save") + save.OnClick(func() { + s.Save() + s.Close() + }) - switch result { - case "Save": - return s.Save() - case "Don't Save": - return true, nil - case "Cancel": - return false, nil - } + dontSave := dialog.AddButton("Don't Save") + dontSave.OnClick(func() { + s.Close() + }) - return false, nil + cancel := dialog.AddButton("Cancel") + // Cancel does nothing, dialog closes + + dialog.SetDefaultButton(save) + dialog.SetCancelButton(cancel) + dialog.Show() } ``` ### Multi-File Processing ```go -func (s *Service) ProcessMultipleFiles() error { +func (s *Service) ProcessMultipleFiles(app *application.App) error { // Select multiple files - paths, err := s.app.OpenFileDialog(). + paths, err := app.Dialog.OpenFile(). SetTitle("Select Files to Process"). - SetFilters([]application.FileFilter{ - {DisplayName: "Images", Pattern: "*.png;*.jpg"}, - }). + AddFilter("Images", "*.png;*.jpg"). PromptForMultipleSelection() if err != nil { @@ -594,7 +632,7 @@ func (s *Service) ProcessMultipleFiles() error { } if len(paths) == 0 { - s.app.InfoDialog(). + app.Dialog.Info(). SetTitle("No Files Selected"). SetMessage("Please select at least one file."). Show() @@ -602,25 +640,19 @@ func (s *Service) ProcessMultipleFiles() error { } // Process files - for i, path := range paths { + for _, path := range paths { err := processFile(path) if err != nil { - s.app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Processing Error"). SetMessage(fmt.Sprintf("Failed to process %s: %v", path, err)). Show() continue } - - // Show progress - s.app.EmitEvent("progress", map[string]interface{}{ - "current": i + 1, - "total": len(paths), - }) } // Show completion - s.app.InfoDialog(). + app.Dialog.Info(). SetTitle("Complete"). SetMessage(fmt.Sprintf("Successfully processed %d files", len(paths))). Show() @@ -632,14 +664,12 @@ func (s *Service) ProcessMultipleFiles() error { ### Error Handling with Dialogs ```go -func (s *Service) SaveFile(data []byte) error { +func (s *Service) SaveFile(app *application.App, data []byte) error { // Select save location - path, err := s.app.SaveFileDialog(). + path, err := app.Dialog.SaveFile(). SetTitle("Save File"). - SetDefaultFilename("data.json"). - SetFilters([]application.FileFilter{ - {DisplayName: "JSON File", Pattern: "*.json"}, - }). + SetFilename("data.json"). + AddFilter("JSON File", "*.json"). PromptForSingleSelection() if err != nil { @@ -651,7 +681,7 @@ func (s *Service) SaveFile(data []byte) error { err = os.WriteFile(path, data, 0644) if err != nil { // Show error dialog - s.app.ErrorDialog(). + app.Dialog.Error(). SetTitle("Save Failed"). SetMessage(fmt.Sprintf("Could not save file: %v", err)). Show() @@ -659,7 +689,7 @@ func (s *Service) SaveFile(data []byte) error { } // Show success - s.app.InfoDialog(). + app.Dialog.Info(). SetTitle("Success"). SetMessage("File saved successfully!"). Show() @@ -692,33 +722,32 @@ func (s *Service) GetDefaultDirectory() string { } } -func (s *Service) OpenWithDefaults() (string, error) { - return s.app.OpenFileDialog(). +func (s *Service) OpenWithDefaults(app *application.App) (string, error) { + return app.Dialog.OpenFile(). SetTitle("Open File"). - SetDefaultDirectory(s.GetDefaultDirectory()). - SetFilters([]application.FileFilter{ - {DisplayName: "All Files", Pattern: "*.*"}, - }). + SetDirectory(s.GetDefaultDirectory()). + AddFilter("All Files", "*.*"). PromptForSingleSelection() } ``` ## Best Practices -### ✅ Do +### Do - **Use native dialogs** - They match the platform's look and feel - **Provide clear titles** - Help users understand the purpose - **Set appropriate filters** - Guide users to correct file types - **Handle cancellation** - Check for errors (user may cancel) -- **Show confirmation for destructive actions** - Use QuestionDialog -- **Provide feedback** - Use InfoDialog for success messages +- **Show confirmation for destructive actions** - Use Question dialogs +- **Provide feedback** - Use Info dialogs for success messages - **Set sensible defaults** - Default directory, filename, etc. +- **Use callbacks for button actions** - Handle user responses properly -### ❌ Don't +### Don't - **Don't ignore errors** - User cancellation returns an error -- **Don't use ambiguous button labels** - "OK" vs "Save"/"Cancel" +- **Don't use ambiguous button labels** - Be specific: "Save"/"Cancel" - **Don't overuse dialogs** - They interrupt workflow - **Don't show errors for cancellation** - It's a normal action - **Don't forget file filters** - Help users find the right files @@ -749,14 +778,14 @@ func (s *Service) OpenWithDefaults() (string, error) { ### "Save As" Pattern ```go -func (s *Service) SaveAs(currentPath string) (string, error) { +func (s *Service) SaveAs(app *application.App, currentPath string) (string, error) { // Extract filename from current path filename := filepath.Base(currentPath) // Show save dialog - path, err := s.app.SaveFileDialog(). + path, err := app.Dialog.SaveFile(). SetTitle("Save As"). - SetDefaultFilename(filename). + SetFilename(filename). PromptForSingleSelection() if err != nil { @@ -770,18 +799,21 @@ func (s *Service) SaveAs(currentPath string) (string, error) { ### "Open Recent" Pattern ```go -func (s *Service) OpenRecent(recentPath string) error { +func (s *Service) OpenRecent(app *application.App, recentPath string) error { // Check if file still exists if _, err := os.Stat(recentPath); os.IsNotExist(err) { - result, _ := s.app.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("File Not Found"). - SetMessage("The file no longer exists. Remove from recent files?"). - SetButtons("Remove", "Cancel"). - Show() + SetMessage("The file no longer exists. Remove from recent files?") - if result == "Remove" { + remove := dialog.AddButton("Remove") + remove.OnClick(func() { s.removeFromRecent(recentPath) - } + }) + + cancel := dialog.AddButton("Cancel") + dialog.SetCancelButton(cancel) + dialog.Show() return err } diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 3630e3c9f..942bd4dc5 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -21,6 +21,8 @@ After processing, the content will be moved to the main changelog and this file ## Changed +- **BREAKING**: Remove package-level dialog functions (`application.InfoDialog()`, `application.QuestionDialog()`, etc.). Use the `app.Dialog` manager instead: `app.Dialog.Info()`, `app.Dialog.Question()`, `app.Dialog.Warning()`, `app.Dialog.Error()`, `app.Dialog.OpenFile()`, `app.Dialog.SaveFile()` +- Update dialogs documentation to match actual API: use `app.Dialog.*`, `AddButton()` with callbacks (not `SetButtons()`), `SetDefaultButton(*Button)` (not string), `AddFilter()` (not `SetFilters()`), `SetFilename()` (not `SetDefaultFilename()`), and `app.Dialog.OpenFile().CanChooseDirectories(true)` for folder selection ## Fixed diff --git a/v3/examples/build/main.go b/v3/examples/build/main.go index 3098527b7..752ad3395 100755 --- a/v3/examples/build/main.go +++ b/v3/examples/build/main.go @@ -143,7 +143,7 @@ func main() { sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { width, height := w.Size() - application.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() }) }) @@ -174,7 +174,7 @@ func main() { positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { x, y := w.RelativePosition() - application.InfoDialog().SetTitle("Current WebviewWindow Relative Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Relative Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() }) }) @@ -241,24 +241,24 @@ func main() { stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { screen := app.Screen.GetPrimary() msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + app.Dialog.Info().SetTitle("Primary Screen").SetMessage(msg).Show() }) stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { screens := app.Screen.GetAll() for _, screen := range screens { msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() } }) stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { screen, err := w.GetScreen() if err != nil { - application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + app.Dialog.Error().SetTitle("Error").SetMessage(err.Error()).Show() return } msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() }) }) app.Window.New() diff --git a/v3/examples/clipboard/main.go b/v3/examples/clipboard/main.go index efeed80de..0c97693aa 100644 --- a/v3/examples/clipboard/main.go +++ b/v3/examples/clipboard/main.go @@ -30,28 +30,28 @@ func main() { setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) { success := app.Clipboard.SetText("Hello") if !success { - application.InfoDialog().SetMessage("Failed to set clipboard text").Show() + app.Dialog.Info().SetMessage("Failed to set clipboard text").Show() } }) setClipboardMenu.Add("Set Text 'World'").OnClick(func(ctx *application.Context) { success := app.Clipboard.SetText("World") if !success { - application.InfoDialog().SetMessage("Failed to set clipboard text").Show() + app.Dialog.Info().SetMessage("Failed to set clipboard text").Show() } }) setClipboardMenu.Add("Set Text (current time)").OnClick(func(ctx *application.Context) { success := app.Clipboard.SetText(time.Now().String()) if !success { - application.InfoDialog().SetMessage("Failed to set clipboard text").Show() + app.Dialog.Info().SetMessage("Failed to set clipboard text").Show() } }) getClipboardMenu := menu.AddSubmenu("Get Clipboard") getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) { result, ok := app.Clipboard.Text() if !ok { - application.InfoDialog().SetMessage("Failed to get clipboard text").Show() + app.Dialog.Info().SetMessage("Failed to get clipboard text").Show() } else { - application.InfoDialog().SetMessage("Got:\n\n" + result).Show() + app.Dialog.Info().SetMessage("Got:\n\n" + result).Show() } }) @@ -59,9 +59,9 @@ func main() { clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) { success := app.Clipboard.SetText("") if success { - application.InfoDialog().SetMessage("Clipboard text cleared").Show() + app.Dialog.Info().SetMessage("Clipboard text cleared").Show() } else { - application.InfoDialog().SetMessage("Clipboard text not cleared").Show() + app.Dialog.Info().SetMessage("Clipboard text not cleared").Show() } }) diff --git a/v3/examples/dialogs-basic/main.go b/v3/examples/dialogs-basic/main.go index 8df5a1c37..0b47b34b3 100644 --- a/v3/examples/dialogs-basic/main.go +++ b/v3/examples/dialogs-basic/main.go @@ -44,7 +44,7 @@ func main() { // Test 1: Basic file open with no filters (no window) testMenu.Add("1. Basic Open (No Window)").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). PromptForSingleSelection() showResult("Basic Open", result, err, nil) @@ -52,7 +52,7 @@ func main() { // Test 1b: Basic file open with window testMenu.Add("1b. Basic Open (With Window)").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). AttachToWindow(mainWindow). PromptForSingleSelection() @@ -61,7 +61,7 @@ func main() { // Test 2: Open with single extension filter testMenu.Add("2. Single Filter").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). AddFilter("Text Files", "*.txt"). AttachToWindow(mainWindow). @@ -71,7 +71,7 @@ func main() { // Test 3: Open with multiple extension filter testMenu.Add("3. Multiple Filter").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). AddFilter("Documents", "*.txt;*.md;*.doc;*.docx"). AttachToWindow(mainWindow). @@ -81,7 +81,7 @@ func main() { // Test 4: Multiple file selection testMenu.Add("4. Multiple Selection").OnClick(func(ctx *application.Context) { - results, err := application.OpenFileDialog(). + results, err := app.Dialog.OpenFile(). CanChooseFiles(true). AddFilter("Images", "*.png;*.jpg;*.jpeg"). AttachToWindow(mainWindow). @@ -95,7 +95,7 @@ func main() { // Test 5: Directory selection testMenu.Add("5. Directory Selection").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseDirectories(true). CanChooseFiles(false). AttachToWindow(mainWindow). @@ -105,7 +105,7 @@ func main() { // Test 6: Save dialog with extension testMenu.Add("6. Save Dialog").OnClick(func(ctx *application.Context) { - result, err := application.SaveFileDialog(). + result, err := app.Dialog.SaveFile(). SetFilename("test.txt"). AddFilter("Text Files", "*.txt"). AttachToWindow(mainWindow). @@ -115,7 +115,7 @@ func main() { // Test 7: Complex filters testMenu.Add("7. Complex Filters").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). AddFilter("All Documents", "*.txt;*.md;*.doc;*.docx;*.pdf"). AddFilter("Text Files", "*.txt"). @@ -129,7 +129,7 @@ func main() { // Test 8: Hidden files testMenu.Add("8. Show Hidden").OnClick(func(ctx *application.Context) { - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). ShowHiddenFiles(true). AttachToWindow(mainWindow). @@ -140,7 +140,7 @@ func main() { // Test 9: Default directory testMenu.Add("9. Default Directory").OnClick(func(ctx *application.Context) { home, _ := os.UserHomeDir() - result, err := application.OpenFileDialog(). + result, err := app.Dialog.OpenFile(). CanChooseFiles(true). SetDirectory(home). AttachToWindow(mainWindow). @@ -151,7 +151,7 @@ func main() { // Test 10: Full featured dialog testMenu.Add("10. Full Featured").OnClick(func(ctx *application.Context) { home, _ := os.UserHomeDir() - dialog := application.OpenFileDialog(). + dialog := app.Dialog.OpenFile(). SetTitle("Full Featured Dialog"). SetDirectory(home). CanChooseFiles(true). @@ -192,7 +192,7 @@ func showResult(test string, result string, err error, window *application.Webvi return } if result == "" { - dialog := application.InfoDialog(). + dialog := application.Get().Dialog.Info(). SetTitle(test). SetMessage("No file selected") if window != nil { @@ -201,7 +201,7 @@ func showResult(test string, result string, err error, window *application.Webvi dialog.Show() return } - dialog := application.InfoDialog(). + dialog := application.Get().Dialog.Info(). SetTitle(test). SetMessage(fmt.Sprintf("Selected: %s\nType: %s", result, getFileType(result))) if window != nil { @@ -212,7 +212,7 @@ func showResult(test string, result string, err error, window *application.Webvi func showResults(test string, results []string, window *application.WebviewWindow) { if len(results) == 0 { - dialog := application.InfoDialog(). + dialog := application.Get().Dialog.Info(). SetTitle(test). SetMessage("No files selected") if window != nil { @@ -226,7 +226,7 @@ func showResults(test string, results []string, window *application.WebviewWindo for _, result := range results { message.WriteString(fmt.Sprintf("%s (%s)\n", result, getFileType(result))) } - dialog := application.InfoDialog(). + dialog := application.Get().Dialog.Info(). SetTitle(test). SetMessage(message.String()) if window != nil { @@ -236,7 +236,7 @@ func showResults(test string, results []string, window *application.WebviewWindo } func showError(test string, err error, window *application.WebviewWindow) { - dialog := application.ErrorDialog(). + dialog := application.Get().Dialog.Error(). SetTitle(test). SetMessage(fmt.Sprintf("Error: %v", err)) if window != nil { @@ -249,11 +249,8 @@ func getFileType(path string) string { if path == "" { return "unknown" } - ext := strings.ToLower(filepath.Ext(path)) + ext := filepath.Ext(path) if ext == "" { - if fi, err := os.Stat(path); err == nil && fi.IsDir() { - return "directory" - } return "no extension" } return ext diff --git a/v3/examples/dialogs/main.go b/v3/examples/dialogs/main.go index 07521b30d..e6e09661c 100644 --- a/v3/examples/dialogs/main.go +++ b/v3/examples/dialogs/main.go @@ -36,24 +36,24 @@ func main() { // Let's make a "Demo" menu infoMenu := menu.AddSubmenu("Info") infoMenu.Add("Info").OnClick(func(ctx *application.Context) { - dialog := application.InfoDialog() + dialog := app.Dialog.Info() dialog.SetTitle("Custom Title") dialog.SetMessage("This is a custom message") dialog.Show() }) infoMenu.Add("Info (Title only)").OnClick(func(ctx *application.Context) { - dialog := application.InfoDialog() + dialog := app.Dialog.Info() dialog.SetTitle("Custom Title") dialog.Show() }) infoMenu.Add("Info (Message only)").OnClick(func(ctx *application.Context) { - dialog := application.InfoDialog() + dialog := app.Dialog.Info() dialog.SetMessage("This is a custom message") dialog.Show() }) infoMenu.Add("Info (Custom Icon)").OnClick(func(ctx *application.Context) { - dialog := application.InfoDialog() + dialog := app.Dialog.Info() dialog.SetTitle("Custom Icon Example") dialog.SetMessage("Using a custom icon") dialog.SetIcon(icons.ApplicationDarkMode256) @@ -65,14 +65,14 @@ func main() { questionMenu := menu.AddSubmenu("Question") questionMenu.Add("Question (No default)").OnClick(func(ctx *application.Context) { - dialog := application.QuestionDialog() + dialog := app.Dialog.Question() dialog.SetMessage("No default button") dialog.AddButton("Yes") dialog.AddButton("No") dialog.Show() }) questionMenu.Add("Question (Attached to Window)").OnClick(func(ctx *application.Context) { - dialog := application.QuestionDialog() + dialog := app.Dialog.Question() dialog.AttachToWindow(app.Window.Current()) dialog.SetMessage("No default button") dialog.AddButton("Yes") @@ -80,7 +80,7 @@ func main() { dialog.Show() }) questionMenu.Add("Question (With Default)").OnClick(func(ctx *application.Context) { - dialog := application.QuestionDialog() + dialog := app.Dialog.Question() dialog.SetTitle("Quit") dialog.SetMessage("You have unsaved work. Are you sure you want to quit?") dialog.AddButton("Yes").OnClick(func() { @@ -91,12 +91,12 @@ func main() { dialog.Show() }) questionMenu.Add("Question (With Cancel)").OnClick(func(ctx *application.Context) { - dialog := application.QuestionDialog(). + dialog := app.Dialog.Question(). SetTitle("Update"). SetMessage("The cancel button is selected when pressing escape") download := dialog.AddButton("📥 Download") download.OnClick(func() { - application.InfoDialog().SetMessage("Downloading...").Show() + app.Dialog.Info().SetMessage("Downloading...").Show() }) no := dialog.AddButton("Cancel") dialog.SetDefaultButton(download) @@ -104,15 +104,15 @@ func main() { dialog.Show() }) questionMenu.Add("Question (Custom Icon)").OnClick(func(ctx *application.Context) { - dialog := application.QuestionDialog() + dialog := app.Dialog.Question() dialog.SetTitle("Custom Icon Example") dialog.SetMessage("Using a custom icon") dialog.SetIcon(icons.WailsLogoWhiteTransparent) likeIt := dialog.AddButton("I like it!").OnClick(func() { - application.InfoDialog().SetMessage("Thanks!").Show() + app.Dialog.Info().SetMessage("Thanks!").Show() }) dialog.AddButton("Not so keen...").OnClick(func() { - application.InfoDialog().SetMessage("Too bad!").Show() + app.Dialog.Info().SetMessage("Too bad!").Show() }) dialog.SetDefaultButton(likeIt) dialog.Show() @@ -120,23 +120,23 @@ func main() { warningMenu := menu.AddSubmenu("Warning") warningMenu.Add("Warning").OnClick(func(ctx *application.Context) { - application.WarningDialog(). + app.Dialog.Warning(). SetTitle("Custom Title"). SetMessage("This is a custom message"). Show() }) warningMenu.Add("Warning (Title only)").OnClick(func(ctx *application.Context) { - dialog := application.WarningDialog() + dialog := app.Dialog.Warning() dialog.SetTitle("Custom Title") dialog.Show() }) warningMenu.Add("Warning (Message only)").OnClick(func(ctx *application.Context) { - dialog := application.WarningDialog() + dialog := app.Dialog.Warning() dialog.SetMessage("This is a custom message") dialog.Show() }) warningMenu.Add("Warning (Custom Icon)").OnClick(func(ctx *application.Context) { - dialog := application.WarningDialog() + dialog := app.Dialog.Warning() dialog.SetTitle("Custom Icon Example") dialog.SetMessage("Using a custom icon") dialog.SetIcon(icons.ApplicationLightMode256) @@ -145,23 +145,23 @@ func main() { errorMenu := menu.AddSubmenu("Error") errorMenu.Add("Error").OnClick(func(ctx *application.Context) { - dialog := application.ErrorDialog() + dialog := app.Dialog.Error() dialog.SetTitle("Ooops") dialog.SetMessage("I accidentally the whole of Twitter") dialog.Show() }) errorMenu.Add("Error (Title Only)").OnClick(func(ctx *application.Context) { - dialog := application.ErrorDialog() + dialog := app.Dialog.Error() dialog.SetTitle("Custom Title") dialog.Show() }) errorMenu.Add("Error (Custom Message)").OnClick(func(ctx *application.Context) { - application.ErrorDialog(). + app.Dialog.Error(). SetMessage("This is a custom message"). Show() }) errorMenu.Add("Error (Custom Icon)").OnClick(func(ctx *application.Context) { - dialog := application.ErrorDialog() + dialog := app.Dialog.Error() dialog.SetTitle("Custom Icon Example") dialog.SetMessage("Using a custom icon") dialog.SetIcon(icons.WailsLogoWhite) @@ -170,90 +170,90 @@ func main() { openMenu := menu.AddSubmenu("Open") openMenu.Add("Open File").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseFiles(true). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No file selected").Show() + app.Dialog.Info().SetMessage("No file selected").Show() } }) openMenu.Add("Open File (Show Hidden Files)").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseFiles(true). CanCreateDirectories(true). ShowHiddenFiles(true). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No file selected").Show() + app.Dialog.Info().SetMessage("No file selected").Show() } }) openMenu.Add("Open File (Attach to window)").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseFiles(true). CanCreateDirectories(true). ShowHiddenFiles(true). AttachToWindow(app.Window.Current()). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No file selected").Show() + app.Dialog.Info().SetMessage("No file selected").Show() } }) openMenu.Add("Open Multiple Files (Show Hidden Files)").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseFiles(true). CanCreateDirectories(true). ShowHiddenFiles(true). PromptForMultipleSelection() if len(result) > 0 { - application.InfoDialog().SetMessage(strings.Join(result, ",")).Show() + app.Dialog.Info().SetMessage(strings.Join(result, ",")).Show() } else { - application.InfoDialog().SetMessage("No file selected").Show() + app.Dialog.Info().SetMessage("No file selected").Show() } }) openMenu.Add("Open Directory").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseDirectories(true). CanChooseFiles(false). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No directory selected").Show() + app.Dialog.Info().SetMessage("No directory selected").Show() } }) openMenu.Add("Open Directory (Create Directories)").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseDirectories(true). CanCreateDirectories(true). CanChooseFiles(false). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No directory selected").Show() + app.Dialog.Info().SetMessage("No directory selected").Show() } }) openMenu.Add("Open Directory (Resolves Aliases)").OnClick(func(ctx *application.Context) { - result, _ := application.OpenFileDialog(). + result, _ := app.Dialog.OpenFile(). CanChooseDirectories(true). CanCreateDirectories(true). CanChooseFiles(false). ResolvesAliases(true). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No directory selected").Show() + app.Dialog.Info().SetMessage("No directory selected").Show() } }) openMenu.Add("Open File/Directory (Set Title)").OnClick(func(ctx *application.Context) { - dialog := application.OpenFileDialog(). + dialog := app.Dialog.OpenFile(). CanChooseDirectories(true). CanCreateDirectories(true). ResolvesAliases(true) @@ -265,14 +265,14 @@ func main() { result, _ := dialog.PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No file/directory selected").Show() + app.Dialog.Info().SetMessage("No file/directory selected").Show() } }) openMenu.Add("Open (Full Example)").OnClick(func(ctx *application.Context) { cwd, _ := os.Getwd() - dialog := application.OpenFileDialog(). + dialog := app.Dialog.OpenFile(). SetTitle("Select a file"). SetMessage("Select a file to open"). SetButtonText("Let's do this!"). @@ -294,46 +294,46 @@ func main() { result, _ := dialog.PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } else { - application.InfoDialog().SetMessage("No file selected").Show() + app.Dialog.Info().SetMessage("No file selected").Show() } }) saveMenu := menu.AddSubmenu("Save") saveMenu.Add("Select File (Defaults)").OnClick(func(ctx *application.Context) { - result, _ := application.SaveFileDialog(). + result, _ := app.Dialog.SaveFile(). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } }) saveMenu.Add("Select File (Attach To WebviewWindow)").OnClick(func(ctx *application.Context) { - result, _ := application.SaveFileDialog(). + result, _ := app.Dialog.SaveFile(). AttachToWindow(app.Window.Current()). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } }) saveMenu.Add("Select File (Show Hidden Files)").OnClick(func(ctx *application.Context) { - result, _ := application.SaveFileDialog(). + result, _ := app.Dialog.SaveFile(). ShowHiddenFiles(true). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } }) saveMenu.Add("Select File (Cannot Create Directories)").OnClick(func(ctx *application.Context) { - result, _ := application.SaveFileDialog(). + result, _ := app.Dialog.SaveFile(). CanCreateDirectories(false). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } }) saveMenu.Add("Select File (Full Example)").OnClick(func(ctx *application.Context) { - result, _ := application.SaveFileDialog(). + result, _ := app.Dialog.SaveFile(). CanCreateDirectories(false). ShowHiddenFiles(true). SetMessage("Select a file"). @@ -346,7 +346,7 @@ func main() { ShowHiddenFiles(true). PromptForSingleSelection() if result != "" { - application.InfoDialog().SetMessage(result).Show() + app.Dialog.Info().SetMessage(result).Show() } }) diff --git a/v3/examples/events-bug/main.go b/v3/examples/events-bug/main.go index 0288243ae..f3bf88554 100644 --- a/v3/examples/events-bug/main.go +++ b/v3/examples/events-bug/main.go @@ -6,8 +6,10 @@ import ( "log" ) +var app *application.App + func main() { - app := application.New(application.Options{ + app = application.New(application.Options{ Name: "Key Bindings Demo", Description: "A demo of the Key Bindings Options", Mac: application.MacOptions{ @@ -15,7 +17,7 @@ func main() { }, KeyBindings: map[string]func(window application.Window){ "shift+ctrl+c": func(window application.Window) { - selection, err := application.OpenFileDialog(). + selection, err := app.Dialog.OpenFile(). CanChooseFiles(true). CanCreateDirectories(true). ShowHiddenFiles(true). diff --git a/v3/examples/file-association/main.go b/v3/examples/file-association/main.go index 9fb6fa058..a81506b52 100644 --- a/v3/examples/file-association/main.go +++ b/v3/examples/file-association/main.go @@ -65,7 +65,7 @@ func main() { }) window.OnWindowEvent(events.Common.WindowShow, func(event *application.WindowEvent) { - application.InfoDialog(). + app.Dialog.Info(). SetTitle("File Opened"). SetMessage("Application opened with file: " + filename). Show() diff --git a/v3/examples/liquid-glass/main.go b/v3/examples/liquid-glass/main.go index 9562c5930..ff2644cab 100644 --- a/v3/examples/liquid-glass/main.go +++ b/v3/examples/liquid-glass/main.go @@ -3,6 +3,7 @@ package main import ( _ "embed" "encoding/base64" + "fmt" "log" "runtime" "strings" @@ -25,10 +26,11 @@ func main() { // Check if running on macOS if runtime.GOOS != "darwin" { // Show dialog for non-macOS platforms - application.InfoDialog(). + app.Dialog.Info(). SetTitle("macOS Only Demo"). SetMessage("The Liquid Glass effect is a macOS-specific feature that uses native NSGlassEffectView (macOS 15.0+) or NSVisualEffectView.\n\nThis demo is not available on " + runtime.GOOS + "."). Show() + fmt.Println("The Liquid Glass effect is a macOS-specific feature. This demo is not available on", runtime.GOOS) return } diff --git a/v3/examples/panic-handling/main.go b/v3/examples/panic-handling/main.go index cb19165be..3bf02136c 100644 --- a/v3/examples/panic-handling/main.go +++ b/v3/examples/panic-handling/main.go @@ -10,6 +10,8 @@ import ( //go:embed assets/* var assets embed.FS +var app *application.App + type WindowService struct{} func (s *WindowService) GeneratePanic() { @@ -27,7 +29,7 @@ func (s *WindowService) call2() { // ============================================== func main() { - app := application.New(application.Options{ + app = application.New(application.Options{ Name: "Panic Handler Demo", Description: "A demo of Handling Panics", Assets: application.AssetOptions{ @@ -44,7 +46,7 @@ func main() { fmt.Printf("Time: %s\n", panicDetails.Time) fmt.Printf("Error: %s\n", panicDetails.Error) fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace) - application.InfoDialog().SetMessage("There was a panic!").Show() + app.Dialog.Info().SetMessage("There was a panic!").Show() }, }) diff --git a/v3/examples/systray-menu/main.go b/v3/examples/systray-menu/main.go index d7ef2c6f4..b342f0fe1 100644 --- a/v3/examples/systray-menu/main.go +++ b/v3/examples/systray-menu/main.go @@ -59,7 +59,7 @@ func main() { myMenu.Add("Hello World!").OnClick(func(ctx *application.Context) { println("Hello World!") - q := application.QuestionDialog().SetTitle("Ready?").SetMessage("Are you feeling ready?") + q := app.Dialog.Question().SetTitle("Ready?").SetMessage("Are you feeling ready?") q.AddButton("Yes").OnClick(func() { println("Awesome!") }) @@ -75,7 +75,7 @@ func main() { myMenu.AddSeparator() myMenu.AddCheckbox("Checked", true).OnClick(func(ctx *application.Context) { println("Checked: ", ctx.ClickedMenuItem().Checked()) - application.InfoDialog().SetTitle("Hello World!").SetMessage("Hello World!").Show() + app.Dialog.Info().SetTitle("Hello World!").SetMessage("Hello World!").Show() }) myMenu.Add("Enabled").OnClick(func(ctx *application.Context) { println("Click me!") diff --git a/v3/examples/window/main.go b/v3/examples/window/main.go index 46a271c49..a70cc1e8a 100644 --- a/v3/examples/window/main.go +++ b/v3/examples/window/main.go @@ -512,7 +512,7 @@ func main() { sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { width, height := w.Size() - application.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() }) }) @@ -544,7 +544,7 @@ func main() { positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { x, y := w.Position() - application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() }) }) @@ -568,7 +568,7 @@ func main() { positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { x, y := w.RelativePosition() - application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() }) }) @@ -681,24 +681,24 @@ func main() { stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { screen := app.Screen.GetPrimary() msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + app.Dialog.Info().SetTitle("Primary Screen").SetMessage(msg).Show() }) stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { screens := app.Screen.GetAll() for _, screen := range screens { msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() } }) stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { screen, err := w.GetScreen() if err != nil { - application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + app.Dialog.Error().SetTitle("Error").SetMessage(err.Error()).Show() return } msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() }) }) stateMenu.Add("Disable for 5s").OnClick(func(ctx *application.Context) { diff --git a/v3/internal/commands/appimage_testfiles/main.go b/v3/internal/commands/appimage_testfiles/main.go index a77c79e70..68289d0a8 100644 --- a/v3/internal/commands/appimage_testfiles/main.go +++ b/v3/internal/commands/appimage_testfiles/main.go @@ -225,7 +225,7 @@ func main() { sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { width, height := w.Size() - application.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() }) }) @@ -256,7 +256,7 @@ func main() { positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { x, y := w.RelativePosition() - application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() }) }) @@ -275,7 +275,7 @@ func main() { positionMenu.Add("Get Position").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { x, y := w.Position() - application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + app.Dialog.Info().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() }) }) @@ -342,24 +342,24 @@ func main() { stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { screen := app.Screen.GetPrimary() msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + app.Dialog.Info().SetTitle("Primary Screen").SetMessage(msg).Show() }) stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { screens := app.Screen.GetAll() for _, screen := range screens { msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() } }) stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { currentWindow(func(w application.Window) { screen, err := w.GetScreen() if err != nil { - application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + app.Dialog.Error().SetTitle("Error").SetMessage(err.Error()).Show() return } msg := fmt.Sprintf("Screen: %+v", screen) - application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + app.Dialog.Info().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() }) }) stateMenu.Add("Disable for 5s").OnClick(func(ctx *application.Context) { diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index ec1320b18..5a8c6f068 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -863,30 +863,6 @@ func (a *App) SetIcon(icon []byte) { } } -func InfoDialog() *MessageDialog { - return newMessageDialog(InfoDialogType) -} - -func QuestionDialog() *MessageDialog { - return newMessageDialog(QuestionDialogType) -} - -func WarningDialog() *MessageDialog { - return newMessageDialog(WarningDialogType) -} - -func ErrorDialog() *MessageDialog { - return newMessageDialog(ErrorDialogType) -} - -func OpenFileDialog() *OpenFileDialogStruct { - return newOpenFileDialog() -} - -func SaveFileDialog() *SaveFileDialogStruct { - return newSaveFileDialog() -} - func (a *App) dispatchOnMainThread(fn func()) { // If we are on the main thread, just call the function if a.impl.isOnMainThread() { diff --git a/v3/pkg/application/dialog_manager.go b/v3/pkg/application/dialog_manager.go index fa49392ae..36e5842de 100644 --- a/v3/pkg/application/dialog_manager.go +++ b/v3/pkg/application/dialog_manager.go @@ -14,44 +14,44 @@ func newDialogManager(app *App) *DialogManager { // OpenFile creates a file dialog for selecting files func (dm *DialogManager) OpenFile() *OpenFileDialogStruct { - return OpenFileDialog() + return newOpenFileDialog() } // OpenFileWithOptions creates a file dialog with options func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct { - result := OpenFileDialog() + result := newOpenFileDialog() result.SetOptions(options) return result } // SaveFile creates a save file dialog func (dm *DialogManager) SaveFile() *SaveFileDialogStruct { - return SaveFileDialog() + return newSaveFileDialog() } // SaveFileWithOptions creates a save file dialog with options func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct { - result := SaveFileDialog() + result := newSaveFileDialog() result.SetOptions(options) return result } // Info creates an information dialog func (dm *DialogManager) Info() *MessageDialog { - return InfoDialog() + return newMessageDialog(InfoDialogType) } // Question creates a question dialog func (dm *DialogManager) Question() *MessageDialog { - return QuestionDialog() + return newMessageDialog(QuestionDialogType) } // Warning creates a warning dialog func (dm *DialogManager) Warning() *MessageDialog { - return WarningDialog() + return newMessageDialog(WarningDialogType) } // Error creates an error dialog func (dm *DialogManager) Error() *MessageDialog { - return ErrorDialog() + return newMessageDialog(ErrorDialogType) } diff --git a/v3/pkg/application/messageprocessor_dialog.go b/v3/pkg/application/messageprocessor_dialog.go index fafbc8b81..097bd7d7f 100644 --- a/v3/pkg/application/messageprocessor_dialog.go +++ b/v3/pkg/application/messageprocessor_dialog.go @@ -43,13 +43,13 @@ func (m *MessageProcessor) processDialogMethod(req *RuntimeRequest, window Windo var dialog *MessageDialog switch req.Method { case DialogInfo: - dialog = InfoDialog() + dialog = newMessageDialog(InfoDialogType) case DialogWarning: - dialog = WarningDialog() + dialog = newMessageDialog(WarningDialogType) case DialogError: - dialog = ErrorDialog() + dialog = newMessageDialog(ErrorDialogType) case DialogQuestion: - dialog = QuestionDialog() + dialog = newMessageDialog(QuestionDialogType) } var detached = args.Bool("Detached") if detached == nil || !*detached {