Implement support for application status indicators (tray icons).

This commit is contained in:
bruvzg 2023-08-03 12:45:56 +03:00
parent dfe226b933
commit 8da36031e4
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
15 changed files with 827 additions and 1 deletions

View file

@ -56,6 +56,15 @@
[b]Note:[/b] This method is only implemented on Linux (X11/Wayland).
</description>
</method>
<method name="create_status_indicator">
<return type="int" />
<param index="0" name="icon" type="Image" />
<param index="1" name="tooltip" type="String" />
<param index="2" name="callback" type="Callable" />
<description>
Creates a new application status indicator with the specified icon, tooltip, and activation callback.
</description>
</method>
<method name="cursor_get_shape" qualifiers="const">
<return type="int" enum="DisplayServer.CursorShape" />
<description>
@ -78,6 +87,13 @@
Sets the default mouse cursor shape. The cursor's appearance will vary depending on the user's operating system and mouse cursor theme. See also [method cursor_get_shape] and [method cursor_set_custom_image].
</description>
</method>
<method name="delete_status_indicator">
<return type="void" />
<param index="0" name="id" type="int" />
<description>
Removes the application status indicator.
</description>
</method>
<method name="dialog_input_text">
<return type="int" enum="Error" />
<param index="0" name="title" type="String" />
@ -1120,6 +1136,30 @@
Sets the window icon (usually displayed in the top-left corner) in the operating system's [i]native[/i] format. The file at [param filename] must be in [code].ico[/code] format on Windows or [code].icns[/code] on macOS. By using specially crafted [code].ico[/code] or [code].icns[/code] icons, [method set_native_icon] allows specifying different icons depending on the size the icon is displayed at. This size is determined by the operating system and user preferences (including the display scale factor). To use icons in other formats, use [method set_icon] instead.
</description>
</method>
<method name="status_indicator_set_callback">
<return type="void" />
<param index="0" name="id" type="int" />
<param index="1" name="callback" type="Callable" />
<description>
Sets the application status indicator activation callback.
</description>
</method>
<method name="status_indicator_set_icon">
<return type="void" />
<param index="0" name="id" type="int" />
<param index="1" name="icon" type="Image" />
<description>
Sets the application status indicator icon.
</description>
</method>
<method name="status_indicator_set_tooltip">
<return type="void" />
<param index="0" name="id" type="int" />
<param index="1" name="tooltip" type="String" />
<description>
Sets the application status indicator tooltip.
</description>
</method>
<method name="tablet_get_current_driver" qualifiers="const">
<return type="String" />
<description>
@ -1748,6 +1788,9 @@
<constant name="FEATURE_SCREEN_CAPTURE" value="21" enum="Feature">
Display server supports reading screen pixels. See [method screen_get_pixel].
</constant>
<constant name="FEATURE_STATUS_INDICATOR" value="22" enum="Feature">
Display server supports application status indicators.
</constant>
<constant name="MOUSE_MODE_VISIBLE" value="0" enum="MouseMode">
Makes the mouse cursor visible if it is hidden.
</constant>
@ -1786,6 +1829,9 @@
<constant name="INVALID_WINDOW_ID" value="-1">
The ID that refers to a nonexistent window. This is returned by some [DisplayServer] methods if no window matches the requested result.
</constant>
<constant name="INVALID_INDICATOR_ID" value="-1">
The ID that refers to a nonexistent application status indicator.
</constant>
<constant name="SCREEN_LANDSCAPE" value="0" enum="ScreenOrientation">
Default landscape orientation.
</constant>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="StatusIndicator" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Application status indicator (aka notification area icon).
[b]Note:[/b] Status indicator is implemented on macOS and Windows.
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<members>
<member name="icon" type="Image" setter="set_icon" getter="get_icon">
Status indicator icon.
</member>
<member name="tooltip" type="String" setter="set_tooltip" getter="get_tooltip" default="&quot;&quot;">
Status indicator tooltip.
</member>
<member name="visible" type="bool" setter="set_visible" getter="is_visible" default="true">
If [code]true[/code], the status indicator is visible.
</member>
</members>
<signals>
<signal name="pressed">
<param index="0" name="mouse_button" type="int" />
<param index="1" name="position" type="Vector2i" />
<description>
Emitted when the status indicator is pressed.
</description>
</signal>
</signals>
</class>

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1 5.184 3H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-3.184ZM3 5h2.584L8 3.4 10.416 5H13v8H3Z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 236 B

View file

@ -14,6 +14,7 @@ files = [
"display_server_macos.mm",
"godot_button_view.mm",
"godot_content_view.mm",
"godot_status_item.mm",
"godot_window_delegate.mm",
"godot_window.mm",
"key_mapping_macos.mm",

View file

@ -201,6 +201,14 @@ private:
HashMap<WindowID, WindowData> windows;
struct IndicatorData {
id view;
id item;
};
IndicatorID indicator_id_counter = 0;
HashMap<IndicatorID, IndicatorData> indicators;
IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID;
struct MenuCall {
@ -486,6 +494,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
virtual void delete_status_indicator(IndicatorID p_id) override;
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);
static Vector<String> get_rendering_drivers_func();

View file

@ -35,6 +35,7 @@
#include "godot_menu_delegate.h"
#include "godot_menu_item.h"
#include "godot_open_save_delegate.h"
#include "godot_status_item.h"
#include "godot_window.h"
#include "godot_window_delegate.h"
#include "key_mapping_macos.h"
@ -838,6 +839,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const {
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_EXTEND_TO_TITLE:
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
return true;
default: {
}
@ -4296,6 +4298,124 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) {
}
}
DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
NSImage *nsimg = nullptr;
if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
Ref<Image> img = p_icon->duplicate();
img->convert(Image::FORMAT_RGBA8);
NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:nullptr
pixelsWide:img->get_width()
pixelsHigh:img->get_height()
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:img->get_width() * 4
bitsPerPixel:32];
if (imgrep) {
uint8_t *pixels = [imgrep bitmapData];
int len = img->get_width() * img->get_height();
const uint8_t *r = img->get_data().ptr();
/* Premultiply the alpha channel */
for (int i = 0; i < len; i++) {
uint8_t alpha = r[i * 4 + 3];
pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
pixels[i * 4 + 3] = alpha;
}
nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
if (nsimg) {
[nsimg addRepresentation:imgrep];
}
}
}
IndicatorData idat;
idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
idat.view = [[GodotStatusItemView alloc] init];
[idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
[idat.view setImage:nsimg];
[idat.view setCallback:p_callback];
[idat.item setView:idat.view];
IndicatorID iid = indicator_id_counter++;
indicators[iid] = idat;
return iid;
}
void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
ERR_FAIL_COND(!indicators.has(p_id));
NSImage *nsimg = nullptr;
if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
Ref<Image> img = p_icon->duplicate();
img->convert(Image::FORMAT_RGBA8);
NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:nullptr
pixelsWide:img->get_width()
pixelsHigh:img->get_height()
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:img->get_width() * 4
bitsPerPixel:32];
if (imgrep) {
uint8_t *pixels = [imgrep bitmapData];
int len = img->get_width() * img->get_height();
const uint8_t *r = img->get_data().ptr();
/* Premultiply the alpha channel */
for (int i = 0; i < len; i++) {
uint8_t alpha = r[i * 4 + 3];
pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255);
pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255);
pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255);
pixels[i * 4 + 3] = alpha;
}
nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())];
if (nsimg) {
[nsimg addRepresentation:imgrep];
}
}
}
[indicators[p_id].view setImage:nsimg];
}
void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
ERR_FAIL_COND(!indicators.has(p_id));
[indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
}
void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
ERR_FAIL_COND(!indicators.has(p_id));
[indicators[p_id].view setCallback:p_callback];
}
void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) {
ERR_FAIL_COND(!indicators.has(p_id));
[[NSStatusBar systemStatusBar] removeStatusItem:indicators[p_id].item];
indicators.erase(p_id);
}
DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) {
DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error));
if (r_error != OK) {
@ -4700,6 +4820,11 @@ DisplayServerMacOS::~DisplayServerMacOS() {
screen_keep_on_assertion = kIOPMNullAssertionID;
}
// Destroy all status indicators.
for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
[[NSStatusBar systemStatusBar] removeStatusItem:E->value.item];
}
// Destroy all windows.
for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) {
HashMap<WindowID, WindowData>::Iterator F = E;

View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* godot_status_item.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GODOT_STATUS_ITEM_H
#define GODOT_STATUS_ITEM_H
#include "core/input/input_enums.h"
#include "core/variant/callable.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@interface GodotStatusItemView : NSView {
NSImage *image;
Callable cb;
}
- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index;
- (void)setImage:(NSImage *)image;
- (void)setCallback:(const Callable &)callback;
@end
#endif // GODOT_STATUS_ITEM_H

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* godot_status_item.mm */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "godot_status_item.h"
#include "display_server_macos.h"
@implementation GodotStatusItemView
- (id)init {
self = [super init];
image = nullptr;
return self;
}
- (void)setImage:(NSImage *)newImage {
image = newImage;
[self setNeedsDisplayInRect:self.frame];
}
- (void)setCallback:(const Callable &)callback {
cb = callback;
}
- (void)drawRect:(NSRect)rect {
if (image) {
[image drawInRect:rect];
}
}
- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index {
DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
if (!ds) {
return;
}
if (cb.is_valid()) {
Variant v_button = index;
Variant v_pos = ds->mouse_get_position();
Variant *v_args[2] = { &v_button, &v_pos };
Variant ret;
Callable::CallError ce;
cb.callp((const Variant **)&v_args, 2, ret, ce);
}
}
- (void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
if (([event modifierFlags] & NSEventModifierFlagControl)) {
[self processMouseEvent:event index:MouseButton::RIGHT];
} else {
[self processMouseEvent:event index:MouseButton::LEFT];
}
}
- (void)rightMouseDown:(NSEvent *)event {
[super rightMouseDown:event];
[self processMouseEvent:event index:MouseButton::RIGHT];
}
- (void)otherMouseDown:(NSEvent *)event {
[super otherMouseDown:event];
if ((int)[event buttonNumber] == 2) {
[self processMouseEvent:event index:MouseButton::MIDDLE];
} else if ((int)[event buttonNumber] == 3) {
[self processMouseEvent:event index:MouseButton::MB_XBUTTON1];
} else if ((int)[event buttonNumber] == 4) {
[self processMouseEvent:event index:MouseButton::MB_XBUTTON2];
}
}
@end

View file

@ -62,6 +62,8 @@
#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19
#endif
#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1)
#if defined(__GNUC__)
// Workaround GCC warning from -Wcast-function-type.
#define GetProcAddress (void *)GetProcAddress
@ -107,6 +109,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const {
case FEATURE_KEEP_SCREEN_ON:
case FEATURE_TEXT_TO_SPEECH:
case FEATURE_SCREEN_CAPTURE:
case FEATURE_STATUS_INDICATOR:
return true;
default:
return false;
@ -2842,6 +2845,172 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) {
}
}
DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
HICON hicon = nullptr;
if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
Ref<Image> img = p_icon;
if (img != icon) {
img = img->duplicate();
img->convert(Image::FORMAT_RGBA8);
}
int w = img->get_width();
int h = img->get_height();
// Create temporary bitmap buffer.
int icon_len = 40 + h * w * 4;
Vector<BYTE> v;
v.resize(icon_len);
BYTE *icon_bmp = v.ptrw();
encode_uint32(40, &icon_bmp[0]);
encode_uint32(w, &icon_bmp[4]);
encode_uint32(h * 2, &icon_bmp[8]);
encode_uint16(1, &icon_bmp[12]);
encode_uint16(32, &icon_bmp[14]);
encode_uint32(BI_RGB, &icon_bmp[16]);
encode_uint32(w * h * 4, &icon_bmp[20]);
encode_uint32(0, &icon_bmp[24]);
encode_uint32(0, &icon_bmp[28]);
encode_uint32(0, &icon_bmp[32]);
encode_uint32(0, &icon_bmp[36]);
uint8_t *wr = &icon_bmp[40];
const uint8_t *r = img->get_data().ptr();
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
uint8_t *wpx = &wr[(i * w + j) * 4];
wpx[0] = rpx[2];
wpx[1] = rpx[1];
wpx[2] = rpx[0];
wpx[3] = rpx[3];
}
}
hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
}
IndicatorData idat;
idat.callback = p_callback;
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
ndat.uID = indicator_id_counter;
ndat.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
ndat.uCallbackMessage = WM_INDICATOR_CALLBACK_MESSAGE;
ndat.hIcon = hicon;
memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
ndat.uVersion = NOTIFYICON_VERSION;
Shell_NotifyIconW(NIM_ADD, &ndat);
Shell_NotifyIconW(NIM_SETVERSION, &ndat);
IndicatorID iid = indicator_id_counter++;
indicators[iid] = idat;
return iid;
}
void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
ERR_FAIL_COND(!indicators.has(p_id));
HICON hicon = nullptr;
if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) {
Ref<Image> img = p_icon;
if (img != icon) {
img = img->duplicate();
img->convert(Image::FORMAT_RGBA8);
}
int w = img->get_width();
int h = img->get_height();
// Create temporary bitmap buffer.
int icon_len = 40 + h * w * 4;
Vector<BYTE> v;
v.resize(icon_len);
BYTE *icon_bmp = v.ptrw();
encode_uint32(40, &icon_bmp[0]);
encode_uint32(w, &icon_bmp[4]);
encode_uint32(h * 2, &icon_bmp[8]);
encode_uint16(1, &icon_bmp[12]);
encode_uint16(32, &icon_bmp[14]);
encode_uint32(BI_RGB, &icon_bmp[16]);
encode_uint32(w * h * 4, &icon_bmp[20]);
encode_uint32(0, &icon_bmp[24]);
encode_uint32(0, &icon_bmp[28]);
encode_uint32(0, &icon_bmp[32]);
encode_uint32(0, &icon_bmp[36]);
uint8_t *wr = &icon_bmp[40];
const uint8_t *r = img->get_data().ptr();
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4];
uint8_t *wpx = &wr[(i * w + j) * 4];
wpx[0] = rpx[2];
wpx[1] = rpx[1];
wpx[2] = rpx[0];
wpx[3] = rpx[3];
}
}
hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
}
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
ndat.uID = p_id;
ndat.uFlags = NIF_ICON;
ndat.hIcon = hicon;
ndat.uVersion = NOTIFYICON_VERSION;
Shell_NotifyIconW(NIM_MODIFY, &ndat);
}
void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
ERR_FAIL_COND(!indicators.has(p_id));
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
ndat.uID = p_id;
ndat.uFlags = NIF_TIP;
memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR));
ndat.uVersion = NOTIFYICON_VERSION;
Shell_NotifyIconW(NIM_MODIFY, &ndat);
}
void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
ERR_FAIL_COND(!indicators.has(p_id));
indicators[p_id].callback = p_callback;
}
void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) {
ERR_FAIL_COND(!indicators.has(p_id));
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
ndat.uID = p_id;
ndat.uVersion = NOTIFYICON_VERSION;
Shell_NotifyIconW(NIM_DELETE, &ndat);
indicators.erase(p_id);
}
void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
_THREAD_SAFE_METHOD_
#if defined(RD_ENABLED)
@ -3351,6 +3520,30 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
}
} break;
case WM_INDICATOR_CALLBACK_MESSAGE: {
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_MBUTTONDOWN || lParam == WM_XBUTTONDOWN) {
IndicatorID iid = (IndicatorID)wParam;
MouseButton mb = MouseButton::LEFT;
if (lParam == WM_RBUTTONDOWN) {
mb = MouseButton::RIGHT;
} else if (lParam == WM_MBUTTONDOWN) {
mb = MouseButton::MIDDLE;
} else if (lParam == WM_XBUTTONDOWN) {
mb = MouseButton::MB_XBUTTON1;
}
if (indicators.has(iid)) {
if (indicators[iid].callback.is_valid()) {
Variant v_button = mb;
Variant v_pos = mouse_get_position();
Variant *v_args[2] = { &v_button, &v_pos };
Variant ret;
Callable::CallError ce;
indicators[iid].callback.callp((const Variant **)&v_args, 2, ret, ce);
}
}
return 0;
}
} break;
case WM_CLOSE: // Did we receive a close message?
{
if (windows[window_id].focus_timer_id != 0U) {
@ -5166,6 +5359,18 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
// Destroy all status indicators.
for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) {
NOTIFYICONDATAW ndat;
ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW));
ndat.cbSize = sizeof(NOTIFYICONDATAW);
ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd;
ndat.uID = E->key;
ndat.uVersion = NOTIFYICON_VERSION;
Shell_NotifyIconW(NIM_DELETE, &ndat);
}
if (mouse_monitor) {
UnhookWindowsHookEx(mouse_monitor);
}

View file

@ -447,6 +447,13 @@ class DisplayServerWindows : public DisplayServer {
WNDPROC user_proc = nullptr;
struct IndicatorData {
Callable callback;
};
IndicatorID indicator_id_counter = 0;
HashMap<IndicatorID, IndicatorData> indicators;
void _send_window_event(const WindowData &wd, WindowEvent p_event);
void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
@ -655,6 +662,12 @@ public:
virtual void set_native_icon(const String &p_filename) override;
virtual void set_icon(const Ref<Image> &p_icon) override;
virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override;
virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override;
virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override;
virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override;
virtual void delete_status_indicator(IndicatorID p_id) override;
virtual void set_context(Context p_context) override;
static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error);

View file

@ -0,0 +1,135 @@
/**************************************************************************/
/* status_indicator.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "status_indicator.h"
void StatusIndicator::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return;
}
#endif
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
}
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
}
} break;
default:
break;
}
}
void StatusIndicator::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tooltip", "tooltip"), &StatusIndicator::set_tooltip);
ClassDB::bind_method(D_METHOD("get_tooltip"), &StatusIndicator::get_tooltip);
ClassDB::bind_method(D_METHOD("set_icon", "texture"), &StatusIndicator::set_icon);
ClassDB::bind_method(D_METHOD("get_icon"), &StatusIndicator::get_icon);
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &StatusIndicator::set_visible);
ClassDB::bind_method(D_METHOD("is_visible"), &StatusIndicator::is_visible);
ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::INT, "mouse_button"), PropertyInfo(Variant::VECTOR2I, "position")));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "get_tooltip");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_icon", "get_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
}
void StatusIndicator::_callback(MouseButton p_index, const Point2i &p_pos) {
emit_signal(SNAME("pressed"), p_index, p_pos);
}
void StatusIndicator::set_icon(const Ref<Image> &p_icon) {
ERR_MAIN_THREAD_GUARD;
icon = p_icon;
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->status_indicator_set_icon(iid, icon);
}
}
Ref<Image> StatusIndicator::get_icon() const {
return icon;
}
void StatusIndicator::set_tooltip(const String &p_tooltip) {
ERR_MAIN_THREAD_GUARD;
tooltip = p_tooltip;
if (iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->status_indicator_set_tooltip(iid, tooltip);
}
}
String StatusIndicator::get_tooltip() const {
return tooltip;
}
void StatusIndicator::set_visible(bool p_visible) {
ERR_MAIN_THREAD_GUARD;
if (visible == p_visible) {
return;
}
visible = p_visible;
if (!is_inside_tree()) {
return;
}
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return;
}
#endif
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_STATUS_INDICATOR)) {
if (visible && iid == DisplayServer::INVALID_INDICATOR_ID) {
iid = DisplayServer::get_singleton()->create_status_indicator(icon, tooltip, callable_mp(this, &StatusIndicator::_callback));
}
if (!visible && iid != DisplayServer::INVALID_INDICATOR_ID) {
DisplayServer::get_singleton()->delete_status_indicator(iid);
iid = DisplayServer::INVALID_INDICATOR_ID;
}
}
}
bool StatusIndicator::is_visible() const {
return visible;
}

View file

@ -0,0 +1,62 @@
/**************************************************************************/
/* status_indicator.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef STATUS_INDICATOR_H
#define STATUS_INDICATOR_H
#include "scene/main/node.h"
#include "servers/display_server.h"
class StatusIndicator : public Node {
GDCLASS(StatusIndicator, Node);
Ref<Image> icon;
String tooltip;
bool visible = true;
DisplayServer::IndicatorID iid = DisplayServer::INVALID_INDICATOR_ID;
protected:
void _notification(int p_what);
static void _bind_methods();
void _callback(MouseButton p_index, const Point2i &p_pos);
public:
void set_icon(const Ref<Image> &p_icon);
Ref<Image> get_icon() const;
void set_tooltip(const String &p_tooltip);
String get_tooltip() const;
void set_visible(bool p_visible);
bool is_visible() const;
};
#endif // STATUS_INDICATOR_H

View file

@ -136,6 +136,7 @@
#include "scene/main/multiplayer_api.h"
#include "scene/main/resource_preloader.h"
#include "scene/main/scene_tree.h"
#include "scene/main/status_indicator.h"
#include "scene/main/timer.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
@ -352,6 +353,8 @@ void register_scene_types() {
GDREGISTER_CLASS(ResourcePreloader);
GDREGISTER_CLASS(Window);
GDREGISTER_CLASS(StatusIndicator);
/* REGISTER GUI */
GDREGISTER_CLASS(ButtonGroup);

View file

@ -587,6 +587,27 @@ void DisplayServer::set_icon(const Ref<Image> &p_icon) {
WARN_PRINT("Icon not supported by this display server.");
}
DisplayServer::IndicatorID DisplayServer::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) {
WARN_PRINT("Status indicator not supported by this display server.");
return INVALID_INDICATOR_ID;
}
void DisplayServer::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) {
WARN_PRINT("Status indicator not supported by this display server.");
}
void DisplayServer::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) {
WARN_PRINT("Status indicator not supported by this display server.");
}
void DisplayServer::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) {
WARN_PRINT("Status indicator not supported by this display server.");
}
void DisplayServer::delete_status_indicator(IndicatorID p_id) {
WARN_PRINT("Status indicator not supported by this display server.");
}
int64_t DisplayServer::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
WARN_PRINT("Native handle not supported by this display server.");
return 0;
@ -825,6 +846,12 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_native_icon", "filename"), &DisplayServer::set_native_icon);
ClassDB::bind_method(D_METHOD("set_icon", "image"), &DisplayServer::set_icon);
ClassDB::bind_method(D_METHOD("create_status_indicator", "icon", "tooltip", "callback"), &DisplayServer::create_status_indicator);
ClassDB::bind_method(D_METHOD("status_indicator_set_icon", "id", "icon"), &DisplayServer::status_indicator_set_icon);
ClassDB::bind_method(D_METHOD("status_indicator_set_tooltip", "id", "tooltip"), &DisplayServer::status_indicator_set_tooltip);
ClassDB::bind_method(D_METHOD("status_indicator_set_callback", "id", "callback"), &DisplayServer::status_indicator_set_callback);
ClassDB::bind_method(D_METHOD("delete_status_indicator", "id"), &DisplayServer::delete_status_indicator);
ClassDB::bind_method(D_METHOD("tablet_get_driver_count"), &DisplayServer::tablet_get_driver_count);
ClassDB::bind_method(D_METHOD("tablet_get_driver_name", "idx"), &DisplayServer::tablet_get_driver_name);
ClassDB::bind_method(D_METHOD("tablet_get_current_driver"), &DisplayServer::tablet_get_current_driver);
@ -851,6 +878,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(FEATURE_TEXT_TO_SPEECH);
BIND_ENUM_CONSTANT(FEATURE_EXTEND_TO_TITLE);
BIND_ENUM_CONSTANT(FEATURE_SCREEN_CAPTURE);
BIND_ENUM_CONSTANT(FEATURE_STATUS_INDICATOR);
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@ -865,6 +893,7 @@ void DisplayServer::_bind_methods() {
BIND_CONSTANT(MAIN_WINDOW_ID);
BIND_CONSTANT(INVALID_WINDOW_ID);
BIND_CONSTANT(INVALID_INDICATOR_ID);
BIND_ENUM_CONSTANT(SCREEN_LANDSCAPE);
BIND_ENUM_CONSTANT(SCREEN_PORTRAIT);

View file

@ -125,6 +125,7 @@ public:
FEATURE_TEXT_TO_SPEECH,
FEATURE_EXTEND_TO_TITLE,
FEATURE_SCREEN_CAPTURE,
FEATURE_STATUS_INDICATOR,
};
virtual bool has_feature(Feature p_feature) const = 0;
@ -332,10 +333,12 @@ public:
virtual bool screen_is_kept_on() const;
enum {
MAIN_WINDOW_ID = 0,
INVALID_WINDOW_ID = -1
INVALID_WINDOW_ID = -1,
INVALID_INDICATOR_ID = -1
};
typedef int WindowID;
typedef int IndicatorID;
virtual Vector<DisplayServer::WindowID> get_window_list() const = 0;
@ -540,6 +543,12 @@ public:
virtual void set_native_icon(const String &p_filename);
virtual void set_icon(const Ref<Image> &p_icon);
virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback);
virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon);
virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip);
virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback);
virtual void delete_status_indicator(IndicatorID p_id);
enum Context {
CONTEXT_EDITOR,
CONTEXT_PROJECTMAN,