1 /*************************************************************************/
2 /*  theme.cpp                                                            */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
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.h"
31 #include "os/file_access.h"
32 #include "print_string.h"
33 
34 Ref<Theme> Theme::default_theme;
35 
_emit_theme_changed()36 void Theme::_emit_theme_changed() {
37 
38 	emit_changed();
39 }
40 
_ref_font(Ref<Font> p_sc)41 void Theme::_ref_font(Ref<Font> p_sc) {
42 
43 	if (!font_refcount.has(p_sc)) {
44 		font_refcount[p_sc] = 1;
45 		p_sc->connect("changed", this, "_emit_theme_changed");
46 	} else {
47 		font_refcount[p_sc] += 1;
48 	}
49 }
50 
_unref_font(Ref<Font> p_sc)51 void Theme::_unref_font(Ref<Font> p_sc) {
52 
53 	ERR_FAIL_COND(!font_refcount.has(p_sc));
54 	font_refcount[p_sc]--;
55 	if (font_refcount[p_sc] == 0) {
56 		p_sc->disconnect("changed", this, "_emit_theme_changed");
57 		font_refcount.erase(p_sc);
58 	}
59 }
60 
_set(const StringName & p_name,const Variant & p_value)61 bool Theme::_set(const StringName &p_name, const Variant &p_value) {
62 
63 	String sname = p_name;
64 
65 	if (sname.find("/") != -1) {
66 
67 		String type = sname.get_slicec('/', 1);
68 		String node_type = sname.get_slicec('/', 0);
69 		String name = sname.get_slicec('/', 2);
70 
71 		if (type == "icons") {
72 
73 			set_icon(name, node_type, p_value);
74 		} else if (type == "styles") {
75 
76 			set_stylebox(name, node_type, p_value);
77 		} else if (type == "fonts") {
78 
79 			set_font(name, node_type, p_value);
80 		} else if (type == "colors") {
81 
82 			set_color(name, node_type, p_value);
83 		} else if (type == "constants") {
84 
85 			set_constant(name, node_type, p_value);
86 		} else
87 			return false;
88 
89 		return true;
90 	}
91 
92 	return false;
93 }
94 
_get(const StringName & p_name,Variant & r_ret) const95 bool Theme::_get(const StringName &p_name, Variant &r_ret) const {
96 
97 	String sname = p_name;
98 
99 	if (sname.find("/") != -1) {
100 
101 		String type = sname.get_slicec('/', 1);
102 		String node_type = sname.get_slicec('/', 0);
103 		String name = sname.get_slicec('/', 2);
104 
105 		if (type == "icons") {
106 
107 			if (!has_icon(name, node_type))
108 				r_ret = Ref<Texture>();
109 			else
110 				r_ret = get_icon(name, node_type);
111 		} else if (type == "styles") {
112 
113 			if (!has_stylebox(name, node_type))
114 				r_ret = Ref<StyleBox>();
115 			else
116 				r_ret = get_stylebox(name, node_type);
117 		} else if (type == "fonts") {
118 
119 			if (!has_font(name, node_type))
120 				r_ret = Ref<Font>();
121 			else
122 				r_ret = get_font(name, node_type);
123 		} else if (type == "colors") {
124 
125 			r_ret = get_color(name, node_type);
126 		} else if (type == "constants") {
127 
128 			r_ret = get_constant(name, node_type);
129 		} else
130 			return false;
131 
132 		return true;
133 	}
134 
135 	return false;
136 }
137 
_get_property_list(List<PropertyInfo> * p_list) const138 void Theme::_get_property_list(List<PropertyInfo> *p_list) const {
139 
140 	List<PropertyInfo> list;
141 
142 	const StringName *key = NULL;
143 
144 	while ((key = icon_map.next(key))) {
145 
146 		const StringName *key2 = NULL;
147 
148 		while ((key2 = icon_map[*key].next(key2))) {
149 
150 			list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/icons/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "Texture", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
151 		}
152 	}
153 
154 	key = NULL;
155 
156 	while ((key = style_map.next(key))) {
157 
158 		const StringName *key2 = NULL;
159 
160 		while ((key2 = style_map[*key].next(key2))) {
161 
162 			list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/styles/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
163 		}
164 	}
165 
166 	key = NULL;
167 
168 	while ((key = font_map.next(key))) {
169 
170 		const StringName *key2 = NULL;
171 
172 		while ((key2 = font_map[*key].next(key2))) {
173 
174 			list.push_back(PropertyInfo(Variant::OBJECT, String() + *key + "/fonts/" + *key2, PROPERTY_HINT_RESOURCE_TYPE, "Font", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
175 		}
176 	}
177 
178 	key = NULL;
179 
180 	while ((key = color_map.next(key))) {
181 
182 		const StringName *key2 = NULL;
183 
184 		while ((key2 = color_map[*key].next(key2))) {
185 
186 			list.push_back(PropertyInfo(Variant::COLOR, String() + *key + "/colors/" + *key2));
187 		}
188 	}
189 
190 	key = NULL;
191 
192 	while ((key = constant_map.next(key))) {
193 
194 		const StringName *key2 = NULL;
195 
196 		while ((key2 = constant_map[*key].next(key2))) {
197 
198 			list.push_back(PropertyInfo(Variant::INT, String() + *key + "/constants/" + *key2));
199 		}
200 	}
201 
202 	list.sort();
203 	for (List<PropertyInfo>::Element *E = list.front(); E; E = E->next()) {
204 		p_list->push_back(E->get());
205 	}
206 }
207 
get_default()208 Ref<Theme> Theme::get_default() {
209 
210 	return default_theme;
211 }
212 
set_default_theme_font(const Ref<Font> & p_default_font)213 void Theme::set_default_theme_font(const Ref<Font> &p_default_font) {
214 
215 	if (default_theme_font == p_default_font)
216 		return;
217 
218 	if (default_theme_font.is_valid()) {
219 		_unref_font(default_theme_font);
220 	}
221 
222 	default_theme_font = p_default_font;
223 
224 	if (default_theme_font.is_valid()) {
225 		_ref_font(default_theme_font);
226 	}
227 
228 	_change_notify();
229 	emit_changed();
230 }
231 
get_default_theme_font() const232 Ref<Font> Theme::get_default_theme_font() const {
233 
234 	return default_theme_font;
235 }
236 
set_default(const Ref<Theme> & p_default)237 void Theme::set_default(const Ref<Theme> &p_default) {
238 
239 	default_theme = p_default;
240 }
241 
242 Ref<Texture> Theme::default_icon;
243 Ref<StyleBox> Theme::default_style;
244 Ref<Font> Theme::default_font;
245 
set_default_icon(const Ref<Texture> & p_icon)246 void Theme::set_default_icon(const Ref<Texture> &p_icon) {
247 
248 	default_icon = p_icon;
249 }
set_default_style(const Ref<StyleBox> & p_style)250 void Theme::set_default_style(const Ref<StyleBox> &p_style) {
251 
252 	default_style = p_style;
253 }
set_default_font(const Ref<Font> & p_font)254 void Theme::set_default_font(const Ref<Font> &p_font) {
255 
256 	default_font = p_font;
257 }
258 
set_icon(const StringName & p_name,const StringName & p_type,const Ref<Texture> & p_icon)259 void Theme::set_icon(const StringName &p_name, const StringName &p_type, const Ref<Texture> &p_icon) {
260 
261 	//	ERR_FAIL_COND(p_icon.is_null());
262 
263 	bool new_value = !icon_map.has(p_type) || !icon_map[p_type].has(p_name);
264 
265 	icon_map[p_type][p_name] = p_icon;
266 
267 	if (new_value) {
268 		_change_notify();
269 		emit_changed();
270 	}
271 }
get_icon(const StringName & p_name,const StringName & p_type) const272 Ref<Texture> Theme::get_icon(const StringName &p_name, const StringName &p_type) const {
273 
274 	if (icon_map.has(p_type) && icon_map[p_type].has(p_name) && icon_map[p_type][p_name].is_valid()) {
275 
276 		return icon_map[p_type][p_name];
277 	} else {
278 		return default_icon;
279 	}
280 }
281 
has_icon(const StringName & p_name,const StringName & p_type) const282 bool Theme::has_icon(const StringName &p_name, const StringName &p_type) const {
283 
284 	return (icon_map.has(p_type) && icon_map[p_type].has(p_name) && icon_map[p_type][p_name].is_valid());
285 }
286 
clear_icon(const StringName & p_name,const StringName & p_type)287 void Theme::clear_icon(const StringName &p_name, const StringName &p_type) {
288 
289 	ERR_FAIL_COND(!icon_map.has(p_type));
290 	ERR_FAIL_COND(!icon_map[p_type].has(p_name));
291 
292 	icon_map[p_type].erase(p_name);
293 	_change_notify();
294 	emit_changed();
295 }
296 
get_icon_list(StringName p_type,List<StringName> * p_list) const297 void Theme::get_icon_list(StringName p_type, List<StringName> *p_list) const {
298 
299 	if (!icon_map.has(p_type))
300 		return;
301 
302 	const StringName *key = NULL;
303 
304 	while ((key = icon_map[p_type].next(key))) {
305 
306 		p_list->push_back(*key);
307 	}
308 }
309 
set_shader(const StringName & p_name,const StringName & p_type,const Ref<Shader> & p_shader)310 void Theme::set_shader(const StringName &p_name, const StringName &p_type, const Ref<Shader> &p_shader) {
311 	bool new_value = !shader_map.has(p_type) || !shader_map[p_type].has(p_name);
312 
313 	shader_map[p_type][p_name] = p_shader;
314 
315 	if (new_value) {
316 		_change_notify();
317 		emit_changed();
318 	}
319 }
320 
get_shader(const StringName & p_name,const StringName & p_type) const321 Ref<Shader> Theme::get_shader(const StringName &p_name, const StringName &p_type) const {
322 	if (shader_map.has(p_type) && shader_map[p_type].has(p_name) && shader_map[p_type][p_name].is_valid()) {
323 		return shader_map[p_type][p_name];
324 	} else {
325 		return NULL;
326 	}
327 }
328 
has_shader(const StringName & p_name,const StringName & p_type) const329 bool Theme::has_shader(const StringName &p_name, const StringName &p_type) const {
330 	return (shader_map.has(p_type) && shader_map[p_type].has(p_name) && shader_map[p_type][p_name].is_valid());
331 }
332 
clear_shader(const StringName & p_name,const StringName & p_type)333 void Theme::clear_shader(const StringName &p_name, const StringName &p_type) {
334 	ERR_FAIL_COND(!shader_map.has(p_type));
335 	ERR_FAIL_COND(!shader_map[p_type].has(p_name));
336 
337 	shader_map[p_type].erase(p_name);
338 	_change_notify();
339 	emit_changed();
340 }
341 
get_shader_list(const StringName & p_type,List<StringName> * p_list) const342 void Theme::get_shader_list(const StringName &p_type, List<StringName> *p_list) const {
343 	if (!shader_map.has(p_type))
344 		return;
345 
346 	const StringName *key = NULL;
347 
348 	while ((key = shader_map[p_type].next(key))) {
349 
350 		p_list->push_back(*key);
351 	}
352 }
353 
set_stylebox(const StringName & p_name,const StringName & p_type,const Ref<StyleBox> & p_style)354 void Theme::set_stylebox(const StringName &p_name, const StringName &p_type, const Ref<StyleBox> &p_style) {
355 
356 	//	ERR_FAIL_COND(p_style.is_null());
357 
358 	bool new_value = !style_map.has(p_type) || !style_map[p_type].has(p_name);
359 
360 	style_map[p_type][p_name] = p_style;
361 
362 	if (new_value)
363 		_change_notify();
364 	emit_changed();
365 }
366 
get_stylebox(const StringName & p_name,const StringName & p_type) const367 Ref<StyleBox> Theme::get_stylebox(const StringName &p_name, const StringName &p_type) const {
368 
369 	if (style_map.has(p_type) && style_map[p_type].has(p_name) && style_map[p_type][p_name].is_valid()) {
370 
371 		return style_map[p_type][p_name];
372 	} else {
373 		return default_style;
374 	}
375 }
376 
has_stylebox(const StringName & p_name,const StringName & p_type) const377 bool Theme::has_stylebox(const StringName &p_name, const StringName &p_type) const {
378 
379 	return (style_map.has(p_type) && style_map[p_type].has(p_name) && style_map[p_type][p_name].is_valid());
380 }
381 
clear_stylebox(const StringName & p_name,const StringName & p_type)382 void Theme::clear_stylebox(const StringName &p_name, const StringName &p_type) {
383 
384 	ERR_FAIL_COND(!style_map.has(p_type));
385 	ERR_FAIL_COND(!style_map[p_type].has(p_name));
386 
387 	style_map[p_type].erase(p_name);
388 	_change_notify();
389 	emit_changed();
390 }
391 
get_stylebox_list(StringName p_type,List<StringName> * p_list) const392 void Theme::get_stylebox_list(StringName p_type, List<StringName> *p_list) const {
393 
394 	if (!style_map.has(p_type))
395 		return;
396 
397 	const StringName *key = NULL;
398 
399 	while ((key = style_map[p_type].next(key))) {
400 
401 		p_list->push_back(*key);
402 	}
403 }
404 
get_stylebox_types(List<StringName> * p_list) const405 void Theme::get_stylebox_types(List<StringName> *p_list) const {
406 	const StringName *key = NULL;
407 	while ((key = style_map.next(key))) {
408 		p_list->push_back(*key);
409 	}
410 }
411 
set_font(const StringName & p_name,const StringName & p_type,const Ref<Font> & p_font)412 void Theme::set_font(const StringName &p_name, const StringName &p_type, const Ref<Font> &p_font) {
413 
414 	//	ERR_FAIL_COND(p_font.is_null());
415 
416 	bool new_value = !font_map.has(p_type) || !font_map[p_type].has(p_name);
417 
418 	if (!new_value) {
419 		if (font_map[p_type][p_name].is_valid()) {
420 			_unref_font(font_map[p_type][p_name]);
421 		}
422 	}
423 	font_map[p_type][p_name] = p_font;
424 
425 	if (p_font.is_valid()) {
426 		_ref_font(p_font);
427 	}
428 
429 	if (new_value) {
430 		_change_notify();
431 		emit_changed();
432 	}
433 }
get_font(const StringName & p_name,const StringName & p_type) const434 Ref<Font> Theme::get_font(const StringName &p_name, const StringName &p_type) const {
435 
436 	if (font_map.has(p_type) && font_map[p_type].has(p_name) && font_map[p_type][p_name].is_valid())
437 		return font_map[p_type][p_name];
438 	else if (default_theme_font.is_valid())
439 		return default_theme_font;
440 	else
441 		return default_font;
442 }
443 
has_font(const StringName & p_name,const StringName & p_type) const444 bool Theme::has_font(const StringName &p_name, const StringName &p_type) const {
445 
446 	return (font_map.has(p_type) && font_map[p_type].has(p_name) && font_map[p_type][p_name].is_valid());
447 }
448 
clear_font(const StringName & p_name,const StringName & p_type)449 void Theme::clear_font(const StringName &p_name, const StringName &p_type) {
450 
451 	ERR_FAIL_COND(!font_map.has(p_type));
452 	ERR_FAIL_COND(!font_map[p_type].has(p_name));
453 
454 	if (font_map.has(p_type) && font_map[p_type].has(p_name) && font_map[p_type][p_name].is_valid()) {
455 		_unref_font(font_map[p_type][p_name]);
456 	}
457 
458 	font_map[p_type].erase(p_name);
459 	_change_notify();
460 	emit_changed();
461 }
462 
get_font_list(StringName p_type,List<StringName> * p_list) const463 void Theme::get_font_list(StringName p_type, List<StringName> *p_list) const {
464 
465 	if (!font_map.has(p_type))
466 		return;
467 
468 	const StringName *key = NULL;
469 
470 	while ((key = font_map[p_type].next(key))) {
471 
472 		p_list->push_back(*key);
473 	}
474 }
475 
set_color(const StringName & p_name,const StringName & p_type,const Color & p_color)476 void Theme::set_color(const StringName &p_name, const StringName &p_type, const Color &p_color) {
477 
478 	bool new_value = !color_map.has(p_type) || !color_map[p_type].has(p_name);
479 
480 	color_map[p_type][p_name] = p_color;
481 
482 	if (new_value) {
483 		_change_notify();
484 		emit_changed();
485 	}
486 }
487 
get_color(const StringName & p_name,const StringName & p_type) const488 Color Theme::get_color(const StringName &p_name, const StringName &p_type) const {
489 
490 	if (color_map.has(p_type) && color_map[p_type].has(p_name))
491 		return color_map[p_type][p_name];
492 	else
493 		return Color();
494 }
495 
has_color(const StringName & p_name,const StringName & p_type) const496 bool Theme::has_color(const StringName &p_name, const StringName &p_type) const {
497 
498 	return (color_map.has(p_type) && color_map[p_type].has(p_name));
499 }
500 
clear_color(const StringName & p_name,const StringName & p_type)501 void Theme::clear_color(const StringName &p_name, const StringName &p_type) {
502 
503 	ERR_FAIL_COND(!color_map.has(p_type));
504 	ERR_FAIL_COND(!color_map[p_type].has(p_name));
505 
506 	color_map[p_type].erase(p_name);
507 	_change_notify();
508 	emit_changed();
509 }
510 
get_color_list(StringName p_type,List<StringName> * p_list) const511 void Theme::get_color_list(StringName p_type, List<StringName> *p_list) const {
512 
513 	if (!color_map.has(p_type))
514 		return;
515 
516 	const StringName *key = NULL;
517 
518 	while ((key = color_map[p_type].next(key))) {
519 
520 		p_list->push_back(*key);
521 	}
522 }
523 
set_constant(const StringName & p_name,const StringName & p_type,int p_constant)524 void Theme::set_constant(const StringName &p_name, const StringName &p_type, int p_constant) {
525 
526 	bool new_value = !constant_map.has(p_type) || !constant_map[p_type].has(p_name);
527 	constant_map[p_type][p_name] = p_constant;
528 
529 	if (new_value) {
530 		_change_notify();
531 		emit_changed();
532 	}
533 }
534 
get_constant(const StringName & p_name,const StringName & p_type) const535 int Theme::get_constant(const StringName &p_name, const StringName &p_type) const {
536 
537 	if (constant_map.has(p_type) && constant_map[p_type].has(p_name))
538 		return constant_map[p_type][p_name];
539 	else {
540 		return 0;
541 	}
542 }
543 
has_constant(const StringName & p_name,const StringName & p_type) const544 bool Theme::has_constant(const StringName &p_name, const StringName &p_type) const {
545 
546 	return (constant_map.has(p_type) && constant_map[p_type].has(p_name));
547 }
548 
clear_constant(const StringName & p_name,const StringName & p_type)549 void Theme::clear_constant(const StringName &p_name, const StringName &p_type) {
550 
551 	ERR_FAIL_COND(!constant_map.has(p_type));
552 	ERR_FAIL_COND(!constant_map[p_type].has(p_name));
553 
554 	constant_map[p_type].erase(p_name);
555 	_change_notify();
556 	emit_changed();
557 }
558 
get_constant_list(StringName p_type,List<StringName> * p_list) const559 void Theme::get_constant_list(StringName p_type, List<StringName> *p_list) const {
560 
561 	if (!constant_map.has(p_type))
562 		return;
563 
564 	const StringName *key = NULL;
565 
566 	while ((key = constant_map[p_type].next(key))) {
567 
568 		p_list->push_back(*key);
569 	}
570 }
571 
copy_default_theme()572 void Theme::copy_default_theme() {
573 
574 	Ref<Theme> default_theme = get_default();
575 
576 	icon_map = default_theme->icon_map;
577 	style_map = default_theme->style_map;
578 	font_map = default_theme->font_map;
579 	color_map = default_theme->color_map;
580 	constant_map = default_theme->constant_map;
581 	_change_notify();
582 	emit_changed();
583 }
584 
get_type_list(List<StringName> * p_list) const585 void Theme::get_type_list(List<StringName> *p_list) const {
586 
587 	Set<StringName> types;
588 
589 	const StringName *key = NULL;
590 
591 	while ((key = icon_map.next(key))) {
592 
593 		types.insert(*key);
594 	}
595 
596 	key = NULL;
597 
598 	while ((key = style_map.next(key))) {
599 
600 		types.insert(*key);
601 	}
602 
603 	key = NULL;
604 
605 	while ((key = font_map.next(key))) {
606 
607 		types.insert(*key);
608 	}
609 
610 	key = NULL;
611 
612 	while ((key = color_map.next(key))) {
613 
614 		types.insert(*key);
615 	}
616 
617 	key = NULL;
618 
619 	while ((key = constant_map.next(key))) {
620 
621 		types.insert(*key);
622 	}
623 
624 	for (Set<StringName>::Element *E = types.front(); E; E = E->next()) {
625 
626 		p_list->push_back(E->get());
627 	}
628 }
629 
_bind_methods()630 void Theme::_bind_methods() {
631 
632 	ObjectTypeDB::bind_method(_MD("set_icon", "name", "type", "texture:Texture"), &Theme::set_icon);
633 	ObjectTypeDB::bind_method(_MD("get_icon:Texture", "name", "type"), &Theme::get_icon);
634 	ObjectTypeDB::bind_method(_MD("has_icon", "name", "type"), &Theme::has_icon);
635 	ObjectTypeDB::bind_method(_MD("clear_icon", "name", "type"), &Theme::clear_icon);
636 	ObjectTypeDB::bind_method(_MD("get_icon_list", "type"), &Theme::_get_icon_list);
637 
638 	ObjectTypeDB::bind_method(_MD("set_stylebox", "name", "type", "texture:StyleBox"), &Theme::set_stylebox);
639 	ObjectTypeDB::bind_method(_MD("get_stylebox:StyleBox", "name", "type"), &Theme::get_stylebox);
640 	ObjectTypeDB::bind_method(_MD("has_stylebox", "name", "type"), &Theme::has_stylebox);
641 	ObjectTypeDB::bind_method(_MD("clear_stylebox", "name", "type"), &Theme::clear_stylebox);
642 	ObjectTypeDB::bind_method(_MD("get_stylebox_list", "type"), &Theme::_get_stylebox_list);
643 	ObjectTypeDB::bind_method(_MD("get_stylebox_types"), &Theme::_get_stylebox_types);
644 
645 	ObjectTypeDB::bind_method(_MD("set_font", "name", "type", "font:Font"), &Theme::set_font);
646 	ObjectTypeDB::bind_method(_MD("get_font:Font", "name", "type"), &Theme::get_font);
647 	ObjectTypeDB::bind_method(_MD("has_font", "name", "type"), &Theme::has_font);
648 	ObjectTypeDB::bind_method(_MD("clear_font", "name", "type"), &Theme::clear_font);
649 	ObjectTypeDB::bind_method(_MD("get_font_list", "type"), &Theme::_get_font_list);
650 
651 	ObjectTypeDB::bind_method(_MD("set_color", "name", "type", "color"), &Theme::set_color);
652 	ObjectTypeDB::bind_method(_MD("get_color", "name", "type"), &Theme::get_color);
653 	ObjectTypeDB::bind_method(_MD("has_color", "name", "type"), &Theme::has_color);
654 	ObjectTypeDB::bind_method(_MD("clear_color", "name", "type"), &Theme::clear_color);
655 	ObjectTypeDB::bind_method(_MD("get_color_list", "type"), &Theme::_get_color_list);
656 
657 	ObjectTypeDB::bind_method(_MD("set_constant", "name", "type", "constant"), &Theme::set_constant);
658 	ObjectTypeDB::bind_method(_MD("get_constant", "name", "type"), &Theme::get_constant);
659 	ObjectTypeDB::bind_method(_MD("has_constant", "name", "type"), &Theme::has_constant);
660 	ObjectTypeDB::bind_method(_MD("clear_constant", "name", "type"), &Theme::clear_constant);
661 	ObjectTypeDB::bind_method(_MD("get_constant_list", "type"), &Theme::_get_constant_list);
662 
663 	ObjectTypeDB::bind_method(_MD("set_default_font", "font"), &Theme::set_default_theme_font);
664 	ObjectTypeDB::bind_method(_MD("get_default_font"), &Theme::get_default_theme_font);
665 
666 	ObjectTypeDB::bind_method(_MD("get_type_list", "type"), &Theme::_get_type_list);
667 
668 	ObjectTypeDB::bind_method(_MD("_emit_theme_changed"), &Theme::_emit_theme_changed);
669 
670 	ObjectTypeDB::bind_method("copy_default_theme", &Theme::copy_default_theme);
671 
672 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), _SCS("set_default_font"), _SCS("get_default_font"));
673 }
674 
Theme()675 Theme::Theme() {
676 }
677 
~Theme()678 Theme::~Theme() {
679 }
680 
load(const String & p_path,const String & p_original_path,Error * r_error)681 RES ResourceFormatLoaderTheme::load(const String &p_path, const String &p_original_path, Error *r_error) {
682 	if (r_error)
683 		*r_error = ERR_CANT_OPEN;
684 
685 	Error err;
686 	FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
687 
688 	ERR_EXPLAIN("Unable to open theme file: " + p_path);
689 	ERR_FAIL_COND_V(err, RES());
690 	String base_path = p_path.get_base_dir();
691 	Ref<Theme> theme(memnew(Theme));
692 	Map<StringName, Variant> library;
693 	if (r_error)
694 		*r_error = ERR_FILE_CORRUPT;
695 
696 	bool reading_library = false;
697 	int line = 0;
698 
699 	while (!f->eof_reached()) {
700 
701 		String l = f->get_line().strip_edges();
702 		line++;
703 
704 		int comment = l.find(";");
705 		if (comment != -1)
706 			l = l.substr(0, comment);
707 		if (l == "")
708 			continue;
709 
710 		if (l.begins_with("[")) {
711 			if (l == "[library]") {
712 				reading_library = true;
713 			} else if (l == "[theme]") {
714 				reading_library = false;
715 			} else {
716 				memdelete(f);
717 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Unknown section type: '" + l + "'.");
718 				ERR_FAIL_V(RES());
719 			}
720 			continue;
721 		}
722 
723 		int eqpos = l.find("=");
724 		if (eqpos == -1) {
725 			memdelete(f);
726 			ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected '='.");
727 			ERR_FAIL_V(RES());
728 		}
729 
730 		String right = l.substr(eqpos + 1, l.length()).strip_edges();
731 		if (right == "") {
732 			memdelete(f);
733 			ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected value after '='.");
734 			ERR_FAIL_V(RES());
735 		}
736 
737 		Variant value;
738 
739 		if (right.is_valid_integer()) {
740 			//is number
741 			value = right.to_int();
742 		} else if (right.is_valid_html_color()) {
743 			//is html color
744 			value = Color::html(right);
745 		} else if (right.begins_with("@")) { //reference
746 
747 			String reference = right.substr(1, right.length());
748 			if (!library.has(reference)) {
749 				memdelete(f);
750 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid reference to '" + reference + "'.");
751 				ERR_FAIL_V(RES());
752 			}
753 
754 			value = library[reference];
755 
756 		} else if (right.begins_with("default")) { //use default
757 			//do none
758 		} else {
759 			//attempt to parse a constructor
760 			int popenpos = right.find("(");
761 
762 			if (popenpos == -1) {
763 				memdelete(f);
764 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid constructor syntax: " + right);
765 				ERR_FAIL_V(RES());
766 			}
767 
768 			int pclosepos = right.find_last(")");
769 
770 			if (pclosepos == -1) {
771 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid constructor parameter syntax: " + right);
772 				ERR_FAIL_V(RES());
773 			}
774 
775 			String type = right.substr(0, popenpos);
776 			String param = right.substr(popenpos + 1, pclosepos - popenpos - 1);
777 
778 			if (type == "icon") {
779 
780 				String path;
781 
782 				if (param.is_abs_path())
783 					path = param;
784 				else
785 					path = base_path + "/" + param;
786 
787 				Ref<Texture> texture = ResourceLoader::load(path);
788 				if (!texture.is_valid()) {
789 					memdelete(f);
790 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Couldn't find icon at path: " + path);
791 					ERR_FAIL_V(RES());
792 				}
793 
794 				value = texture;
795 
796 			} else if (type == "sbox") {
797 
798 				String path;
799 
800 				if (param.is_abs_path())
801 					path = param;
802 				else
803 					path = base_path + "/" + param;
804 
805 				Ref<StyleBox> stylebox = ResourceLoader::load(path);
806 				if (!stylebox.is_valid()) {
807 					memdelete(f);
808 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Couldn't find stylebox at path: " + path);
809 					ERR_FAIL_V(RES());
810 				}
811 
812 				value = stylebox;
813 
814 			} else if (type == "sboxt") {
815 
816 				Vector<String> params = param.split(",");
817 				if (params.size() != 5 && params.size() != 9) {
818 					memdelete(f);
819 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid param count for sboxt(): '" + right + "'.");
820 					ERR_FAIL_V(RES());
821 				}
822 
823 				String path = params[0];
824 
825 				if (!param.is_abs_path())
826 					path = base_path + "/" + path;
827 
828 				Ref<Texture> tex = ResourceLoader::load(path);
829 				if (tex.is_null()) {
830 					memdelete(f);
831 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Could not open texture for sboxt at path: '" + params[0] + "'.");
832 					ERR_FAIL_V(RES());
833 				}
834 
835 				Ref<StyleBoxTexture> sbtex(memnew(StyleBoxTexture));
836 
837 				sbtex->set_texture(tex);
838 
839 				for (int i = 0; i < 4; i++) {
840 					if (!params[i + 1].is_valid_integer()) {
841 
842 						memdelete(f);
843 						ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid expand margin parameter for sboxt #" + itos(i + 1) + ", expected integer constant, got: '" + params[i + 1] + "'.");
844 						ERR_FAIL_V(RES());
845 					}
846 
847 					int margin = params[i + 1].to_int();
848 					sbtex->set_expand_margin_size(Margin(i), margin);
849 				}
850 
851 				if (params.size() == 9) {
852 
853 					for (int i = 0; i < 4; i++) {
854 
855 						if (!params[i + 5].is_valid_integer()) {
856 							memdelete(f);
857 							ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid expand margin parameter for sboxt #" + itos(i + 5) + ", expected integer constant, got: '" + params[i + 5] + "'.");
858 							ERR_FAIL_V(RES());
859 						}
860 
861 						int margin = params[i + 5].to_int();
862 						sbtex->set_margin_size(Margin(i), margin);
863 					}
864 				}
865 
866 				value = sbtex;
867 			} else if (type == "sboxf") {
868 
869 				Vector<String> params = param.split(",");
870 				if (params.size() < 2) {
871 
872 					memdelete(f);
873 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid param count for sboxf(): '" + right + "'.");
874 					ERR_FAIL_V(RES());
875 				}
876 
877 				Ref<StyleBoxFlat> sbflat(memnew(StyleBoxFlat));
878 
879 				if (!params[0].is_valid_integer()) {
880 
881 					memdelete(f);
882 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected integer numeric constant for parameter 0 (border size).");
883 					ERR_FAIL_V(RES());
884 				}
885 
886 				sbflat->set_border_size(params[0].to_int());
887 
888 				if (!params[0].is_valid_integer()) {
889 
890 					memdelete(f);
891 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected integer numeric constant for parameter 0 (border size).");
892 					ERR_FAIL_V(RES());
893 				}
894 
895 				int left = MIN(params.size() - 1, 3);
896 
897 				int ccodes = 0;
898 
899 				for (int i = 0; i < left; i++) {
900 
901 					if (params[i + 1].is_valid_html_color())
902 						ccodes++;
903 					else
904 						break;
905 				}
906 
907 				Color normal;
908 				Color bright;
909 				Color dark;
910 
911 				if (ccodes < 1) {
912 					memdelete(f);
913 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected at least 1, 2 or 3 html color codes.");
914 					ERR_FAIL_V(RES());
915 				} else if (ccodes == 1) {
916 
917 					normal = Color::html(params[1]);
918 					bright = Color::html(params[1]);
919 					dark = Color::html(params[1]);
920 				} else if (ccodes == 2) {
921 
922 					normal = Color::html(params[1]);
923 					bright = Color::html(params[2]);
924 					dark = Color::html(params[2]);
925 				} else {
926 
927 					normal = Color::html(params[1]);
928 					bright = Color::html(params[2]);
929 					dark = Color::html(params[3]);
930 				}
931 
932 				sbflat->set_dark_color(dark);
933 				sbflat->set_light_color(bright);
934 				sbflat->set_bg_color(normal);
935 
936 				if (params.size() == ccodes + 5) {
937 					//margins
938 					for (int i = 0; i < 4; i++) {
939 
940 						if (!params[i + ccodes + 1].is_valid_integer()) {
941 							memdelete(f);
942 							ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid expand margin parameter for sboxf #" + itos(i + ccodes + 1) + ", expected integer constant, got: '" + params[i + ccodes + 1] + "'.");
943 							ERR_FAIL_V(RES());
944 						}
945 
946 						//						int margin = params[i+ccodes+1].to_int();
947 						//sbflat->set_margin_size(Margin(i),margin);
948 					}
949 				} else if (params.size() != ccodes + 1) {
950 					memdelete(f);
951 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid amount of margin parameters for sboxt.");
952 					ERR_FAIL_V(RES());
953 				}
954 
955 				value = sbflat;
956 
957 			} else {
958 				memdelete(f);
959 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid constructor type: '" + type + "'.");
960 				ERR_FAIL_V(RES());
961 			}
962 		}
963 
964 		//parse left and do something with it
965 		String left = l.substr(0, eqpos);
966 
967 		if (reading_library) {
968 
969 			left = left.strip_edges();
970 			if (!left.is_valid_identifier()) {
971 				memdelete(f);
972 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": <LibraryItem> is not a valid identifier.");
973 				ERR_FAIL_V(RES());
974 			}
975 			if (library.has(left)) {
976 				memdelete(f);
977 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Already in library: '" + left + "'.");
978 				ERR_FAIL_V(RES());
979 			}
980 
981 			library[left] = value;
982 		} else {
983 
984 			int pointpos = left.find(".");
985 			if (pointpos == -1) {
986 				memdelete(f);
987 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Expected 'control.item=..' assign syntax.");
988 				ERR_FAIL_V(RES());
989 			}
990 
991 			String control = left.substr(0, pointpos).strip_edges();
992 			if (!control.is_valid_identifier()) {
993 				memdelete(f);
994 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": <Control> is not a valid identifier.");
995 				ERR_FAIL_V(RES());
996 			}
997 			String item = left.substr(pointpos + 1, left.size()).strip_edges();
998 			if (!item.is_valid_identifier()) {
999 				memdelete(f);
1000 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": <Item> is not a valid identifier.");
1001 				ERR_FAIL_V(RES());
1002 			}
1003 
1004 			if (value.get_type() == Variant::NIL) {
1005 				//try to use exiting
1006 				if (Theme::get_default()->has_stylebox(item, control))
1007 					value = Theme::get_default()->get_stylebox(item, control);
1008 				else if (Theme::get_default()->has_font(item, control))
1009 					value = Theme::get_default()->get_font(item, control);
1010 				else if (Theme::get_default()->has_icon(item, control))
1011 					value = Theme::get_default()->get_icon(item, control);
1012 				else if (Theme::get_default()->has_color(item, control))
1013 					value = Theme::get_default()->get_color(item, control);
1014 				else if (Theme::get_default()->has_constant(item, control))
1015 					value = Theme::get_default()->get_constant(item, control);
1016 				else {
1017 					memdelete(f);
1018 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Default not present for: '" + control + "." + item + "'.");
1019 					ERR_FAIL_V(RES());
1020 				}
1021 			}
1022 
1023 			if (value.get_type() == Variant::OBJECT) {
1024 
1025 				Ref<Resource> res = value;
1026 				if (!res.is_valid()) {
1027 
1028 					memdelete(f);
1029 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid resource (NULL).");
1030 					ERR_FAIL_V(RES());
1031 				}
1032 
1033 				if (res->cast_to<StyleBox>()) {
1034 
1035 					theme->set_stylebox(item, control, res);
1036 				} else if (res->cast_to<Font>()) {
1037 					theme->set_font(item, control, res);
1038 				} else if (res->cast_to<Font>()) {
1039 					theme->set_font(item, control, res);
1040 				} else if (res->cast_to<Texture>()) {
1041 					theme->set_icon(item, control, res);
1042 				} else {
1043 					memdelete(f);
1044 					ERR_EXPLAIN(p_path + ":" + itos(line) + ": Invalid resource type.");
1045 					ERR_FAIL_V(RES());
1046 				}
1047 			} else if (value.get_type() == Variant::COLOR) {
1048 
1049 				theme->set_color(item, control, value);
1050 
1051 			} else if (value.get_type() == Variant::INT) {
1052 
1053 				theme->set_constant(item, control, value);
1054 
1055 			} else {
1056 
1057 				memdelete(f);
1058 				ERR_EXPLAIN(p_path + ":" + itos(line) + ": Couldn't even determine what this setting is! what did you do!?");
1059 				ERR_FAIL_V(RES());
1060 			}
1061 		}
1062 	}
1063 
1064 	f->close();
1065 	memdelete(f);
1066 
1067 	if (r_error)
1068 		*r_error = OK;
1069 
1070 	return theme;
1071 }
1072 
get_recognized_extensions(List<String> * p_extensions) const1073 void ResourceFormatLoaderTheme::get_recognized_extensions(List<String> *p_extensions) const {
1074 
1075 	p_extensions->push_back("theme");
1076 }
1077 
handles_type(const String & p_type) const1078 bool ResourceFormatLoaderTheme::handles_type(const String &p_type) const {
1079 
1080 	return p_type == "Theme";
1081 }
1082 
get_resource_type(const String & p_path) const1083 String ResourceFormatLoaderTheme::get_resource_type(const String &p_path) const {
1084 
1085 	if (p_path.extension().to_lower() == "theme")
1086 		return "Theme";
1087 	return "";
1088 }
1089