Support custom dialog icons. Move icons to new dir.

This commit is contained in:
Lea Anthony
2020-12-30 08:27:00 +11:00
parent c7d5e7de72
commit 91fb3501c5
12 changed files with 212 additions and 11 deletions

View File

@@ -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);

View File

@@ -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":

View File

@@ -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 {

View File

@@ -21,13 +21,13 @@ func (d *DesktopBuilder) convertToHexLiteral(bytes []byte) string {
return result
}
// desktop_linux.go will compile the tray icon found at <projectdir>/trayicon.png into the application
// We will compile all tray icons found at <projectdir>/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 <projectdir>/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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -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) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB