Add native window/taskbar icon support for Windows and macOS.
Co-authored-by: Markus Törnqvist <mjt@nysv.org>
This commit is contained in:
parent
c088386c5b
commit
2b9ed68d6a
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue