diff --git a/v2/internal/ffenestri/ffenestri_darwin.c b/v2/internal/ffenestri/ffenestri_darwin.c index 167b8605..3fa1c64e 100644 --- a/v2/internal/ffenestri/ffenestri_darwin.c +++ b/v2/internal/ffenestri/ffenestri_darwin.c @@ -20,6 +20,7 @@ #define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input)) #define ALLOC(classname) msg(c(classname), s("alloc")) +#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init")) #define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame")) #define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds")) @@ -86,14 +87,24 @@ #define NSImageAbove 5 #define NSImageOverlaps 6 +#define NSAlertStyleWarning 0 +#define NSAlertStyleInformational 1 +#define NSAlertStyleCritical 2 + +#define NSAlertFirstButtonReturn 1000 +#define NSAlertSecondButtonReturn 1001 +#define NSAlertThirdButtonReturn 1002 // References to assets extern const unsigned char *assets[]; extern const unsigned char runtime; -// Tray icon +// Tray icons extern const unsigned char *trayIcons[]; +// Dialog icons +extern const unsigned char *dialogIcons[]; + // MAIN DEBUG FLAG int debug; @@ -121,6 +132,9 @@ struct hashmap_s menuItemMapForContextMenus; // RadioGroup map for the context menus. Maps a menuitem id with its associated radio group items struct hashmap_s radioGroupMapForContextMenus; +// A cache for all our dialog icons +struct hashmap_s dialogIconCache; + // Context menu data is given by the frontend when clicking a context menu. // We send this to the backend when an item is selected; const char *contextMenuData; @@ -927,12 +941,23 @@ void destroyContextMenus(struct Application *app) { } +void freeDialogIconCache(struct Application *app) { + // Release the tray cache images + if( hashmap_num_entries(&dialogIconCache) > 0 ) { + if (0!=hashmap_iterate_pairs(&dialogIconCache, releaseNSObject, NULL)) { + Fatal(app, "failed to release hashmap entries!"); + } + } + + //Free radio groups hashmap + hashmap_destroy(&dialogIconCache); +} void destroyTray(struct Application *app) { // Release the tray cache images if( hashmap_num_entries(&trayIconCache) > 0 ) { - if (0!=hashmap_iterate_pairs(&radioGroupMapForApplicationMenu, releaseNSObject, NULL)) { + if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) { Fatal(app, "failed to release hashmap entries!"); } } @@ -996,6 +1021,9 @@ void DestroyApplication(struct Application *app) { // Destroy the context menus destroyContextMenus(app); + // Free dialog icon cache + freeDialogIconCache(app); + // Clear context menu data if we have it if( contextMenuData != NULL ) { MEMFREE(contextMenuData); @@ -1140,6 +1168,35 @@ void SetPosition(struct Application *app, int x, int y) { ); } + +void showDialog(struct Application *app) { + ON_MAIN_THREAD( + id alert = ALLOC_INIT("NSAlert"); + msg(alert, s("setAlertStyle:"), NSAlertStyleInformational); + msg(alert, s("setMessageText:"), str("Hello World!")); + msg(alert, s("setInformativeText:"), str("Hello again World!")); + msg(alert, s("addButtonWithTitle:"), str("One")); + msg(alert, s("addButtonWithTitle:"), str("Two")); + msg(alert, s("addButtonWithTitle:"), str("Three")); + msg(alert, s("addButtonWithTitle:"), str("Four")); + id dialogImage = hashmap_get(&dialogIconCache, "info", strlen("info")); + msg(alert, s("setIcon:"), dialogImage); + int response = (int)msg(alert, s("runModal")); + if( response == NSAlertFirstButtonReturn ) { + printf("FIRST BUTTON PRESSED"); + } + else if( response == NSAlertSecondButtonReturn ) { + printf("SECOND BUTTON PRESSED"); + } + else if( response == NSAlertThirdButtonReturn ) { + printf("THIRD BUTTON PRESSED"); + } + else { + printf("FORTH BUTTON PRESSED"); + } + ); +} + // OpenDialog opens a dialog to select files/directories void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) { Debug(app, "OpenDialog Called with callback id: %s", callbackID); @@ -1365,6 +1422,7 @@ void SetContextMenus(struct Application *app, const char *contextMenusAsJSON) { app->contextMenusAsJSON = contextMenusAsJSON; } + void SetBindings(struct Application *app, const char *bindings) { const char* temp = concat("window.wailsbindings = \"", bindings); const char* jscall = concat(temp, "\";"); @@ -2409,6 +2467,40 @@ void UpdateContextMenus(struct Application *app, const char *contextMenusAsJSON) ); } +void processDialogIcons(struct Application *app) { + + // Allocate the Dialog icon hashmap + if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) { + // Couldn't allocate map + Fatal(app, "Not enough memory to allocate dialogIconCache!"); + return; + } + + unsigned int count = 0; + while( 1 ) { + const unsigned char *name = dialogIcons[count++]; + if( name == 0x00 ) { + break; + } + const unsigned char *lengthAsString = dialogIcons[count++]; + if( name == 0x00 ) { + break; + } + const unsigned char *data = dialogIcons[count++]; + if( data == 0x00 ) { + break; + } + int length = atoi((const char *)lengthAsString); + + // Create the icon and add to the hashmap + id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length); + id dialogImage = ALLOC("NSImage"); + msg(dialogImage, s("initWithData:"), imageData); + hashmap_put(&dialogIconCache, (const char *)name, strlen((const char *)name), dialogImage); + } + +} + void Run(struct Application *app, int argc, char **argv) { @@ -2595,6 +2687,9 @@ void Run(struct Application *app, int argc, char **argv) { parseContextMenus(app); } + // Process dialog icons + processDialogIcons(app); + // We set it to be invisible by default. It will become visible when everything has initialised msg(app->mainWindow, s("setIsVisible:"), NO); diff --git a/v2/internal/project/project.go b/v2/internal/project/project.go index a196d3ac..947315ff 100644 --- a/v2/internal/project/project.go +++ b/v2/internal/project/project.go @@ -25,6 +25,9 @@ type Project struct { // The path to the project directory Path string + // Icons directory + IconsDir string `json:"icons_dir"` + // The output filename OutputFilename string `json:"outputfilename"` @@ -72,6 +75,11 @@ func Load(projectPath string) (*Project, error) { result.Name = "wailsapp" } + // Set default icons directory if none given + if result.IconsDir == "" { + result.IconsDir = filepath.Join(result.Path, "icons") + } + // Fix up OutputFilename switch runtime.GOOS { case "windows": diff --git a/v2/pkg/commands/build/desktop.go b/v2/pkg/commands/build/desktop.go index 2b98d413..e06d3da7 100644 --- a/v2/pkg/commands/build/desktop.go +++ b/v2/pkg/commands/build/desktop.go @@ -24,6 +24,11 @@ func newDesktopBuilder() *DesktopBuilder { func (d *DesktopBuilder) BuildAssets(options *Options) error { var err error + // Check icon directory exists + if !fs.DirExists(options.ProjectData.IconsDir) { + return fmt.Errorf("icon directory %s does not exist", options.ProjectData.IconsDir) + } + // Get a list of assets from the HTML assets, err := d.BaseBuilder.ExtractAssets() if err != nil { @@ -74,6 +79,12 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti return err } + // Process Dialog Icons + err = d.processDialogIcons(assetDir, options) + if err != nil { + return err + } + outputLogger.Println("done.") return nil @@ -84,7 +95,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti func (d *DesktopBuilder) processApplicationIcon(assetDir string) error { // Copy default icon if one doesn't exist - iconFile := filepath.Join(d.projectData.Path, "icon.png") + iconFile := filepath.Join(d.projectData.IconsDir, "appicon.png") if !fs.FileExists(iconFile) { err := fs.CopyFile(defaultIconPath(), iconFile) if err != nil { diff --git a/v2/pkg/commands/build/desktop_darwin.go b/v2/pkg/commands/build/desktop_darwin.go index f65badec..a1c7cdc1 100644 --- a/v2/pkg/commands/build/desktop_darwin.go +++ b/v2/pkg/commands/build/desktop_darwin.go @@ -21,13 +21,13 @@ func (d *DesktopBuilder) convertToHexLiteral(bytes []byte) string { return result } -// desktop_linux.go will compile the tray icon found at /trayicon.png into the application +// We will compile all tray icons found at /icons/tray/*.png into the application func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) error { var err error // Get all the tray icon filenames - trayIconDirectory := filepath.Join(options.ProjectData.Path, "trayicons") + trayIconDirectory := filepath.Join(options.ProjectData.IconsDir, "tray") var trayIconFilenames []string if fs.DirExists(trayIconDirectory) { trayIconFilenames, err = filepath.Glob(trayIconDirectory + "/*.png") @@ -101,3 +101,93 @@ func (d *DesktopBuilder) processTrayIcons(assetDir string, options *Options) err } return nil } + +// We will compile all dialog icons found at /icons/dialog/*.png into the application +func (d *DesktopBuilder) processDialogIcons(assetDir string, options *Options) error { + + var err error + + // Get all the dialog icon filenames + dialogIconDirectory := filepath.Join(options.ProjectData.IconsDir, "dialog") + var dialogIconFilenames []string + + // If the user has no custom dialog icons, copy the defaults + if !fs.DirExists(dialogIconDirectory) { + defaultDialogIconsDirectory := fs.RelativePath("./internal/packager/icons/dialog") + err := fs.CopyDir(defaultDialogIconsDirectory, dialogIconDirectory) + if err != nil { + return err + } + } + + dialogIconFilenames, err = filepath.Glob(dialogIconDirectory + "/*.png") + if err != nil { + log.Fatal(err) + return err + } + + // Setup target + targetFilename := "dialogicons" + targetFile := filepath.Join(assetDir, targetFilename+".c") + //d.addFileToDelete(targetFile) + + var dataBytes []byte + + // Use a strings builder + var cdata strings.Builder + + // Write header + header := `// dialogicons.c +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL. +// This file was auto-generated. DO NOT MODIFY. + +` + cdata.WriteString(header) + + var variableList slicer.StringSlicer + + // Loop over icons + for count, filename := range dialogIconFilenames { + + // Load the tray icon + dataBytes, err = ioutil.ReadFile(filename) + if err != nil { + return err + } + + iconname := strings.TrimSuffix(filepath.Base(filename), ".png") + dialogIconName := fmt.Sprintf("dialogIcon%dName", count) + variableList.Add(dialogIconName) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { %s0x00 };\n", dialogIconName, d.convertToHexLiteral([]byte(iconname)))) + + dialogIconLength := fmt.Sprintf("dialogIcon%dLength", count) + variableList.Add(dialogIconLength) + lengthAsString := strconv.Itoa(len(dataBytes)) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { %s0x00 };\n", dialogIconLength, d.convertToHexLiteral([]byte(lengthAsString)))) + + dialogIconData := fmt.Sprintf("dialogIcon%dData", count) + variableList.Add(dialogIconData) + cdata.WriteString(fmt.Sprintf("const unsigned char %s[] = { ", dialogIconData)) + + // Convert each byte to hex + for _, b := range dataBytes { + cdata.WriteString(fmt.Sprintf("0x%x, ", b)) + } + + cdata.WriteString("0x00 };\n") + } + + // Write out main dialogIcons data + cdata.WriteString("const unsigned char *dialogIcons[] = { ") + cdata.WriteString(variableList.Join(", ")) + if len(dialogIconFilenames) > 0 { + cdata.WriteString(", ") + } + cdata.WriteString("0x00 };\n") + + err = ioutil.WriteFile(targetFile, []byte(cdata.String()), 0600) + if err != nil { + return err + } + return nil +} diff --git a/v2/pkg/commands/build/internal/packager/icons/dialog/info.png b/v2/pkg/commands/build/internal/packager/icons/dialog/info.png new file mode 100644 index 00000000..0812e0ff Binary files /dev/null and b/v2/pkg/commands/build/internal/packager/icons/dialog/info.png differ diff --git a/v2/pkg/commands/build/packager_darwin.go b/v2/pkg/commands/build/packager_darwin.go index 7dad5453..a52e0116 100644 --- a/v2/pkg/commands/build/packager_darwin.go +++ b/v2/pkg/commands/build/packager_darwin.go @@ -47,7 +47,7 @@ func packageApplication(options *Options) error { } // Generate Icons - err = processApplicationIcon(resourceDir) + err = processApplicationIcon(resourceDir, options.ProjectData.IconsDir) if err != nil { return err } @@ -129,12 +129,9 @@ func newPlistData(title, exe, packageID, version, author string) *plistData { } } -func processApplicationIcon(resourceDir string) (err error) { +func processApplicationIcon(resourceDir string, iconsDir string) (err error) { - appIcon, err := fs.RelativeToCwd("appicon.png") - if err != nil { - return err - } + appIcon := filepath.Join(iconsDir, "appicon.png") // Install default icon if one doesn't exist if !fs.FileExists(appIcon) { diff --git a/v2/test/kitchensink/icon.png b/v2/test/kitchensink/icon.png deleted file mode 100644 index a2b30415..00000000 Binary files a/v2/test/kitchensink/icon.png and /dev/null differ diff --git a/v2/test/kitchensink/appicon.png b/v2/test/kitchensink/icons/appicon.png similarity index 100% rename from v2/test/kitchensink/appicon.png rename to v2/test/kitchensink/icons/appicon.png diff --git a/v2/test/kitchensink/icons/dialog/info.png b/v2/test/kitchensink/icons/dialog/info.png new file mode 100644 index 00000000..0812e0ff Binary files /dev/null and b/v2/test/kitchensink/icons/dialog/info.png differ diff --git a/v2/test/kitchensink/trayicons/dark.png b/v2/test/kitchensink/icons/tray/dark.png similarity index 100% rename from v2/test/kitchensink/trayicons/dark.png rename to v2/test/kitchensink/icons/tray/dark.png diff --git a/v2/test/kitchensink/trayicons/light.png b/v2/test/kitchensink/icons/tray/light.png similarity index 100% rename from v2/test/kitchensink/trayicons/light.png rename to v2/test/kitchensink/icons/tray/light.png diff --git a/v2/test/kitchensink/trayicons/svelte.png b/v2/test/kitchensink/icons/tray/svelte.png similarity index 100% rename from v2/test/kitchensink/trayicons/svelte.png rename to v2/test/kitchensink/icons/tray/svelte.png