1 /*************************************************************************/
2 /*  texture_button.cpp                                                   */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 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 
31 #include "texture_button.h"
32 #include "core/typedefs.h"
33 #include <stdlib.h>
34 
get_minimum_size() const35 Size2 TextureButton::get_minimum_size() const {
36 
37 	Size2 rscale = Control::get_minimum_size();
38 
39 	if (!expand) {
40 		if (normal.is_null()) {
41 			if (pressed.is_null()) {
42 				if (hover.is_null())
43 					if (click_mask.is_null())
44 						rscale = Size2();
45 					else
46 						rscale = click_mask->get_size();
47 				else
48 					rscale = hover->get_size();
49 			} else
50 				rscale = pressed->get_size();
51 
52 		} else
53 			rscale = normal->get_size();
54 	}
55 
56 	return rscale.abs();
57 }
58 
has_point(const Point2 & p_point) const59 bool TextureButton::has_point(const Point2 &p_point) const {
60 
61 	if (click_mask.is_valid()) {
62 
63 		Point2 point = p_point;
64 		Rect2 rect = Rect2();
65 		Size2 mask_size = click_mask->get_size();
66 
67 		if (_position_rect.has_no_area()) {
68 			rect.size = mask_size;
69 		} else if (_tile) {
70 			// if the stretch mode is tile we offset the point to keep it inside the mask size
71 			rect.size = mask_size;
72 			if (_position_rect.has_point(point)) {
73 				int cols = (int)Math::ceil(_position_rect.size.x / mask_size.x);
74 				int rows = (int)Math::ceil(_position_rect.size.y / mask_size.y);
75 				int col = (int)(point.x / mask_size.x) % cols;
76 				int row = (int)(point.y / mask_size.y) % rows;
77 				point.x -= mask_size.x * col;
78 				point.y -= mask_size.y * row;
79 			}
80 		} else {
81 			// we need to transform the point from our scaled / translated image back to our mask image
82 			Point2 ofs = _position_rect.position;
83 			Size2 scale = mask_size / _position_rect.size;
84 
85 			switch (stretch_mode) {
86 				case STRETCH_KEEP_ASPECT_COVERED: {
87 					// if the stretch mode is aspect covered the image uses a texture region so we need to take that into account
88 					float min = MIN(scale.x, scale.y);
89 					scale.x = min;
90 					scale.y = min;
91 					ofs -= _texture_region.position / min;
92 				} break;
93 				default: {
94 					// FIXME: Why a switch if we only handle one enum value?
95 				}
96 			}
97 
98 			// offset and scale the new point position to adjust it to the bitmask size
99 			point -= ofs;
100 			point *= scale;
101 
102 			// finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size
103 			rect.position = Point2(MAX(0, _texture_region.position.x), MAX(0, _texture_region.position.y));
104 			rect.size = Size2(MIN(mask_size.x, _texture_region.size.x), MIN(mask_size.y, _texture_region.size.y));
105 		}
106 
107 		if (!rect.has_point(point)) {
108 			return false;
109 		}
110 
111 		Point2i p = point;
112 		return click_mask->get_bit(p);
113 	}
114 
115 	return Control::has_point(p_point);
116 }
117 
_notification(int p_what)118 void TextureButton::_notification(int p_what) {
119 
120 	switch (p_what) {
121 
122 		case NOTIFICATION_DRAW: {
123 			DrawMode draw_mode = get_draw_mode();
124 
125 			Ref<Texture> texdraw;
126 
127 			switch (draw_mode) {
128 				case DRAW_NORMAL: {
129 
130 					if (normal.is_valid())
131 						texdraw = normal;
132 				} break;
133 				case DRAW_HOVER_PRESSED:
134 				case DRAW_PRESSED: {
135 
136 					if (pressed.is_null()) {
137 						if (hover.is_null()) {
138 							if (normal.is_valid())
139 								texdraw = normal;
140 						} else
141 							texdraw = hover;
142 
143 					} else
144 						texdraw = pressed;
145 				} break;
146 				case DRAW_HOVER: {
147 
148 					if (hover.is_null()) {
149 						if (pressed.is_valid() && is_pressed())
150 							texdraw = pressed;
151 						else if (normal.is_valid())
152 							texdraw = normal;
153 					} else
154 						texdraw = hover;
155 				} break;
156 				case DRAW_DISABLED: {
157 
158 					if (disabled.is_null()) {
159 						if (normal.is_valid())
160 							texdraw = normal;
161 					} else
162 						texdraw = disabled;
163 				} break;
164 			}
165 
166 			if (texdraw.is_valid()) {
167 				Point2 ofs;
168 				Size2 size = texdraw->get_size();
169 				_texture_region = Rect2(Point2(), texdraw->get_size());
170 				_tile = false;
171 				if (expand) {
172 					switch (stretch_mode) {
173 						case STRETCH_KEEP:
174 							size = texdraw->get_size();
175 							break;
176 						case STRETCH_SCALE:
177 							size = get_size();
178 							break;
179 						case STRETCH_TILE:
180 							size = get_size();
181 							_tile = true;
182 							break;
183 						case STRETCH_KEEP_CENTERED:
184 							ofs = (get_size() - texdraw->get_size()) / 2;
185 							size = texdraw->get_size();
186 							break;
187 						case STRETCH_KEEP_ASPECT_CENTERED:
188 						case STRETCH_KEEP_ASPECT: {
189 							Size2 _size = get_size();
190 							float tex_width = texdraw->get_width() * _size.height / texdraw->get_height();
191 							float tex_height = _size.height;
192 
193 							if (tex_width > _size.width) {
194 								tex_width = _size.width;
195 								tex_height = texdraw->get_height() * tex_width / texdraw->get_width();
196 							}
197 
198 							if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) {
199 								ofs.x = (_size.width - tex_width) / 2;
200 								ofs.y = (_size.height - tex_height) / 2;
201 							}
202 							size.width = tex_width;
203 							size.height = tex_height;
204 						} break;
205 						case STRETCH_KEEP_ASPECT_COVERED: {
206 							size = get_size();
207 							Size2 tex_size = texdraw->get_size();
208 							Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height);
209 							float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height;
210 							Size2 scaled_tex_size = tex_size * scale;
211 							Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f;
212 							_texture_region = Rect2(ofs2, size / scale);
213 						} break;
214 					}
215 				}
216 
217 				_position_rect = Rect2(ofs, size);
218 				if (_tile) {
219 					draw_texture_rect(texdraw, _position_rect, _tile);
220 				} else {
221 					draw_texture_rect_region(texdraw, _position_rect, _texture_region);
222 				}
223 			} else {
224 				_position_rect = Rect2();
225 			}
226 
227 			if (has_focus() && focused.is_valid()) {
228 				draw_texture_rect(focused, _position_rect, false);
229 			};
230 		} break;
231 	}
232 }
233 
_bind_methods()234 void TextureButton::_bind_methods() {
235 
236 	ClassDB::bind_method(D_METHOD("set_normal_texture", "texture"), &TextureButton::set_normal_texture);
237 	ClassDB::bind_method(D_METHOD("set_pressed_texture", "texture"), &TextureButton::set_pressed_texture);
238 	ClassDB::bind_method(D_METHOD("set_hover_texture", "texture"), &TextureButton::set_hover_texture);
239 	ClassDB::bind_method(D_METHOD("set_disabled_texture", "texture"), &TextureButton::set_disabled_texture);
240 	ClassDB::bind_method(D_METHOD("set_focused_texture", "texture"), &TextureButton::set_focused_texture);
241 	ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask);
242 	ClassDB::bind_method(D_METHOD("set_expand", "p_expand"), &TextureButton::set_expand);
243 	ClassDB::bind_method(D_METHOD("set_stretch_mode", "p_mode"), &TextureButton::set_stretch_mode);
244 
245 	ClassDB::bind_method(D_METHOD("get_normal_texture"), &TextureButton::get_normal_texture);
246 	ClassDB::bind_method(D_METHOD("get_pressed_texture"), &TextureButton::get_pressed_texture);
247 	ClassDB::bind_method(D_METHOD("get_hover_texture"), &TextureButton::get_hover_texture);
248 	ClassDB::bind_method(D_METHOD("get_disabled_texture"), &TextureButton::get_disabled_texture);
249 	ClassDB::bind_method(D_METHOD("get_focused_texture"), &TextureButton::get_focused_texture);
250 	ClassDB::bind_method(D_METHOD("get_click_mask"), &TextureButton::get_click_mask);
251 	ClassDB::bind_method(D_METHOD("get_expand"), &TextureButton::get_expand);
252 	ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureButton::get_stretch_mode);
253 
254 	ADD_GROUP("Textures", "texture_");
255 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_normal_texture", "get_normal_texture");
256 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_pressed_texture", "get_pressed_texture");
257 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_hover", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_hover_texture", "get_hover_texture");
258 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_disabled_texture", "get_disabled_texture");
259 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_focused_texture", "get_focused_texture");
260 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask");
261 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_expand", "get_expand");
262 	ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Scale,Tile,Keep,Keep Centered,Keep Aspect,Keep Aspect Centered,Keep Aspect Covered"), "set_stretch_mode", "get_stretch_mode");
263 
264 	BIND_ENUM_CONSTANT(STRETCH_SCALE);
265 	BIND_ENUM_CONSTANT(STRETCH_TILE);
266 	BIND_ENUM_CONSTANT(STRETCH_KEEP);
267 	BIND_ENUM_CONSTANT(STRETCH_KEEP_CENTERED);
268 	BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT);
269 	BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_CENTERED);
270 	BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_COVERED);
271 }
272 
set_normal_texture(const Ref<Texture> & p_normal)273 void TextureButton::set_normal_texture(const Ref<Texture> &p_normal) {
274 
275 	normal = p_normal;
276 	update();
277 	minimum_size_changed();
278 }
279 
set_pressed_texture(const Ref<Texture> & p_pressed)280 void TextureButton::set_pressed_texture(const Ref<Texture> &p_pressed) {
281 
282 	pressed = p_pressed;
283 	update();
284 }
set_hover_texture(const Ref<Texture> & p_hover)285 void TextureButton::set_hover_texture(const Ref<Texture> &p_hover) {
286 
287 	hover = p_hover;
288 	update();
289 }
set_disabled_texture(const Ref<Texture> & p_disabled)290 void TextureButton::set_disabled_texture(const Ref<Texture> &p_disabled) {
291 
292 	disabled = p_disabled;
293 	update();
294 }
set_click_mask(const Ref<BitMap> & p_click_mask)295 void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) {
296 
297 	click_mask = p_click_mask;
298 	update();
299 }
300 
get_normal_texture() const301 Ref<Texture> TextureButton::get_normal_texture() const {
302 
303 	return normal;
304 }
get_pressed_texture() const305 Ref<Texture> TextureButton::get_pressed_texture() const {
306 
307 	return pressed;
308 }
get_hover_texture() const309 Ref<Texture> TextureButton::get_hover_texture() const {
310 
311 	return hover;
312 }
get_disabled_texture() const313 Ref<Texture> TextureButton::get_disabled_texture() const {
314 
315 	return disabled;
316 }
get_click_mask() const317 Ref<BitMap> TextureButton::get_click_mask() const {
318 
319 	return click_mask;
320 }
321 
get_focused_texture() const322 Ref<Texture> TextureButton::get_focused_texture() const {
323 
324 	return focused;
325 };
326 
set_focused_texture(const Ref<Texture> & p_focused)327 void TextureButton::set_focused_texture(const Ref<Texture> &p_focused) {
328 
329 	focused = p_focused;
330 };
331 
get_expand() const332 bool TextureButton::get_expand() const {
333 	return expand;
334 }
335 
set_expand(bool p_expand)336 void TextureButton::set_expand(bool p_expand) {
337 	expand = p_expand;
338 	minimum_size_changed();
339 	update();
340 }
341 
set_stretch_mode(StretchMode p_stretch_mode)342 void TextureButton::set_stretch_mode(StretchMode p_stretch_mode) {
343 	stretch_mode = p_stretch_mode;
344 	update();
345 }
346 
get_stretch_mode() const347 TextureButton::StretchMode TextureButton::get_stretch_mode() const {
348 	return stretch_mode;
349 }
350 
TextureButton()351 TextureButton::TextureButton() {
352 	expand = false;
353 	stretch_mode = STRETCH_SCALE;
354 
355 	_texture_region = Rect2();
356 	_position_rect = Rect2();
357 	_tile = false;
358 }
359