Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/theme/theme_owner.cpp
9898 views
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
39
void 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
56
Node *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
65
bool ThemeOwner::has_owner_node() const {
66
return bool(owner_control || owner_window);
67
}
68
69
void 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(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
73
owner_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
74
} else if (default_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
75
default_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
76
}
77
78
owner_context = p_context;
79
80
if (owner_context) {
81
owner_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
82
} else {
83
default_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
84
}
85
86
if (p_propagate) {
87
_owner_context_changed();
88
}
89
}
90
91
void 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
107
ThemeContext *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
117
void 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
136
void 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
154
void 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
202
void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, Vector<StringName> &r_result) 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 relevant themes.
216
if (p_theme_type == StringName() || p_theme_type == type_name || p_theme_type == type_variation) {
217
// We need one theme that can give us a valid dependency chain. It must be complete
218
// (i.e. variations can depend on other variations, but only within the same theme,
219
// and eventually the chain must lead to native types).
220
221
// First, look through themes owned by nodes in the tree.
222
Node *owner_node = get_owner_node();
223
224
while (owner_node) {
225
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
226
if (owner_theme.is_valid() && owner_theme->get_type_variation_base(type_variation) != StringName()) {
227
owner_theme->get_type_dependencies(type_name, type_variation, r_result);
228
return;
229
}
230
231
owner_node = _get_next_owner_node(owner_node);
232
}
233
234
// Second, check global contexts.
235
ThemeContext *global_context = _get_active_owner_context();
236
for (const Ref<Theme> &theme : global_context->get_themes()) {
237
if (theme.is_valid() && theme->get_type_variation_base(type_variation) != StringName()) {
238
theme->get_type_dependencies(type_name, type_variation, r_result);
239
return;
240
}
241
}
242
243
// If nothing was found, get the native dependencies for the current class.
244
ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_result);
245
return;
246
}
247
248
// Otherwise, get the native dependencies for the provided theme type.
249
ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_result);
250
}
251
252
Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) {
253
ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), Variant(), "At least one theme type must be specified.");
254
255
// First, look through each control or window node in the branch, until no valid parent can be found.
256
// Only nodes with a theme resource attached are considered.
257
Node *owner_node = get_owner_node();
258
259
while (owner_node) {
260
// For each theme resource check the theme types provided and see if p_name exists with any of them.
261
for (const StringName &E : p_theme_types) {
262
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
263
264
if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
265
return owner_theme->get_theme_item(p_data_type, p_name, E);
266
}
267
}
268
269
owner_node = _get_next_owner_node(owner_node);
270
}
271
272
// Second, check global themes from the appropriate context.
273
ThemeContext *global_context = _get_active_owner_context();
274
for (const Ref<Theme> &theme : global_context->get_themes()) {
275
if (theme.is_valid()) {
276
for (const StringName &E : p_theme_types) {
277
if (theme->has_theme_item(p_data_type, p_name, E)) {
278
return theme->get_theme_item(p_data_type, p_name, E);
279
}
280
}
281
}
282
}
283
284
// Finally, if no match exists, use any type to return the default/empty value.
285
return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName());
286
}
287
288
bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) {
289
ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), false, "At least one theme type must be specified.");
290
291
// First, look through each control or window node in the branch, until no valid parent can be found.
292
// Only nodes with a theme resource attached are considered.
293
Node *owner_node = get_owner_node();
294
295
while (owner_node) {
296
// For each theme resource check the theme types provided and see if p_name exists with any of them.
297
for (const StringName &E : p_theme_types) {
298
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
299
300
if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
301
return true;
302
}
303
}
304
305
owner_node = _get_next_owner_node(owner_node);
306
}
307
308
// Second, check global themes from the appropriate context.
309
ThemeContext *global_context = _get_active_owner_context();
310
for (const Ref<Theme> &theme : global_context->get_themes()) {
311
if (theme.is_valid()) {
312
for (const StringName &E : p_theme_types) {
313
if (theme->has_theme_item(p_data_type, p_name, E)) {
314
return true;
315
}
316
}
317
}
318
}
319
320
// Finally, if no match exists, return false.
321
return false;
322
}
323
324
float ThemeOwner::get_theme_default_base_scale() {
325
// First, look through each control or window node in the branch, until no valid parent can be found.
326
// Only nodes with a theme resource attached are considered.
327
// For each theme resource see if their assigned theme has the default value defined and valid.
328
Node *owner_node = get_owner_node();
329
330
while (owner_node) {
331
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
332
333
if (owner_theme.is_valid() && owner_theme->has_default_base_scale()) {
334
return owner_theme->get_default_base_scale();
335
}
336
337
owner_node = _get_next_owner_node(owner_node);
338
}
339
340
// Second, check global themes from the appropriate context.
341
ThemeContext *global_context = _get_active_owner_context();
342
for (const Ref<Theme> &theme : global_context->get_themes()) {
343
if (theme.is_valid()) {
344
if (theme->has_default_base_scale()) {
345
return theme->get_default_base_scale();
346
}
347
}
348
}
349
350
// Finally, if no match exists, return the universal default.
351
return ThemeDB::get_singleton()->get_fallback_base_scale();
352
}
353
354
Ref<Font> ThemeOwner::get_theme_default_font() {
355
// First, look through each control or window node in the branch, until no valid parent can be found.
356
// Only nodes with a theme resource attached are considered.
357
// For each theme resource see if their assigned theme has the default value defined and valid.
358
Node *owner_node = get_owner_node();
359
360
while (owner_node) {
361
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
362
363
if (owner_theme.is_valid() && owner_theme->has_default_font()) {
364
return owner_theme->get_default_font();
365
}
366
367
owner_node = _get_next_owner_node(owner_node);
368
}
369
370
// Second, check global themes from the appropriate context.
371
ThemeContext *global_context = _get_active_owner_context();
372
for (const Ref<Theme> &theme : global_context->get_themes()) {
373
if (theme.is_valid()) {
374
if (theme->has_default_font()) {
375
return theme->get_default_font();
376
}
377
}
378
}
379
380
// Finally, if no match exists, return the universal default.
381
return ThemeDB::get_singleton()->get_fallback_font();
382
}
383
384
int ThemeOwner::get_theme_default_font_size() {
385
// First, look through each control or window node in the branch, until no valid parent can be found.
386
// Only nodes with a theme resource attached are considered.
387
// For each theme resource see if their assigned theme has the default value defined and valid.
388
Node *owner_node = get_owner_node();
389
390
while (owner_node) {
391
Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
392
393
if (owner_theme.is_valid() && owner_theme->has_default_font_size()) {
394
return owner_theme->get_default_font_size();
395
}
396
397
owner_node = _get_next_owner_node(owner_node);
398
}
399
400
// Second, check global themes from the appropriate context.
401
ThemeContext *global_context = _get_active_owner_context();
402
for (const Ref<Theme> &theme : global_context->get_themes()) {
403
if (theme.is_valid()) {
404
if (theme->has_default_font_size()) {
405
return theme->get_default_font_size();
406
}
407
}
408
}
409
410
// Finally, if no match exists, return the universal default.
411
return ThemeDB::get_singleton()->get_fallback_font_size();
412
}
413
414
Ref<Theme> ThemeOwner::_get_owner_node_theme(Node *p_owner_node) const {
415
const Control *owner_c = Object::cast_to<Control>(p_owner_node);
416
if (owner_c) {
417
return owner_c->get_theme();
418
}
419
420
const Window *owner_w = Object::cast_to<Window>(p_owner_node);
421
if (owner_w) {
422
return owner_w->get_theme();
423
}
424
425
return Ref<Theme>();
426
}
427
428
Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
429
Node *parent = p_from_node->get_parent();
430
431
Control *parent_c = Object::cast_to<Control>(parent);
432
if (parent_c) {
433
return parent_c->get_theme_owner_node();
434
} else {
435
Window *parent_w = Object::cast_to<Window>(parent);
436
if (parent_w) {
437
return parent_w->get_theme_owner_node();
438
}
439
}
440
441
return nullptr;
442
}
443
444