theme_owner.cpp 16 KB

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