Add native window/taskbar icon support for Windows and macOS.

Co-authored-by: Markus Törnqvist <mjt@nysv.org>
This commit is contained in:
bruvzg 2019-05-17 16:43:56 +03:00
parent c088386c5b
commit 2b9ed68d6a
No known key found for this signature in database
GPG Key ID: 89DD917D9CE4218D
11 changed files with 209 additions and 2 deletions

View File

@ -611,6 +611,11 @@ uint64_t _OS::get_dynamic_memory_usage() const {
return OS::get_singleton()->get_dynamic_memory_usage(); return OS::get_singleton()->get_dynamic_memory_usage();
} }
void _OS::set_native_icon(const String &p_filename) {
OS::get_singleton()->set_native_icon(p_filename);
}
void _OS::set_icon(const Ref<Image> &p_icon) { void _OS::set_icon(const Ref<Image> &p_icon) {
OS::get_singleton()->set_icon(p_icon); OS::get_singleton()->set_icon(p_icon);
@ -1199,6 +1204,7 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_system_time_secs"), &_OS::get_system_time_secs); ClassDB::bind_method(D_METHOD("get_system_time_secs"), &_OS::get_system_time_secs);
ClassDB::bind_method(D_METHOD("get_system_time_msecs"), &_OS::get_system_time_msecs); ClassDB::bind_method(D_METHOD("get_system_time_msecs"), &_OS::get_system_time_msecs);
ClassDB::bind_method(D_METHOD("set_native_icon", "filename"), &_OS::set_native_icon);
ClassDB::bind_method(D_METHOD("set_icon", "icon"), &_OS::set_icon); ClassDB::bind_method(D_METHOD("set_icon", "icon"), &_OS::set_icon);
ClassDB::bind_method(D_METHOD("get_exit_code"), &_OS::get_exit_code); ClassDB::bind_method(D_METHOD("get_exit_code"), &_OS::get_exit_code);

View File

@ -275,6 +275,7 @@ public:
void set_use_file_access_save_and_swap(bool p_enable); void set_use_file_access_save_and_swap(bool p_enable);
void set_native_icon(const String &p_filename);
void set_icon(const Ref<Image> &p_icon); void set_icon(const Ref<Image> &p_icon);
int get_exit_code() const; int get_exit_code() const;

View File

@ -465,6 +465,9 @@ void OS::_ensure_user_data_dir() {
memdelete(da); memdelete(da);
} }
void OS::set_native_icon(const String &p_filename) {
}
void OS::set_icon(const Ref<Image> &p_icon) { void OS::set_icon(const Ref<Image> &p_icon) {
} }

View File

@ -451,6 +451,7 @@ public:
virtual void make_rendering_thread(); virtual void make_rendering_thread();
virtual void swap_buffers(); virtual void swap_buffers();
virtual void set_native_icon(const String &p_filename);
virtual void set_icon(const Ref<Image> &p_icon); virtual void set_icon(const Ref<Image> &p_icon);
virtual int get_exit_code() const; virtual int get_exit_code() const;

View File

@ -697,7 +697,19 @@
<argument index="0" name="icon" type="Image"> <argument index="0" name="icon" type="Image">
</argument> </argument>
<description> <description>
Sets the game's icon. Sets the game's icon using an [Image] resource.
The same image is used for window caption, taskbar/dock and window selection dialog. Image is scaled as needed.
</description>
</method>
<method name="set_native_icon">
<return type="void">
</return>
<argument index="0" name="filename" type="String">
</argument>
<description>
Sets the game's icon using a multi-size platform-specific icon file ([code]*.ico[/code] on Windows and [code]*.icns[/code] on macOS).
Appropriate size sub-icons are used for window caption, taskbar/dock and window selection dialog.
Note: This method is only implemented on macOS and Windows.
</description> </description>
</method> </method>
<method name="set_ime_active"> <method name="set_ime_active">

View File

@ -692,6 +692,10 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
} }
} }
//add native icons to non-resource include list
_edit_filter_list(paths, String("*.icns"), false);
_edit_filter_list(paths, String("*.ico"), false);
_edit_filter_list(paths, p_preset->get_include_filter(), false); _edit_filter_list(paths, p_preset->get_include_filter(), false);
_edit_filter_list(paths, p_preset->get_exclude_filter(), true); _edit_filter_list(paths, p_preset->get_exclude_filter(), true);

View File

@ -1189,6 +1189,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
GLOBAL_DEF("application/config/icon", String()); GLOBAL_DEF("application/config/icon", String());
ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon", PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp")); ProjectSettings::get_singleton()->set_custom_property_info("application/config/icon", PropertyInfo(Variant::STRING, "application/config/icon", PROPERTY_HINT_FILE, "*.png,*.webp"));
GLOBAL_DEF("application/config/macos_native_icon", String());
ProjectSettings::get_singleton()->set_custom_property_info("application/config/macos_native_icon", PropertyInfo(Variant::STRING, "application/config/macos_native_icon", PROPERTY_HINT_FILE, "*.icns"));
GLOBAL_DEF("application/config/windows_native_icon", String());
ProjectSettings::get_singleton()->set_custom_property_info("application/config/windows_native_icon", PropertyInfo(Variant::STRING, "application/config/windows_native_icon", PROPERTY_HINT_FILE, "*.ico"));
InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton()); InputDefault *id = Object::cast_to<InputDefault>(Input::get_singleton());
if (id) { if (id) {
if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) {
@ -1739,8 +1745,24 @@ bool Main::start() {
ERR_FAIL_COND_V(!scene, false) ERR_FAIL_COND_V(!scene, false)
sml->add_current_scene(scene); sml->add_current_scene(scene);
#ifdef OSX_ENABLED
String mac_iconpath = GLOBAL_DEF("application/config/macos_native_icon", "Variant()");
if (mac_iconpath != "") {
OS::get_singleton()->set_native_icon(mac_iconpath);
hasicon = true;
}
#endif
#ifdef WINDOWS_ENABLED
String win_iconpath = GLOBAL_DEF("application/config/windows_native_icon", "Variant()");
if (win_iconpath != "") {
OS::get_singleton()->set_native_icon(win_iconpath);
hasicon = true;
}
#endif
String iconpath = GLOBAL_DEF("application/config/icon", "Variant()"); String iconpath = GLOBAL_DEF("application/config/icon", "Variant()");
if (iconpath != "") { if ((iconpath != "") && (!hasicon)) {
Ref<Image> icon; Ref<Image> icon;
icon.instance(); icon.instance();
if (ImageLoader::load_image(iconpath, icon) == OK) { if (ImageLoader::load_image(iconpath, icon) == OK) {

View File

@ -186,6 +186,7 @@ public:
virtual Size2 get_window_size() const; virtual Size2 get_window_size() const;
virtual Size2 get_real_window_size() const; virtual Size2 get_real_window_size() const;
virtual void set_native_icon(const String &p_filename);
virtual void set_icon(const Ref<Image> &p_icon); virtual void set_icon(const Ref<Image> &p_icon);
virtual MainLoop *get_main_loop() const; virtual MainLoop *get_main_loop() const;

View File

@ -1858,6 +1858,31 @@ void OS_OSX::set_window_title(const String &p_title) {
[window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]]; [window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
} }
void OS_OSX::set_native_icon(const String &p_filename) {
FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
ERR_FAIL_COND(!f);
Vector<uint8_t> data;
uint32_t len = f->get_len();
data.resize(len);
f->get_buffer((uint8_t *)&data.write[0], len);
memdelete(f);
NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
if (!icon_data) {
ERR_EXPLAIN("Error reading icon data");
ERR_FAIL();
}
NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
if (!icon) {
ERR_EXPLAIN("Error loading icon");
ERR_FAIL();
}
[NSApp setApplicationIconImage:icon];
}
void OS_OSX::set_icon(const Ref<Image> &p_icon) { void OS_OSX::set_icon(const Ref<Image> &p_icon) {
Ref<Image> img = p_icon; Ref<Image> img = p_icon;

View File

@ -2577,6 +2577,117 @@ String OS_Windows::get_executable_path() const {
return s; return s;
} }
void OS_Windows::set_native_icon(const String &p_filename) {
FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
ERR_FAIL_COND(!f);
ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
int pos = 0;
icon_dir->idReserved = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
icon_dir->idType = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
if (icon_dir->idType != 1) {
ERR_EXPLAIN("Invalid icon file format!");
ERR_FAIL();
}
icon_dir->idCount = f->get_32();
pos += sizeof(WORD);
f->seek(pos);
icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY));
f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY));
int small_icon_index = -1; // Select 16x16 with largest color count
int small_icon_cc = 0;
int big_icon_index = -1; // Select largest
int big_icon_width = 16;
int big_icon_cc = 0;
for (int i = 0; i < icon_dir->idCount; i++) {
int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount;
int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth;
if (width == 16) {
if (colors >= small_icon_cc) {
small_icon_index = i;
small_icon_cc = colors;
}
}
if (width >= big_icon_width) {
if (colors >= big_icon_cc) {
big_icon_index = i;
big_icon_width = width;
big_icon_cc = colors;
}
}
}
if (big_icon_index == -1) {
ERR_EXPLAIN("No valid icons found!");
ERR_FAIL();
}
if (small_icon_index == -1) {
WARN_PRINTS("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!");
small_icon_index = big_icon_index;
small_icon_cc = big_icon_cc;
}
// Read the big icon
DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes;
Vector<uint8_t> data_big;
data_big.resize(bytecount_big);
pos = icon_dir->idEntries[big_icon_index].dwImageOffset;
f->seek(pos);
f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big);
HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000);
if (!icon_big) {
ERR_EXPLAIN("Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()));
ERR_FAIL();
}
// Read the small icon
DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes;
Vector<uint8_t> data_small;
data_small.resize(bytecount_small);
pos = icon_dir->idEntries[small_icon_index].dwImageOffset;
f->seek(pos);
f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small);
HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000);
if (!icon_small) {
ERR_EXPLAIN("Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()));
ERR_FAIL();
}
// Online tradition says to be sure last error is cleared and set the small icon first
int err = 0;
SetLastError(err);
SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);
err = GetLastError();
if (err) {
ERR_EXPLAIN("Error setting ICON_SMALL: " + format_error_message(err));
ERR_FAIL();
}
SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big);
err = GetLastError();
if (err) {
ERR_EXPLAIN("Error setting ICON_BIG: " + format_error_message(err));
ERR_FAIL();
}
memdelete(f);
memdelete(icon_dir);
}
void OS_Windows::set_icon(const Ref<Image> &p_icon) { void OS_Windows::set_icon(const Ref<Image> &p_icon) {
ERR_FAIL_COND(!p_icon.is_valid()); ERR_FAIL_COND(!p_icon.is_valid());

View File

@ -58,6 +58,25 @@
/** /**
@author Juan Linietsky <reduzio@gmail.com> @author Juan Linietsky <reduzio@gmail.com>
*/ */
typedef struct {
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved ( must be 0)
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // How many bytes in this resource?
DWORD dwImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct {
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
class JoypadWindows; class JoypadWindows;
class OS_Windows : public OS { class OS_Windows : public OS {
@ -276,6 +295,8 @@ public:
CursorShape get_cursor_shape() const; CursorShape get_cursor_shape() const;
virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap); void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap);
void set_native_icon(const String &p_filename);
void set_icon(const Ref<Image> &p_icon); void set_icon(const Ref<Image> &p_icon);
virtual String get_executable_path() const; virtual String get_executable_path() const;