diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp index ddb60e1345f..27b33d942bc 100644 --- a/core/bind/core_bind.cpp +++ b/core/bind/core_bind.cpp @@ -611,6 +611,11 @@ uint64_t _OS::get_dynamic_memory_usage() const { 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 &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_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("get_exit_code"), &_OS::get_exit_code); diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h index d4fa3bc7356..3e46d24627b 100644 --- a/core/bind/core_bind.h +++ b/core/bind/core_bind.h @@ -275,6 +275,7 @@ public: void set_use_file_access_save_and_swap(bool p_enable); + void set_native_icon(const String &p_filename); void set_icon(const Ref &p_icon); int get_exit_code() const; diff --git a/core/os/os.cpp b/core/os/os.cpp index ea378c9e836..1a3c9ac5f8f 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -465,6 +465,9 @@ void OS::_ensure_user_data_dir() { memdelete(da); } +void OS::set_native_icon(const String &p_filename) { +} + void OS::set_icon(const Ref &p_icon) { } diff --git a/core/os/os.h b/core/os/os.h index 07865b636e6..4f6a539e786 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -451,6 +451,7 @@ public: virtual void make_rendering_thread(); virtual void swap_buffers(); + virtual void set_native_icon(const String &p_filename); virtual void set_icon(const Ref &p_icon); virtual int get_exit_code() const; diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index c9c83bc0e0f..ec2ee787017 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -697,7 +697,19 @@ - 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. + + + + + + + + + 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. diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index df481e08556..37f148bdbb5 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -693,6 +693,10 @@ Error EditorExportPlatform::export_project_files(const Ref & } } + //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_exclude_filter(), true); diff --git a/main/main.cpp b/main/main.cpp index fb75b4d78fb..fdb5bca624b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1197,6 +1197,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) { 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")); + 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(Input::get_singleton()); if (id) { if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { @@ -1747,8 +1753,24 @@ bool Main::start() { ERR_FAIL_COND_V(!scene, false) 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()"); - if (iconpath != "") { + if ((iconpath != "") && (!hasicon)) { Ref icon; icon.instance(); if (ImageLoader::load_image(iconpath, icon) == OK) { diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index d2a6f38b012..212966af118 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -186,6 +186,7 @@ public: virtual Size2 get_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 &p_icon); virtual MainLoop *get_main_loop() const; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index fc97bd4a20e..113c6636f0c 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -1858,6 +1858,31 @@ void OS_OSX::set_window_title(const String &p_title) { [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 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 &p_icon) { Ref img = p_icon; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 4c6e4e96b56..e3494a1dbf0 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -2577,6 +2577,117 @@ String OS_Windows::get_executable_path() const { 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 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 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 &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 59aeb01b51d..0aacbcb9ff3 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -58,6 +58,25 @@ /** @author Juan Linietsky */ + +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 OS_Windows : public OS { @@ -276,6 +295,8 @@ public: CursorShape get_cursor_shape() const; 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 set_native_icon(const String &p_filename); void set_icon(const Ref &p_icon); virtual String get_executable_path() const;