theme_owner.cpp 15 KB

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