Merge pull request #29119 from bruvzg/native_icon_support
Add native window/taskbar icon support for Windows and macOS.
This commit is contained in:
commit
e8fbb28e20
|
@ -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<Image> &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);
|
||||
|
|
|
@ -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<Image> &p_icon);
|
||||
|
||||
int get_exit_code() const;
|
||||
|
|
|
@ -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<Image> &p_icon) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Image> &p_icon);
|
||||
|
||||
virtual int get_exit_code() const;
|
||||
|
|
|
@ -697,7 +697,19 @@
|
|||
<argument index="0" name="icon" type="Image">
|
||||
</argument>
|
||||
<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>
|
||||
</method>
|
||||
<method name="set_ime_active">
|
||||
|
|
|
@ -693,6 +693,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_exclude_filter(), true);
|
||||
|
||||
|
|
|
@ -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<InputDefault>(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<Image> icon;
|
||||
icon.instance();
|
||||
if (ImageLoader::load_image(iconpath, icon) == OK) {
|
||||
|
|
|
@ -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<Image> &p_icon);
|
||||
|
||||
virtual MainLoop *get_main_loop() const;
|
||||
|
|
|
@ -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<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) {
|
||||
|
||||
Ref<Image> img = p_icon;
|
||||
|
|
|
@ -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<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) {
|
||||
|
||||
ERR_FAIL_COND(!p_icon.is_valid());
|
||||
|
|
|
@ -58,6 +58,25 @@
|
|||
/**
|
||||
@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 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<Image> &p_icon);
|
||||
|
||||
virtual String get_executable_path() const;
|
||||
|
|
Loading…
Reference in New Issue