Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/theme/theme_owner.cpp
20873 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
ERR_FAIL_COND(p_node && !Object::cast_to<Control>(p_node) && !Object::cast_to<Window>(p_node));
41
owner_node = p_node;
42
}
43
44
void ThemeOwner::set_owner_context(ThemeContext *p_context, bool p_propagate) {
45
ThemeContext *default_context = ThemeDB::get_singleton()->get_default_theme_context();
46
47
if (owner_context && owner_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
48
owner_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
49
} else if (default_context->is_connected(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed))) {
50
default_context->disconnect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
51
}
52
53
owner_context = p_context;
54
55
if (owner_context) {
56
owner_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
57
} else {
58
default_context->connect(CoreStringName(changed), callable_mp(this, &ThemeOwner::_owner_context_changed));
59
}
60
61
if (p_propagate) {
62
_owner_context_changed();
63
}
64
}
65
66
void ThemeOwner::_owner_context_changed() {
67
if (!holder->is_inside_tree()) {
68
// We ignore theme changes outside of tree, because NOTIFICATION_ENTER_TREE covers everything.
69
return;
70
}
71
72
Control *c = Object::cast_to<Control>(holder);
73
Window *w = c == nullptr ? Object::cast_to<Window>(holder) : nullptr;
74
75
if (c) {
76
c->notification(Control::NOTIFICATION_THEME_CHANGED);
77
} else if (w) {
78
w->notification(Window::NOTIFICATION_THEME_CHANGED);
79
}
80
}
81
82
ThemeContext *ThemeOwner::_get_active_owner_context() const {
83
if (owner_context) {
84
return owner_context;
85
}
86
87
return ThemeDB::get_singleton()->get_default_theme_context();
88
}
89
90
// Theme propagation.
91
92
void ThemeOwner::assign_theme_on_parented(Node *p_for_node) {
93
// We check if there are any themes affecting the parent. If that's the case
94
// its children also need to be affected.
95
// We don't notify here because `NOTIFICATION_THEME_CHANGED` will be handled
96
// a bit later by `NOTIFICATION_ENTER_TREE`.
97
98
Node *parent = p_for_node->get_parent();
99
100
Control *parent_c = Object::cast_to<Control>(parent);
101
if (parent_c && parent_c->has_theme_owner_node()) {
102
propagate_theme_changed(p_for_node, parent_c->get_theme_owner_node(), false, true);
103
} else {
104
Window *parent_w = Object::cast_to<Window>(parent);
105
if (parent_w && parent_w->has_theme_owner_node()) {
106
propagate_theme_changed(p_for_node, parent_w->get_theme_owner_node(), false, true);
107
}
108
}
109
}
110
111
void ThemeOwner::clear_theme_on_unparented(Node *p_for_node) {
112
// We check if there were any themes affecting the parent. If that's the case
113
// its children need were also affected and need to be updated.
114
// We don't notify because we're exiting the tree, and it's not important.
115
116
Node *parent = p_for_node->get_parent();
117
118
Control *parent_c = Object::cast_to<Control>(parent);
119
if (parent_c && parent_c->has_theme_owner_node()) {
120
propagate_theme_changed(p_for_node, nullptr, false, true);
121
} else {
122
Window *parent_w = Object::cast_to<Window>(parent);
123
if (parent_w && parent_w->has_theme_owner_node()) {
124
propagate_theme_changed(p_for_node, nullptr, false, true);
125
}
126
}
127
}
128
129
void ThemeOwner::propagate_theme_changed(Node *p_to_node, Node *p_owner_node, bool p_notify, bool p_assign) {
130
Control *c = Object::cast_to<Control>(p_to_node);
131
Window *w = c == nullptr ? Object::cast_to<Window>(p_to_node) : nullptr;
132
133
if (!c && !w) {
134
// Theme inheritance chains are broken by nodes that aren't Control or Window.
135
return;
136
}
137
138
bool assign = p_assign;
139
if (c) {
140
if (c != p_owner_node && c->get_theme().is_valid()) {
141
// Has a theme, so we don't want to change the theme owner,
142
// but we still want to propagate in case this child has theme items
143
// it inherits from the theme this node uses.
144
// See https://github.com/godotengine/godot/issues/62844.
145
assign = false;
146
}
147
148
if (assign) {
149
c->set_theme_owner_node(p_owner_node);
150
}
151
152
if (p_notify) {
153
c->notification(Control::NOTIFICATION_THEME_CHANGED);
154
}
155
} else if (w) {
156
if (w != p_owner_node && w->get_theme().is_valid()) {
157
// Same as above.
158
assign = false;
159
}
160
161
if (assign) {
162
w->set_theme_owner_node(p_owner_node);
163
}
164
165
if (p_notify) {
166
w->notification(Window::NOTIFICATION_THEME_CHANGED);
167
}
168
}
169
170
for (int i = 0; i < p_to_node->get_child_count(); i++) {
171
propagate_theme_changed(p_to_node->get_child(i), p_owner_node, p_notify, assign);
172
}
173
}
174
175
// Theme lookup.
176
177
void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, Vector<StringName> &r_result) const {
178
const Control *for_c = Object::cast_to<Control>(p_for_node);
179
const Window *for_w = Object::cast_to<Window>(p_for_node);
180
ERR_FAIL_COND_MSG(!for_c && !for_w, "Only Control and Window nodes and derivatives can be polled for theming.");
181
182
StringName type_name = p_for_node->get_class_name();
183
StringName type_variation;
184
if (for_c) {
185
type_variation = for_c->get_theme_type_variation();
186
} else if (for_w) {
187
type_variation = for_w->get_theme_type_variation();
188
}
189
190
// If we are looking for dependencies of the current class (or a variation of it), check relevant themes.
191
if (p_theme_type == StringName() || p_theme_type == type_name || p_theme_type == type_variation) {
192
// We need one theme that can give us a valid dependency chain. It must be complete
193
// (i.e. variations can depend on other variations, but only within the same theme,
194
// and eventually the chain must lead to native types).
195
196
// First, look through themes owned by nodes in the tree.
197
Node *current_owner = owner_node;
198
199
while (current_owner) {
200
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
201
if (owner_theme.is_valid() && owner_theme->get_type_variation_base(type_variation) != StringName()) {
202
owner_theme->get_type_dependencies(type_name, type_variation, r_result);
203
return;
204
}
205
206
current_owner = _get_next_owner_node(current_owner);
207
}
208
209
// Second, check global contexts.
210
ThemeContext *global_context = _get_active_owner_context();
211
for (const Ref<Theme> &theme : global_context->get_themes()) {
212
if (theme.is_valid() && theme->get_type_variation_base(type_variation) != StringName()) {
213
theme->get_type_dependencies(type_name, type_variation, r_result);
214
return;
215
}
216
}
217
218
// If nothing was found, get the native dependencies for the current class.
219
ThemeDB::get_singleton()->get_native_type_dependencies(type_name, r_result);
220
return;
221
}
222
223
// Otherwise, get the native dependencies for the provided theme type.
224
ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_result);
225
}
226
227
Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) {
228
ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), Variant(), "At least one theme type must be specified.");
229
230
// First, look through each control or window node in the branch, until no valid parent can be found.
231
// Only nodes with a theme resource attached are considered.
232
Node *current_owner = owner_node;
233
234
while (current_owner) {
235
// For each theme resource check the theme types provided and see if p_name exists with any of them.
236
for (const StringName &E : p_theme_types) {
237
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
238
239
if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
240
return owner_theme->get_theme_item(p_data_type, p_name, E);
241
}
242
}
243
244
current_owner = _get_next_owner_node(current_owner);
245
}
246
247
// Second, check global themes from the appropriate context.
248
ThemeContext *global_context = _get_active_owner_context();
249
for (const Ref<Theme> &theme : global_context->get_themes()) {
250
if (theme.is_valid()) {
251
for (const StringName &E : p_theme_types) {
252
if (theme->has_theme_item(p_data_type, p_name, E)) {
253
return theme->get_theme_item(p_data_type, p_name, E);
254
}
255
}
256
}
257
}
258
259
// Finally, if no match exists, use any type to return the default/empty value.
260
return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName());
261
}
262
263
bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const Vector<StringName> &p_theme_types) {
264
ERR_FAIL_COND_V_MSG(p_theme_types.is_empty(), false, "At least one theme type must be specified.");
265
266
// First, look through each control or window node in the branch, until no valid parent can be found.
267
// Only nodes with a theme resource attached are considered.
268
Node *current_owner = owner_node;
269
270
while (current_owner) {
271
// For each theme resource check the theme types provided and see if p_name exists with any of them.
272
for (const StringName &E : p_theme_types) {
273
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
274
275
if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
276
return true;
277
}
278
}
279
280
current_owner = _get_next_owner_node(current_owner);
281
}
282
283
// Second, check global themes from the appropriate context.
284
ThemeContext *global_context = _get_active_owner_context();
285
for (const Ref<Theme> &theme : global_context->get_themes()) {
286
if (theme.is_valid()) {
287
for (const StringName &E : p_theme_types) {
288
if (theme->has_theme_item(p_data_type, p_name, E)) {
289
return true;
290
}
291
}
292
}
293
}
294
295
// Finally, if no match exists, return false.
296
return false;
297
}
298
299
float ThemeOwner::get_theme_default_base_scale() {
300
// First, look through each control or window node in the branch, until no valid parent can be found.
301
// Only nodes with a theme resource attached are considered.
302
// For each theme resource see if their assigned theme has the default value defined and valid.
303
Node *current_owner = owner_node;
304
305
while (current_owner) {
306
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
307
308
if (owner_theme.is_valid() && owner_theme->has_default_base_scale()) {
309
return owner_theme->get_default_base_scale();
310
}
311
312
current_owner = _get_next_owner_node(current_owner);
313
}
314
315
// Second, check global themes from the appropriate context.
316
ThemeContext *global_context = _get_active_owner_context();
317
for (const Ref<Theme> &theme : global_context->get_themes()) {
318
if (theme.is_valid()) {
319
if (theme->has_default_base_scale()) {
320
return theme->get_default_base_scale();
321
}
322
}
323
}
324
325
// Finally, if no match exists, return the universal default.
326
return ThemeDB::get_singleton()->get_fallback_base_scale();
327
}
328
329
Ref<Font> ThemeOwner::get_theme_default_font() {
330
// First, look through each control or window node in the branch, until no valid parent can be found.
331
// Only nodes with a theme resource attached are considered.
332
// For each theme resource see if their assigned theme has the default value defined and valid.
333
Node *current_owner = owner_node;
334
335
while (current_owner) {
336
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
337
338
if (owner_theme.is_valid() && owner_theme->has_default_font()) {
339
return owner_theme->get_default_font();
340
}
341
342
current_owner = _get_next_owner_node(current_owner);
343
}
344
345
// Second, check global themes from the appropriate context.
346
ThemeContext *global_context = _get_active_owner_context();
347
for (const Ref<Theme> &theme : global_context->get_themes()) {
348
if (theme.is_valid()) {
349
if (theme->has_default_font()) {
350
return theme->get_default_font();
351
}
352
}
353
}
354
355
// Finally, if no match exists, return the universal default.
356
return ThemeDB::get_singleton()->get_fallback_font();
357
}
358
359
int ThemeOwner::get_theme_default_font_size() {
360
// First, look through each control or window node in the branch, until no valid parent can be found.
361
// Only nodes with a theme resource attached are considered.
362
// For each theme resource see if their assigned theme has the default value defined and valid.
363
Node *current_owner = owner_node;
364
365
while (current_owner) {
366
Ref<Theme> owner_theme = _get_owner_node_theme(current_owner);
367
368
if (owner_theme.is_valid() && owner_theme->has_default_font_size()) {
369
return owner_theme->get_default_font_size();
370
}
371
372
current_owner = _get_next_owner_node(current_owner);
373
}
374
375
// Second, check global themes from the appropriate context.
376
ThemeContext *global_context = _get_active_owner_context();
377
for (const Ref<Theme> &theme : global_context->get_themes()) {
378
if (theme.is_valid()) {
379
if (theme->has_default_font_size()) {
380
return theme->get_default_font_size();
381
}
382
}
383
}
384
385
// Finally, if no match exists, return the universal default.
386
return ThemeDB::get_singleton()->get_fallback_font_size();
387
}
388
389
Ref<Theme> ThemeOwner::_get_owner_node_theme(Node *p_owner_node) const {
390
const Control *owner_c = Object::cast_to<Control>(p_owner_node);
391
if (owner_c) {
392
return owner_c->get_theme();
393
}
394
395
const Window *owner_w = Object::cast_to<Window>(p_owner_node);
396
if (owner_w) {
397
return owner_w->get_theme();
398
}
399
400
return Ref<Theme>();
401
}
402
403
Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
404
Node *parent = p_from_node->get_parent();
405
406
Control *parent_c = Object::cast_to<Control>(parent);
407
if (parent_c) {
408
return parent_c->get_theme_owner_node();
409
} else {
410
Window *parent_w = Object::cast_to<Window>(parent);
411
if (parent_w) {
412
return parent_w->get_theme_owner_node();
413
}
414
}
415
416
return nullptr;
417
}
418
419