1/**************************************************************************/
2/* theme_owner.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "theme_owner.h"
32
33#include "scene/gui/control.h"
34#include "scene/main/window.h"
35#include "scene/theme/theme_db.h"
36
37// Theme owner node.
38
39void ThemeOwner::set_owner_node(Node *p_node) {
40 owner_control = nullptr;
41 owner_window = nullptr;
42
43 Control *c = Object::cast_to<Control>(p_node);
44 if (c) {
45 owner_control = c;
46 return;
47 }
48
49 Window *w = Object::cast_to<Window>(p_node);
50 if (w) {
51 owner_window = w;
52 return;
53 }
54}
55
56Node *ThemeOwner::get_owner_node() const {
57 if (owner_control) {
58 return owner_control;
59 } else if (owner_window) {
60 return owner_window;
61 }
62 return nullptr;
63}
64
65bool ThemeOwner::has_owner_node() const {
66 return bool(owner_control || owner_window);
67}
68
69void ThemeOwner::set_owner_context(ThemeContext *p_context, bool p_propagate) {
70 ThemeContext *default_context = ThemeDB::get_singleton()->get_default_theme_context();
71
72 if (owner_context && owner_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
73 owner_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
74 } else if (default_context->is_connected("changed", callable_mp(this, &ThemeOwner::_owner_context_changed))) {
75 default_context->disconnect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
76 }
77
78 owner_context = p_context;
79
80 if (owner_context) {
81 owner_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
82 } else {
83 default_context->connect("changed", callable_mp(this, &ThemeOwner::_owner_context_changed));
84 }
85
86 if (p_propagate) {
87 _owner_context_changed();
88 }
89}
90
91void ThemeOwner::_owner_context_changed() {
92 if (!holder->is_inside_tree()) {
93 // We ignore theme changes outside of tree, because NOTIFICATION_ENTER_TREE covers everything.
94 return;
95 }
96
97 Control *c = Object::cast_to<Control>(holder);
98 Window *w = c == nullptr ? Object::cast_to<Window>(holder) : nullptr;
99
100 if (c) {
101 c->notification(Control::NOTIFICATION_THEME_CHANGED);
102 } else if (w) {
103 w->notification(Window::NOTIFICATION_THEME_CHANGED);
104 }
105}
106
107ThemeContext *ThemeOwner::_get_active_owner_context() const {
108 if (owner_context) {
109 return owner_context;
110 }
111
112 return ThemeDB::get_singleton()->get_default_theme_context();
113}
114
115// Theme propagation.
116
117void ThemeOwner::assign_theme_on_parented(Node *p_for_node) {
118 // We check if there are any themes affecting the parent. If that's the case
119 // its children also need to be affected.
120 // We don't notify here because `NOTIFICATION_THEME_CHANGED` will be handled
121 // a bit later by `NOTIFICATION_ENTER_TREE`.
122
123 Node *parent = p_for_node->get_parent();
124
125 Control *parent_c = Object::cast_to<Control>(parent);
126 if (parent_c && parent_c->has_theme_owner_node()) {
127 propagate_theme_changed(p_for_node, parent_c->get_theme_owner_node(), false, true);
128 } else {
129 Window *parent_w = Object::cast_to<Window>(parent);
130 if (parent_w && parent_w->has_theme_owner_node()) {
131 propagate_theme_changed(p_for_node, parent_w->get_theme_owner_node(), false, true);
132 }
133 }
134}
135
136void ThemeOwner::clear_theme_on_unparented(Node *p_for_node) {
137 // We check if there were any themes affecting the parent. If that's the case
138 // its children need were also affected and need to be updated.
139 // We don't notify because we're exiting the tree, and it's not important.
140
141 Node *parent = p_for_node->get_parent();
142
143 Control *parent_c = Object::cast_to<Control>(parent);
144 if (parent_c && parent_c->has_theme_owner_node()) {
145 propagate_theme_changed(p_for_node, nullptr, false, true);
146 } else {
147 Window *parent_w = Object::cast_to<Window>(parent);
148 if (parent_w && parent_w->has_theme_owner_node()) {
149 propagate_theme_changed(p_for_node, nullptr, false, true);
150 }
151 }
152}
153
154void ThemeOwner::propagate_theme_changed(Node *p_to_node, Node *p_owner_node, bool p_notify, bool p_assign) {
155 Control *c = Object::cast_to<Control>(p_to_node);
156 Window *w = c == nullptr ? Object::cast_to<Window>(p_to_node) : nullptr;
157
158 if (!c && !w) {
159 // Theme inheritance chains are broken by nodes that aren't Control or Window.
160 return;
161 }
162
163 bool assign = p_assign;
164 if (c) {
165 if (c != p_owner_node && c->get_theme().is_valid()) {
166 // Has a theme, so we don't want to change the theme owner,
167 // but we still want to propagate in case this child has theme items
168 // it inherits from the theme this node uses.
169 // See https://github.com/godotengine/godot/issues/62844.
170 assign = false;
171 }
172
173 if (assign) {
174 c->set_theme_owner_node(p_owner_node);
175 }
176
177 if (p_notify) {
178 c->notification(Control::NOTIFICATION_THEME_CHANGED);
179 }
180 } else if (w) {
181 if (w != p_owner_node && w->get_theme().is_valid()) {
182 // Same as above.
183 assign = false;
184 }
185
186 if (assign) {
187 w->set_theme_owner_node(p_owner_node);
188 }
189
190 if (p_notify) {
191 w->notification(Window::NOTIFICATION_THEME_CHANGED);
192 }
193 }
194
195 for (int i = 0; i < p_to_node->get_child_count(); i++) {
196 propagate_theme_changed(p_to_node->get_child(i), p_owner_node, p_notify, assign);
197 }
198}
199
200// Theme lookup.
201
202void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, List<StringName> *r_list) const {
203 const Control *for_c = Object::cast_to<Control>(p_for_node);
204 const Window *for_w = Object::cast_to<Window>(p_for_node);
205 ERR_FAIL_COND_MSG(!for_c && !for_w, "Only Control and Window nodes and derivatives can be polled for theming.");
206
207 StringName type_name = p_for_node->get_class_name();
208 StringName type_variation;
209 if (for_c) {
210 type_variation = for_c->get_theme_type_variation();
211 } else if (for_w) {
212 type_variation = for_w->get_theme_type_variation();
213 }
214
215 // If we are looking for dependencies of the current class (or a variation of it), check themes from the context.
216 if (p_theme_type == StringName() || p_theme_type == type_name || p_theme_type == type_variation) {
217 ThemeContext *global_context = _get_active_owner_context();
218 for (const Ref<Theme> &theme : global_context->get_themes()) {
219 if (theme.is_valid() && theme->get_type_variation_base(type_variation) != StringName()) {
220 theme->get_type_dependencies(type_name, type_variation, r_list);
221 return;
222 }
223 }
224
225 // If nothing was found, get the native dependencies for the current class.
226 ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_list);
227 return;
228 }
229
230 // Otherwise, get the native dependencies for the provided theme type.
231 ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_list);
232}
233
234Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
235 ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, Variant(), "At least one theme type must be specified.");
236
237 // First, look through each control or window node in the branch, until no valid parent can be found.
238 // Only nodes with a theme resource attached are considered.
239 Node *owner_node = get_owner_node();
240
241 while (owner_node) {
242 // For each theme resource check the theme types provided and see if p_name exists with any of them.
243 for (const StringName &E : p_theme_types) {
244 Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
245
246 if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
247 return owner_theme->get_theme_item(p_data_type, p_name, E);
248 }
249 }
250
251 owner_node = _get_next_owner_node(owner_node);
252 }
253
254 // Second, check global themes from the appropriate context.
255 ThemeContext *global_context = _get_active_owner_context();
256 for (const Ref<Theme> &theme : global_context->get_themes()) {
257 if (theme.is_valid()) {
258 for (const StringName &E : p_theme_types) {
259 if (theme->has_theme_item(p_data_type, p_name, E)) {
260 return theme->get_theme_item(p_data_type, p_name, E);
261 }
262 }
263 }
264 }
265
266 // Finally, if no match exists, use any type to return the default/empty value.
267 return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName());
268}
269
270bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
271 ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified.");
272
273 // First, look through each control or window node in the branch, until no valid parent can be found.
274 // Only nodes with a theme resource attached are considered.
275 Node *owner_node = get_owner_node();
276
277 while (owner_node) {
278 // For each theme resource check the theme types provided and see if p_name exists with any of them.
279 for (const StringName &E : p_theme_types) {
280 Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
281
282 if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
283 return true;
284 }
285 }
286
287 owner_node = _get_next_owner_node(owner_node);
288 }
289
290 // Second, check global themes from the appropriate context.
291 ThemeContext *global_context = _get_active_owner_context();
292 for (const Ref<Theme> &theme : global_context->get_themes()) {
293 if (theme.is_valid()) {
294 for (const StringName &E : p_theme_types) {
295 if (theme->has_theme_item(p_data_type, p_name, E)) {
296 return true;
297 }
298 }
299 }
300 }
301
302 // Finally, if no match exists, return false.
303 return false;
304}
305
306float ThemeOwner::get_theme_default_base_scale() {
307 // First, look through each control or window node in the branch, until no valid parent can be found.
308 // Only nodes with a theme resource attached are considered.
309 // For each theme resource see if their assigned theme has the default value defined and valid.
310 Node *owner_node = get_owner_node();
311
312 while (owner_node) {
313 Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
314
315 if (owner_theme.is_valid() && owner_theme->has_default_base_scale()) {
316 return owner_theme->get_default_base_scale();
317 }
318
319 owner_node = _get_next_owner_node(owner_node);
320 }
321
322 // Second, check global themes from the appropriate context.
323 ThemeContext *global_context = _get_active_owner_context();
324 for (const Ref<Theme> &theme : global_context->get_themes()) {
325 if (theme.is_valid()) {
326 if (theme->has_default_base_scale()) {
327 return theme->get_default_base_scale();
328 }
329 }
330 }
331
332 // Finally, if no match exists, return the universal default.
333 return ThemeDB::get_singleton()->get_fallback_base_scale();
334}
335
336Ref<Font> ThemeOwner::get_theme_default_font() {
337 // First, look through each control or window node in the branch, until no valid parent can be found.
338 // Only nodes with a theme resource attached are considered.
339 // For each theme resource see if their assigned theme has the default value defined and valid.
340 Node *owner_node = get_owner_node();
341
342 while (owner_node) {
343 Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
344
345 if (owner_theme.is_valid() && owner_theme->has_default_font()) {
346 return owner_theme->get_default_font();
347 }
348
349 owner_node = _get_next_owner_node(owner_node);
350 }
351
352 // Second, check global themes from the appropriate context.
353 ThemeContext *global_context = _get_active_owner_context();
354 for (const Ref<Theme> &theme : global_context->get_themes()) {
355 if (theme.is_valid()) {
356 if (theme->has_default_font()) {
357 return theme->get_default_font();
358 }
359 }
360 }
361
362 // Finally, if no match exists, return the universal default.
363 return ThemeDB::get_singleton()->get_fallback_font();
364}
365
366int ThemeOwner::get_theme_default_font_size() {
367 // First, look through each control or window node in the branch, until no valid parent can be found.
368 // Only nodes with a theme resource attached are considered.
369 // For each theme resource see if their assigned theme has the default value defined and valid.
370 Node *owner_node = get_owner_node();
371
372 while (owner_node) {
373 Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
374
375 if (owner_theme.is_valid() && owner_theme->has_default_font_size()) {
376 return owner_theme->get_default_font_size();
377 }
378
379 owner_node = _get_next_owner_node(owner_node);
380 }
381
382 // Second, check global themes from the appropriate context.
383 ThemeContext *global_context = _get_active_owner_context();
384 for (const Ref<Theme> &theme : global_context->get_themes()) {
385 if (theme.is_valid()) {
386 if (theme->has_default_font_size()) {
387 return theme->get_default_font_size();
388 }
389 }
390 }
391
392 // Finally, if no match exists, return the universal default.
393 return ThemeDB::get_singleton()->get_fallback_font_size();
394}
395
396Ref<Theme> ThemeOwner::_get_owner_node_theme(Node *p_owner_node) const {
397 const Control *owner_c = Object::cast_to<Control>(p_owner_node);
398 if (owner_c) {
399 return owner_c->get_theme();
400 }
401
402 const Window *owner_w = Object::cast_to<Window>(p_owner_node);
403 if (owner_w) {
404 return owner_w->get_theme();
405 }
406
407 return Ref<Theme>();
408}
409
410Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
411 Node *parent = p_from_node->get_parent();
412
413 Control *parent_c = Object::cast_to<Control>(parent);
414 if (parent_c) {
415 return parent_c->get_theme_owner_node();
416 } else {
417 Window *parent_w = Object::cast_to<Window>(parent);
418 if (parent_w) {
419 return parent_w->get_theme_owner_node();
420 }
421 }
422
423 return nullptr;
424}
425