[Window] Add unfiltered input handler signal for custom decorations.

This commit is contained in:
Pāvels Nadtočajevs
2025-10-05 16:53:58 +03:00
parent cb7cd815ee
commit 101fbbbd1f
5 changed files with 96 additions and 47 deletions

View File

@ -691,6 +691,9 @@
[b]Note:[/b] On Windows, the portion of a window that lies outside the region is not drawn, while on Linux (X11) and macOS it is.
[b]Note:[/b] This property is implemented on Linux (X11), macOS and Windows.
</member>
<member name="nonclient_area" type="Rect2i" setter="set_nonclient_area" getter="get_nonclient_area" default="Rect2i(0, 0, 0, 0)">
If set, defines the window's custom decoration area which will receive mouse input, even if normal input to the window is blocked (such as when it has an exclusive child opened). See also [signal nonclient_window_input].
</member>
<member name="popup_window" type="bool" setter="set_flag" getter="get_flag" default="false">
If [code]true[/code], the [Window] will be considered a popup. Popups are sub-windows that don't show as separate windows in system's window manager's window list and will send close request when anything is clicked outside of them (unless [member exclusive] is enabled).
</member>
@ -803,6 +806,12 @@
Emitted when the mouse cursor leaves the [Window]'s visible area, that is not occluded behind other [Control]s or windows, provided its [member Viewport.gui_disable_input] is [code]false[/code] and regardless if it's currently focused or not.
</description>
</signal>
<signal name="nonclient_window_input">
<param index="0" name="event" type="InputEvent" />
<description>
Emitted when the mouse event is received by the custom decoration area defined by [member nonclient_area], and normal input to the window is blocked (such as when it has an exclusive child opened). [param event]'s position is in the embedder's coordinate system.
</description>
</signal>
<signal name="theme_changed">
<description>
Emitted when the [constant NOTIFICATION_THEME_CHANGED] notification is sent.

View File

@ -89,57 +89,71 @@ Control *EditorTitleBar::get_center_control() const {
}
void EditorTitleBar::_notification(int p_what) {
if (!center_control || p_what != NOTIFICATION_SORT_CHILDREN) {
return;
}
Control *prev = nullptr;
Control *base = nullptr;
Control *next = nullptr;
bool rtl = is_layout_rtl();
int start;
int end;
int delta;
if (rtl) {
start = get_child_count() - 1;
end = -1;
delta = -1;
} else {
start = 0;
end = get_child_count();
delta = +1;
}
for (int i = start; i != end; i += delta) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {
SceneTree::get_singleton()->get_root()->disconnect(SceneStringName(nonclient_window_input), callable_mp(this, &EditorTitleBar::gui_input));
get_window()->set_nonclient_area(Rect2i());
} break;
case NOTIFICATION_ENTER_TREE: {
SceneTree::get_singleton()->get_root()->connect(SceneStringName(nonclient_window_input), callable_mp(this, &EditorTitleBar::gui_input));
[[fallthrough]];
}
if (base) {
next = c;
break;
}
if (c != center_control) {
prev = c;
continue;
}
base = c;
}
if (base && prev && next) {
Size2i title_size = get_size();
Size2i c_size = base->get_combined_minimum_size();
case NOTIFICATION_RESIZED: {
get_window()->set_nonclient_area(get_global_transform().xform(Rect2i(get_position(), get_size())));
} break;
case NOTIFICATION_SORT_CHILDREN: {
if (!center_control) {
break;
}
Control *prev = nullptr;
Control *base = nullptr;
Control *next = nullptr;
int min_offset = prev->get_position().x + prev->get_combined_minimum_size().x;
int max_offset = next->get_position().x + next->get_size().x - next->get_combined_minimum_size().x - c_size.x;
bool rtl = is_layout_rtl();
int offset = (title_size.width - c_size.width) / 2;
offset = CLAMP(offset, min_offset, max_offset);
int start;
int end;
int delta;
if (rtl) {
start = get_child_count() - 1;
end = -1;
delta = -1;
} else {
start = 0;
end = get_child_count();
delta = +1;
}
fit_child_in_rect(prev, Rect2i(prev->get_position().x, 0, offset - prev->get_position().x, title_size.height));
fit_child_in_rect(base, Rect2i(offset, 0, c_size.width, title_size.height));
fit_child_in_rect(next, Rect2i(offset + c_size.width, 0, next->get_position().x + next->get_size().x - (offset + c_size.width), title_size.height));
for (int i = start; i != end; i += delta) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
if (base) {
next = c;
break;
}
if (c != center_control) {
prev = c;
continue;
}
base = c;
}
if (base && prev && next) {
Size2i title_size = get_size();
Size2i c_size = base->get_combined_minimum_size();
int min_offset = prev->get_position().x + prev->get_combined_minimum_size().x;
int max_offset = next->get_position().x + next->get_size().x - next->get_combined_minimum_size().x - c_size.x;
int offset = (title_size.width - c_size.width) / 2;
offset = CLAMP(offset, min_offset, max_offset);
fit_child_in_rect(prev, Rect2i(prev->get_position().x, 0, offset - prev->get_position().x, title_size.height));
fit_child_in_rect(base, Rect2i(offset, 0, c_size.width, title_size.height));
fit_child_in_rect(next, Rect2i(offset + c_size.width, 0, next->get_position().x + next->get_size().x - (offset + c_size.width), title_size.height));
}
} break;
}
}

View File

@ -1726,6 +1726,15 @@ real_t Window::get_content_scale_factor() const {
return content_scale_factor;
}
void Window::set_nonclient_area(const Rect2i &p_rect) {
ERR_MAIN_THREAD_GUARD;
nonclient_area = p_rect;
}
Rect2i Window::get_nonclient_area() const {
return nonclient_area;
}
DisplayServer::WindowID Window::get_window_id() const {
ERR_READ_THREAD_GUARD_V(DisplayServer::INVALID_WINDOW_ID);
if (get_embedder()) {
@ -1816,6 +1825,12 @@ void Window::_window_input(const Ref<InputEvent> &p_ev) {
ERR_MAIN_THREAD_GUARD;
if (exclusive_child != nullptr) {
if (nonclient_area.has_area() && is_inside_tree()) {
Ref<InputEventMouse> me = p_ev;
if (me.is_valid() && nonclient_area.has_point(me->get_position())) {
emit_signal(SceneStringName(nonclient_window_input), p_ev);
}
}
if (!is_embedding_subwindows()) { // Not embedding, no need for event.
return;
}
@ -3227,6 +3242,9 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch);
ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch);
ClassDB::bind_method(D_METHOD("set_nonclient_area", "area"), &Window::set_nonclient_area);
ClassDB::bind_method(D_METHOD("get_nonclient_area"), &Window::get_nonclient_area);
ClassDB::bind_method(D_METHOD("set_keep_title_visible", "title_visible"), &Window::set_keep_title_visible);
ClassDB::bind_method(D_METHOD("get_keep_title_visible"), &Window::get_keep_title_visible);
@ -3331,6 +3349,7 @@ void Window::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "position", PROPERTY_HINT_NONE, "suffix:px"), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), "set_current_screen", "get_current_screen");
ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "nonclient_area", PROPERTY_HINT_NONE, ""), "set_nonclient_area", "get_nonclient_area");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "mouse_passthrough_polygon"), "set_mouse_passthrough_polygon", "get_mouse_passthrough_polygon");
@ -3380,6 +3399,7 @@ void Window::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation");
ADD_SIGNAL(MethodInfo("window_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
ADD_SIGNAL(MethodInfo("nonclient_window_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent")));
ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files")));
ADD_SIGNAL(MethodInfo("mouse_entered"));
ADD_SIGNAL(MethodInfo("mouse_exited"));

View File

@ -171,6 +171,8 @@ private:
Size2i max_size_used;
Rect2i nonclient_area;
Size2i _clamp_limit_size(const Size2i &p_limit_size);
Size2i _clamp_window_size(const Size2i &p_size);
void _validate_limit_size();
@ -391,6 +393,9 @@ public:
void set_content_scale_factor(real_t p_factor);
real_t get_content_scale_factor() const;
void set_nonclient_area(const Rect2i &p_rect);
Rect2i get_nonclient_area() const;
void set_mouse_passthrough_polygon(const Vector<Vector2> &p_region);
Vector<Vector2> get_mouse_passthrough_polygon() const;

View File

@ -53,6 +53,7 @@ public:
const StringName input_event = "input_event";
const StringName gui_input = "gui_input";
const StringName window_input = "window_input";
const StringName nonclient_window_input = "nonclient_window_input";
const StringName tree_entered = "tree_entered";
const StringName tree_exiting = "tree_exiting";