theme_owner.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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. // Theme propagation.
  61. void ThemeOwner::assign_theme_on_parented(Node *p_for_node) {
  62. // We check if there are any themes affecting the parent. If that's the case
  63. // its children also need to be affected.
  64. // We don't notify here because `NOTIFICATION_THEME_CHANGED` will be handled
  65. // a bit later by `NOTIFICATION_ENTER_TREE`.
  66. Node *parent = p_for_node->get_parent();
  67. Control *parent_c = Object::cast_to<Control>(parent);
  68. if (parent_c && parent_c->has_theme_owner_node()) {
  69. propagate_theme_changed(p_for_node, parent_c->get_theme_owner_node(), false, true);
  70. } else {
  71. Window *parent_w = Object::cast_to<Window>(parent);
  72. if (parent_w && parent_w->has_theme_owner_node()) {
  73. propagate_theme_changed(p_for_node, parent_w->get_theme_owner_node(), false, true);
  74. }
  75. }
  76. }
  77. void ThemeOwner::clear_theme_on_unparented(Node *p_for_node) {
  78. // We check if there were any themes affecting the parent. If that's the case
  79. // its children need were also affected and need to be updated.
  80. // We don't notify because we're exiting the tree, and it's not important.
  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, nullptr, 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, nullptr, false, true);
  89. }
  90. }
  91. }
  92. void ThemeOwner::propagate_theme_changed(Node *p_to_node, Node *p_owner_node, bool p_notify, bool p_assign) {
  93. Control *c = Object::cast_to<Control>(p_to_node);
  94. Window *w = c == nullptr ? Object::cast_to<Window>(p_to_node) : nullptr;
  95. if (!c && !w) {
  96. // Theme inheritance chains are broken by nodes that aren't Control or Window.
  97. return;
  98. }
  99. bool assign = p_assign;
  100. if (c) {
  101. if (c != p_owner_node && c->get_theme().is_valid()) {
  102. // Has a theme, so we don't want to change the theme owner,
  103. // but we still want to propagate in case this child has theme items
  104. // it inherits from the theme this node uses.
  105. // See https://github.com/godotengine/godot/issues/62844.
  106. assign = false;
  107. }
  108. if (assign) {
  109. c->set_theme_owner_node(p_owner_node);
  110. }
  111. if (p_notify) {
  112. c->notification(Control::NOTIFICATION_THEME_CHANGED);
  113. }
  114. } else if (w) {
  115. if (w != p_owner_node && w->get_theme().is_valid()) {
  116. // Same as above.
  117. assign = false;
  118. }
  119. if (assign) {
  120. w->set_theme_owner_node(p_owner_node);
  121. }
  122. if (p_notify) {
  123. w->notification(Window::NOTIFICATION_THEME_CHANGED);
  124. }
  125. }
  126. for (int i = 0; i < p_to_node->get_child_count(); i++) {
  127. propagate_theme_changed(p_to_node->get_child(i), p_owner_node, p_notify, assign);
  128. }
  129. }
  130. // Theme lookup.
  131. void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, List<StringName> *r_list) const {
  132. const Control *for_c = Object::cast_to<Control>(p_for_node);
  133. const Window *for_w = Object::cast_to<Window>(p_for_node);
  134. ERR_FAIL_COND_MSG(!for_c && !for_w, "Only Control and Window nodes and derivatives can be polled for theming.");
  135. Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme();
  136. Ref<Theme> project_theme = ThemeDB::get_singleton()->get_project_theme();
  137. StringName type_variation;
  138. if (for_c) {
  139. type_variation = for_c->get_theme_type_variation();
  140. } else if (for_w) {
  141. type_variation = for_w->get_theme_type_variation();
  142. }
  143. if (p_theme_type == StringName() || p_theme_type == p_for_node->get_class_name() || p_theme_type == type_variation) {
  144. if (project_theme.is_valid() && project_theme->get_type_variation_base(type_variation) != StringName()) {
  145. project_theme->get_type_dependencies(p_for_node->get_class_name(), type_variation, r_list);
  146. } else {
  147. default_theme->get_type_dependencies(p_for_node->get_class_name(), type_variation, r_list);
  148. }
  149. } else {
  150. default_theme->get_type_dependencies(p_theme_type, StringName(), r_list);
  151. }
  152. }
  153. Node *ThemeOwner::_get_next_owner_node(Node *p_from_node) const {
  154. Node *parent = p_from_node->get_parent();
  155. Control *parent_c = Object::cast_to<Control>(parent);
  156. if (parent_c) {
  157. return parent_c->get_theme_owner_node();
  158. } else {
  159. Window *parent_w = Object::cast_to<Window>(parent);
  160. if (parent_w) {
  161. return parent_w->get_theme_owner_node();
  162. }
  163. }
  164. return nullptr;
  165. }
  166. Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
  167. ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, Variant(), "At least one theme type must be specified.");
  168. // First, look through each control or window node in the branch, until no valid parent can be found.
  169. // Only nodes with a theme resource attached are considered.
  170. Node *owner_node = get_owner_node();
  171. while (owner_node) {
  172. // For each theme resource check the theme types provided and see if p_name exists with any of them.
  173. for (const StringName &E : p_theme_types) {
  174. Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
  175. if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
  176. return owner_theme->get_theme_item(p_data_type, p_name, E);
  177. }
  178. }
  179. owner_node = _get_next_owner_node(owner_node);
  180. }
  181. // Secondly, check the project-defined Theme resource.
  182. if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
  183. for (const StringName &E : p_theme_types) {
  184. if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(p_data_type, p_name, E)) {
  185. return ThemeDB::get_singleton()->get_project_theme()->get_theme_item(p_data_type, p_name, E);
  186. }
  187. }
  188. }
  189. // Lastly, fall back on the items defined in the default Theme, if they exist.
  190. for (const StringName &E : p_theme_types) {
  191. if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(p_data_type, p_name, E)) {
  192. return ThemeDB::get_singleton()->get_default_theme()->get_theme_item(p_data_type, p_name, E);
  193. }
  194. }
  195. // If they don't exist, use any type to return the default/empty value.
  196. return ThemeDB::get_singleton()->get_default_theme()->get_theme_item(p_data_type, p_name, p_theme_types[0]);
  197. }
  198. bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) {
  199. ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified.");
  200. // First, look through each control or window node in the branch, until no valid parent can be found.
  201. // Only nodes with a theme resource attached are considered.
  202. Node *owner_node = get_owner_node();
  203. while (owner_node) {
  204. // For each theme resource check the theme types provided and see if p_name exists with any of them.
  205. for (const StringName &E : p_theme_types) {
  206. Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
  207. if (owner_theme.is_valid() && owner_theme->has_theme_item(p_data_type, p_name, E)) {
  208. return true;
  209. }
  210. }
  211. owner_node = _get_next_owner_node(owner_node);
  212. }
  213. // Secondly, check the project-defined Theme resource.
  214. if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
  215. for (const StringName &E : p_theme_types) {
  216. if (ThemeDB::get_singleton()->get_project_theme()->has_theme_item(p_data_type, p_name, E)) {
  217. return true;
  218. }
  219. }
  220. }
  221. // Lastly, fall back on the items defined in the default Theme, if they exist.
  222. for (const StringName &E : p_theme_types) {
  223. if (ThemeDB::get_singleton()->get_default_theme()->has_theme_item(p_data_type, p_name, E)) {
  224. return true;
  225. }
  226. }
  227. return false;
  228. }
  229. float ThemeOwner::get_theme_default_base_scale() {
  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. // For each theme resource see if their assigned theme has the default value defined and valid.
  233. Node *owner_node = get_owner_node();
  234. while (owner_node) {
  235. Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
  236. if (owner_theme.is_valid() && owner_theme->has_default_base_scale()) {
  237. return owner_theme->get_default_base_scale();
  238. }
  239. owner_node = _get_next_owner_node(owner_node);
  240. }
  241. // Secondly, check the project-defined Theme resource.
  242. if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
  243. if (ThemeDB::get_singleton()->get_project_theme()->has_default_base_scale()) {
  244. return ThemeDB::get_singleton()->get_project_theme()->get_default_base_scale();
  245. }
  246. }
  247. // Lastly, fall back on the default Theme.
  248. if (ThemeDB::get_singleton()->get_default_theme()->has_default_base_scale()) {
  249. return ThemeDB::get_singleton()->get_default_theme()->get_default_base_scale();
  250. }
  251. return ThemeDB::get_singleton()->get_fallback_base_scale();
  252. }
  253. Ref<Font> ThemeOwner::get_theme_default_font() {
  254. // First, look through each control or window node in the branch, until no valid parent can be found.
  255. // Only nodes with a theme resource attached are considered.
  256. // For each theme resource see if their assigned theme has the default value defined and valid.
  257. Node *owner_node = get_owner_node();
  258. while (owner_node) {
  259. Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
  260. if (owner_theme.is_valid() && owner_theme->has_default_font()) {
  261. return owner_theme->get_default_font();
  262. }
  263. owner_node = _get_next_owner_node(owner_node);
  264. }
  265. // Secondly, check the project-defined Theme resource.
  266. if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
  267. if (ThemeDB::get_singleton()->get_project_theme()->has_default_font()) {
  268. return ThemeDB::get_singleton()->get_project_theme()->get_default_font();
  269. }
  270. }
  271. // Lastly, fall back on the default Theme.
  272. if (ThemeDB::get_singleton()->get_default_theme()->has_default_font()) {
  273. return ThemeDB::get_singleton()->get_default_theme()->get_default_font();
  274. }
  275. return ThemeDB::get_singleton()->get_fallback_font();
  276. }
  277. int ThemeOwner::get_theme_default_font_size() {
  278. // First, look through each control or window node in the branch, until no valid parent can be found.
  279. // Only nodes with a theme resource attached are considered.
  280. // For each theme resource see if their assigned theme has the default value defined and valid.
  281. Node *owner_node = get_owner_node();
  282. while (owner_node) {
  283. Ref<Theme> owner_theme = _get_owner_node_theme(owner_node);
  284. if (owner_theme.is_valid() && owner_theme->has_default_font_size()) {
  285. return owner_theme->get_default_font_size();
  286. }
  287. owner_node = _get_next_owner_node(owner_node);
  288. }
  289. // Secondly, check the project-defined Theme resource.
  290. if (ThemeDB::get_singleton()->get_project_theme().is_valid()) {
  291. if (ThemeDB::get_singleton()->get_project_theme()->has_default_font_size()) {
  292. return ThemeDB::get_singleton()->get_project_theme()->get_default_font_size();
  293. }
  294. }
  295. // Lastly, fall back on the default Theme.
  296. if (ThemeDB::get_singleton()->get_default_theme()->has_default_font_size()) {
  297. return ThemeDB::get_singleton()->get_default_theme()->get_default_font_size();
  298. }
  299. return ThemeDB::get_singleton()->get_fallback_font_size();
  300. }
  301. Ref<Theme> ThemeOwner::_get_owner_node_theme(Node *p_owner_node) const {
  302. const Control *owner_c = Object::cast_to<Control>(p_owner_node);
  303. if (owner_c) {
  304. return owner_c->get_theme();
  305. }
  306. const Window *owner_w = Object::cast_to<Window>(p_owner_node);
  307. if (owner_w) {
  308. return owner_w->get_theme();
  309. }
  310. return Ref<Theme>();
  311. }