Add taskbar progress and state support for Windows & macOS

Co-Authored-By: bruvzg <7645683+bruvzg@users.noreply.github.com>
Co-Authored-By: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com>
This commit is contained in:
SheepYhangCN 2025-05-18 06:05:56 +08:00
parent 56f3e2611d
commit 851faa08d8
16 changed files with 347 additions and 0 deletions

View file

@ -2467,6 +2467,27 @@
[b]Note:[/b] It's recommended to change this value using [member Window.size] instead.
</description>
</method>
<method name="window_set_taskbar_progress_state">
<return type="void" />
<param index="0" name="state" type="int" enum="DisplayServer.ProgressState" />
<param index="1" name="window_id" type="int" default="0" />
<description>
Sets the type and state of the progress bar on the taskbar/dock icon of the window specified by [param window_id]. See [enum ProgressState] for possible values and how each mode behaves.
[b]Note:[/b] This method is implemented only on Windows and macOS.
[b]Note:[/b] On macOS, the progress bar is displayed only for the main window.
</description>
</method>
<method name="window_set_taskbar_progress_value">
<return type="void" />
<param index="0" name="value" type="float" />
<param index="1" name="window_id" type="int" default="0" />
<description>
Creates a progress bar on the taskbar/dock icon of the window specified by [param window_id] if it does not exist, sets the progress of the icon.
[param value] acts as a relative percentage value, ranges from [code]0.0[/code] (lowest) to [code]1.0[/code] (highest).
[b]Note:[/b] This method is implemented only on Windows and macOS.
[b]Note:[/b] On macOS, the progress bar is displayed only for the main window.
</description>
</method>
<method name="window_set_title">
<return type="void" />
<param index="0" name="title" type="String" />
@ -3125,6 +3146,24 @@
[b]On Linux (Wayland):[/b] Equivalent to [constant WINDOW_MODE_FULLSCREEN].
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="PROGRESS_STATE_NOPROGRESS" value="0" enum="ProgressState">
Stops displaying progress and returns the button to its normal state.
</constant>
<constant name="PROGRESS_STATE_INDETERMINATE" value="1" enum="ProgressState">
The progress indicator shows an indeterminate progress.
On Windows, the progress indicator does not grow in size, but cycles repeatedly along the length of the taskbar button by default.
</constant>
<constant name="PROGRESS_STATE_NORMAL" value="2" enum="ProgressState">
The progress indicator shows progress normally.
</constant>
<constant name="PROGRESS_STATE_ERROR" value="3" enum="ProgressState">
The progress indicator shows that an error has occurred.
On Windows, the progress indicator turns red by default to show that an error has occurred in one of the windows that is broadcasting progress.
</constant>
<constant name="PROGRESS_STATE_PAUSED" value="4" enum="ProgressState">
The progress indicator shows it was paused.
On Windows, the progress indicator turns yellow by default to show that progress is currently stopped in one of the windows but can be resumed by the user.
</constant>
<constant name="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags">
The window can't be resized by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows.
</constant>

View file

@ -542,6 +542,23 @@
Sets layout direction and text writing direction. Right-to-left layouts are necessary for certain languages (e.g. Arabic and Hebrew).
</description>
</method>
<method name="set_taskbar_progress_state">
<return type="void" />
<param index="0" name="state" type="int" enum="DisplayServer.ProgressState" />
<description>
Sets the type and state of the progress bar on the taskbar/dock icon of the [Window]. See [enum DisplayServer.ProgressState] for possible values and how each mode behaves.
[b]Note:[/b] This method is implemented only on Windows and macOS.
</description>
</method>
<method name="set_taskbar_progress_value">
<return type="void" />
<param index="0" name="value" type="float" />
<description>
Creates a progress bar on the taskbar/dock icon of the [Window] if it does not exist, sets the progress of the icon.
[param value] acts as a relative percentage value, ranges from [code]0.0[/code] (lowest) to [code]1.0[/code] (highest).
[b]Note:[/b] This method is implemented only on Windows and macOS.
</description>
</method>
<method name="set_unparent_when_invisible">
<return type="void" />
<param index="0" name="unparent" type="bool" />

View file

@ -28,6 +28,7 @@ files = [
"rendering_context_driver_vulkan_macos.mm",
"gl_manager_macos_angle.mm",
"gl_manager_macos_legacy.mm",
"godot_progress_view.mm",
]
if env.editor_build:

View file

@ -197,6 +197,8 @@ public:
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_value(float p_value, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;

View file

@ -705,6 +705,14 @@ void DisplayServerEmbedded::window_request_attention(WindowID p_window) {
// Not supported
}
void DisplayServerEmbedded::window_set_taskbar_progress_value(float p_value, WindowID p_window) {
// Not supported.
}
void DisplayServerEmbedded::window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window) {
// Not supported.
}
void DisplayServerEmbedded::window_move_to_foreground(WindowID p_window) {
// Not supported
}

View file

@ -64,6 +64,7 @@
@class GodotContentView;
@class GodotWindowDelegate;
@class GodotButtonView;
@class GodotProgressView;
#ifdef TOOLS_ENABLED
@class GodotEmbeddedView;
@class CALayerHost;
@ -154,6 +155,8 @@ public:
List<WindowID> popup_list;
uint64_t time_since_popup = 0;
GodotProgressView *dock_progress = nullptr;
private:
#if defined(GLES3_ENABLED)
GLManagerLegacy_MacOS *gl_manager_legacy = nullptr;
@ -386,6 +389,8 @@ public:
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_value(float p_value, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;

View file

@ -38,6 +38,7 @@
#import "godot_menu_delegate.h"
#import "godot_menu_item.h"
#import "godot_open_save_delegate.h"
#import "godot_progress_view.h"
#import "godot_status_item.h"
#import "godot_window.h"
#import "godot_window_delegate.h"
@ -2715,6 +2716,28 @@ void DisplayServerMacOS::window_request_attention(WindowID p_window) {
[NSApp requestUserAttention:NSCriticalRequest];
}
void DisplayServerMacOS::window_set_taskbar_progress_value(float p_value, WindowID p_window) {
ERR_FAIL_COND(p_window != MAIN_WINDOW_ID);
if (!dock_progress) {
dock_progress = [[GodotProgressView alloc] init];
[NSApp.dockTile setContentView:dock_progress];
}
[dock_progress setValue:p_value];
}
void DisplayServerMacOS::window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window) {
ERR_FAIL_COND(p_window != MAIN_WINDOW_ID);
if (!dock_progress) {
dock_progress = [[GodotProgressView alloc] init];
[NSApp.dockTile setContentView:dock_progress];
}
[dock_progress setState:p_state];
}
void DisplayServerMacOS::window_move_to_foreground(WindowID p_window) {
_THREAD_SAFE_METHOD_
@ -3260,6 +3283,11 @@ void DisplayServerMacOS::_process_events(bool p_pump) {
}
}
if (dock_progress) {
dock_progress.needsDisplay = true;
[NSApp.dockTile display];
}
_THREAD_SAFE_UNLOCK_
}

View file

@ -0,0 +1,45 @@
/**************************************************************************/
/* godot_progress_view.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. */
/**************************************************************************/
#pragma once
#include "display_server_macos_base.h"
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@interface GodotProgressView : NSView {
DisplayServer::ProgressState pr_state;
float pr_value;
float pr_offset;
}
- (void)setValue:(float)value;
- (void)setState:(DisplayServer::ProgressState)state;
@end

View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* godot_progress_view.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. */
/**************************************************************************/
#import "godot_progress_view.h"
@implementation GodotProgressView
- (id)init {
self = [super init];
pr_state = DisplayServer::PROGRESS_STATE_NOPROGRESS;
pr_value = 0.f;
pr_offset = 0.f;
return self;
}
- (void)setValue:(float)value {
pr_value = value;
}
- (void)setState:(DisplayServer::ProgressState)state {
pr_state = state;
}
- (void)drawRect:(NSRect)dirtyRect {
// Icon draw.
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[[NSApp applicationIconImage] drawInRect:self.bounds];
if (pr_state == DisplayServer::PROGRESS_STATE_NOPROGRESS) {
return;
}
// Border draw.
NSRect rect = NSMakeRect(1.f, 1.f, self.bounds.size.width - 2.f, 16.f);
NSBezierPath *bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:8.f yRadius:8.f];
[bezier_path setLineWidth:2.0];
[[NSColor grayColor] set];
[bezier_path stroke];
// Fill clip path.
rect = NSMakeRect(2.f, 2.f, self.bounds.size.width - 4.f, 14.f);
bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:7.f yRadius:7.f];
[bezier_path setLineWidth:1.0];
[bezier_path addClip];
// Fill draw.
if (pr_state == DisplayServer::PROGRESS_STATE_INDETERMINATE) {
rect.size.width /= 5.0;
pr_offset += rect.size.width / 10.0;
if (pr_offset > self.bounds.size.width - rect.size.width) {
pr_offset = 0.f;
}
rect.origin.x += pr_offset;
} else {
rect.size.width = Math::floor(rect.size.width * pr_value);
}
if (pr_state == DisplayServer::PROGRESS_STATE_ERROR) {
[[NSColor colorWithSRGBRed:1.0 green:0.2 blue:0.2 alpha:1.0] set];
} else if (pr_state == DisplayServer::PROGRESS_STATE_PAUSED) {
[[NSColor colorWithSRGBRed:1.0 green:1.0 blue:0.2 alpha:1.0] set];
} else {
[[NSColor colorWithSRGBRed:0.2 green:0.6 blue:1.0 alpha:1.0] set];
}
NSRectFill(rect);
}
@end

View file

@ -2820,6 +2820,69 @@ void DisplayServerWindows::window_request_attention(WindowID p_window) {
FlashWindowEx(&info);
}
void DisplayServerWindows::window_set_taskbar_progress_value(float p_value, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
wd.progress_value = p_value;
if (wd.progress_state == PROGRESS_STATE_NOPROGRESS) {
return;
}
if (taskbar == nullptr) {
if (CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_ITaskbarList, (void **)&taskbar) != S_OK) {
taskbar = nullptr;
return;
} else {
taskbar->HrInit();
}
}
taskbar->SetProgressValue(wd.hWnd, Math::round(p_value * 100000), 100000);
}
void DisplayServerWindows::window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
wd.progress_state = p_state;
if (taskbar == nullptr) {
if (CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER, IID_ITaskbarList, (void **)&taskbar) != S_OK) {
taskbar = nullptr;
return;
} else {
taskbar->HrInit();
}
}
TBPFLAG tbpf = TBPF_NOPROGRESS;
switch (p_state) {
case PROGRESS_STATE_NOPROGRESS:
tbpf = TBPF_NOPROGRESS;
break;
case PROGRESS_STATE_INDETERMINATE:
tbpf = TBPF_INDETERMINATE;
break;
case PROGRESS_STATE_ERROR:
tbpf = TBPF_ERROR;
break;
case PROGRESS_STATE_PAUSED:
tbpf = TBPF_PAUSED;
break;
case PROGRESS_STATE_NORMAL:
tbpf = TBPF_NORMAL;
break;
default:
break;
}
taskbar->SetProgressState(wd.hWnd, tbpf);
if (p_state != PROGRESS_STATE_INDETERMINATE) {
taskbar->SetProgressValue(wd.hWnd, Math::round(wd.progress_value * 100000), 100000);
}
}
void DisplayServerWindows::window_move_to_foreground(WindowID p_window) {
_THREAD_SAFE_METHOD_

View file

@ -66,6 +66,7 @@
#include <cstdio>
#define WIN32_LEAN_AND_MEAN
#include <shobjidl.h>
#include <windows.h>
#include <windowsx.h>
@ -290,12 +291,15 @@ class DisplayServerWindows : public DisplayServer {
TTS_Windows *tts = nullptr;
NativeMenuWindows *native_menu = nullptr;
ITaskbarList3 *taskbar = nullptr;
struct WindowData {
HWND hWnd;
WindowID id;
Vector<Vector2> mpath;
ProgressState progress_state = PROGRESS_STATE_NOPROGRESS;
float progress_value = 0.0;
bool create_completed = false;
bool pre_fs_valid = false;
@ -662,6 +666,8 @@ public:
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_value(float p_value, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;

View file

@ -589,6 +589,20 @@ void Window::request_attention() {
}
}
void Window::set_taskbar_progress_value(float p_value) {
ERR_MAIN_THREAD_GUARD;
if (window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_taskbar_progress_value(p_value, window_id);
}
}
void Window::set_taskbar_progress_state(DisplayServer::ProgressState p_state) {
ERR_MAIN_THREAD_GUARD;
if (window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_taskbar_progress_state(p_state);
}
}
#ifndef DISABLE_DEPRECATED
void Window::move_to_foreground() {
WARN_DEPRECATED_MSG(R"*(The "move_to_foreground()" method is deprecated, use "grab_focus()" instead.)*");
@ -3238,6 +3252,8 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("request_attention"), &Window::request_attention);
ClassDB::bind_method(D_METHOD("set_taskbar_progress_value", "value"), &Window::set_taskbar_progress_value);
ClassDB::bind_method(D_METHOD("set_taskbar_progress_state", "state"), &Window::set_taskbar_progress_state);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("move_to_foreground"), &Window::move_to_foreground);
#endif // DISABLE_DEPRECATED

View file

@ -338,6 +338,8 @@ public:
bool is_maximize_allowed() const;
void request_attention();
void set_taskbar_progress_value(float p_value);
void set_taskbar_progress_state(DisplayServer::ProgressState p_state);
#ifndef DISABLE_DEPRECATED
void move_to_foreground();
#endif // DISABLE_DEPRECATED

View file

@ -1459,6 +1459,8 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("window_get_safe_title_margins", "window_id"), &DisplayServer::window_get_safe_title_margins, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_request_attention", "window_id"), &DisplayServer::window_request_attention, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_taskbar_progress_value", "value", "window_id"), &DisplayServer::window_set_taskbar_progress_value, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_taskbar_progress_state", "state", "window_id"), &DisplayServer::window_set_taskbar_progress_state, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_move_to_foreground", "window_id"), &DisplayServer::window_move_to_foreground, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_is_focused", "window_id"), &DisplayServer::window_is_focused, DEFVAL(MAIN_WINDOW_ID));
@ -1831,6 +1833,12 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_MODE_FULLSCREEN);
BIND_ENUM_CONSTANT(WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
BIND_ENUM_CONSTANT(PROGRESS_STATE_NOPROGRESS);
BIND_ENUM_CONSTANT(PROGRESS_STATE_INDETERMINATE);
BIND_ENUM_CONSTANT(PROGRESS_STATE_NORMAL);
BIND_ENUM_CONSTANT(PROGRESS_STATE_ERROR);
BIND_ENUM_CONSTANT(PROGRESS_STATE_PAUSED);
BIND_ENUM_CONSTANT(WINDOW_FLAG_RESIZE_DISABLED);
BIND_ENUM_CONSTANT(WINDOW_FLAG_BORDERLESS);
BIND_ENUM_CONSTANT(WINDOW_FLAG_ALWAYS_ON_TOP);

View file

@ -76,6 +76,14 @@ public:
WINDOW_MODE_EXCLUSIVE_FULLSCREEN,
};
enum ProgressState {
PROGRESS_STATE_NOPROGRESS,
PROGRESS_STATE_INDETERMINATE,
PROGRESS_STATE_NORMAL,
PROGRESS_STATE_ERROR,
PROGRESS_STATE_PAUSED,
};
// Keep the VSyncMode enum values in sync with the `display/window/vsync/vsync_mode`
// project setting hint.
enum VSyncMode {
@ -518,6 +526,8 @@ public:
virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const = 0;
virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) = 0;
virtual void window_set_taskbar_progress_value(float p_value, WindowID p_window = MAIN_WINDOW_ID) {}
virtual void window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window = MAIN_WINDOW_ID) {}
virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) = 0;
virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const = 0;
@ -1027,3 +1037,4 @@ VARIANT_ENUM_CAST(DisplayServer::CursorShape)
VARIANT_ENUM_CAST(DisplayServer::VSyncMode)
VARIANT_ENUM_CAST(DisplayServer::TTSUtteranceEvent)
VARIANT_ENUM_CAST(DisplayServer::FileDialogMode)
VARIANT_ENUM_CAST(DisplayServer::ProgressState)

View file

@ -142,6 +142,8 @@ public:
bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override { return false; }
void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override {}
void window_set_taskbar_progress_value(float p_value, WindowID p_window = MAIN_WINDOW_ID) override {}
void window_set_taskbar_progress_state(ProgressState p_state, WindowID p_window = MAIN_WINDOW_ID) override {}
void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override {}
bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override { return true; }