1/**************************************************************************/
2/* texture_button.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
31#include "texture_button.h"
32
33#include "core/typedefs.h"
34
35#include <stdlib.h>
36
37Size2 TextureButton::get_minimum_size() const {
38 Size2 rscale = Control::get_minimum_size();
39
40 if (!ignore_texture_size) {
41 if (normal.is_null()) {
42 if (pressed.is_null()) {
43 if (hover.is_null()) {
44 if (click_mask.is_null()) {
45 rscale = Size2();
46 } else {
47 rscale = click_mask->get_size();
48 }
49 } else {
50 rscale = hover->get_size();
51 }
52 } else {
53 rscale = pressed->get_size();
54 }
55
56 } else {
57 rscale = normal->get_size();
58 }
59 }
60
61 return rscale.abs();
62}
63
64bool TextureButton::has_point(const Point2 &p_point) const {
65 if (click_mask.is_valid()) {
66 Point2 point = p_point;
67 Rect2 rect;
68 Size2 mask_size = click_mask->get_size();
69
70 if (!_position_rect.has_area()) {
71 rect.size = mask_size;
72 } else if (_tile) {
73 // if the stretch mode is tile we offset the point to keep it inside the mask size
74 rect.size = mask_size;
75 if (_position_rect.has_point(point)) {
76 int cols = (int)Math::ceil(_position_rect.size.x / mask_size.x);
77 int rows = (int)Math::ceil(_position_rect.size.y / mask_size.y);
78 int col = (int)(point.x / mask_size.x) % cols;
79 int row = (int)(point.y / mask_size.y) % rows;
80 point.x -= mask_size.x * col;
81 point.y -= mask_size.y * row;
82 }
83 } else {
84 // we need to transform the point from our scaled / translated image back to our mask image
85 Point2 ofs = _position_rect.position;
86 Size2 scale = mask_size / _position_rect.size;
87
88 switch (stretch_mode) {
89 case STRETCH_KEEP_ASPECT_COVERED: {
90 // if the stretch mode is aspect covered the image uses a texture region so we need to take that into account
91 float min = MIN(scale.x, scale.y);
92 scale.x = min;
93 scale.y = min;
94 ofs -= _texture_region.position / min;
95 } break;
96 default: {
97 // FIXME: Why a switch if we only handle one enum value?
98 }
99 }
100
101 // offset and scale the new point position to adjust it to the bitmask size
102 point -= ofs;
103 point *= scale;
104
105 // finally, we need to check if the point is inside a rectangle with a position >= 0,0 and a size <= mask_size
106 rect.position = Point2(MAX(0, _texture_region.position.x), MAX(0, _texture_region.position.y));
107 rect.size = Size2(MIN(mask_size.x, _texture_region.size.x), MIN(mask_size.y, _texture_region.size.y));
108 }
109
110 if (!rect.has_point(point)) {
111 return false;
112 }
113
114 Point2i p = point;
115 return click_mask->get_bitv(p);
116 }
117
118 return Control::has_point(p_point);
119}
120
121void TextureButton::_notification(int p_what) {
122 switch (p_what) {
123 case NOTIFICATION_DRAW: {
124 DrawMode draw_mode = get_draw_mode();
125
126 Ref<Texture2D> texdraw;
127
128 switch (draw_mode) {
129 case DRAW_NORMAL: {
130 if (normal.is_valid()) {
131 texdraw = normal;
132 }
133 } break;
134 case DRAW_HOVER_PRESSED:
135 case DRAW_PRESSED: {
136 if (pressed.is_null()) {
137 if (hover.is_null()) {
138 if (normal.is_valid()) {
139 texdraw = normal;
140 }
141 } else {
142 texdraw = hover;
143 }
144
145 } else {
146 texdraw = pressed;
147 }
148 } break;
149 case DRAW_HOVER: {
150 if (hover.is_null()) {
151 if (pressed.is_valid() && is_pressed()) {
152 texdraw = pressed;
153 } else if (normal.is_valid()) {
154 texdraw = normal;
155 }
156 } else {
157 texdraw = hover;
158 }
159 } break;
160 case DRAW_DISABLED: {
161 if (disabled.is_null()) {
162 if (normal.is_valid()) {
163 texdraw = normal;
164 }
165 } else {
166 texdraw = disabled;
167 }
168 } break;
169 }
170
171 Point2 ofs;
172 Size2 size;
173 bool draw_focus = (has_focus() && focused.is_valid());
174
175 // If no other texture is valid, try using focused texture.
176 bool draw_focus_only = draw_focus && !texdraw.is_valid();
177 if (draw_focus_only) {
178 texdraw = focused;
179 }
180
181 if (texdraw.is_valid()) {
182 size = texdraw->get_size();
183 _texture_region = Rect2(Point2(), texdraw->get_size());
184 _tile = false;
185 switch (stretch_mode) {
186 case STRETCH_KEEP:
187 size = texdraw->get_size();
188 break;
189 case STRETCH_SCALE:
190 size = get_size();
191 break;
192 case STRETCH_TILE:
193 size = get_size();
194 _tile = true;
195 break;
196 case STRETCH_KEEP_CENTERED:
197 ofs = (get_size() - texdraw->get_size()) / 2;
198 size = texdraw->get_size();
199 break;
200 case STRETCH_KEEP_ASPECT_CENTERED:
201 case STRETCH_KEEP_ASPECT: {
202 Size2 _size = get_size();
203 float tex_width = texdraw->get_width() * _size.height / texdraw->get_height();
204 float tex_height = _size.height;
205
206 if (tex_width > _size.width) {
207 tex_width = _size.width;
208 tex_height = texdraw->get_height() * tex_width / texdraw->get_width();
209 }
210
211 if (stretch_mode == STRETCH_KEEP_ASPECT_CENTERED) {
212 ofs.x = (_size.width - tex_width) / 2;
213 ofs.y = (_size.height - tex_height) / 2;
214 }
215 size.width = tex_width;
216 size.height = tex_height;
217 } break;
218 case STRETCH_KEEP_ASPECT_COVERED: {
219 size = get_size();
220 Size2 tex_size = texdraw->get_size();
221 Size2 scale_size(size.width / tex_size.width, size.height / tex_size.height);
222 float scale = scale_size.width > scale_size.height ? scale_size.width : scale_size.height;
223 Size2 scaled_tex_size = tex_size * scale;
224 Point2 ofs2 = ((scaled_tex_size - size) / scale).abs() / 2.0f;
225 _texture_region = Rect2(ofs2, size / scale);
226 } break;
227 }
228
229 _position_rect = Rect2(ofs, size);
230
231 size.width *= hflip ? -1.0f : 1.0f;
232 size.height *= vflip ? -1.0f : 1.0f;
233
234 if (draw_focus_only) {
235 // Do nothing, we only needed to calculate the rectangle.
236 } else if (_tile) {
237 draw_texture_rect(texdraw, Rect2(ofs, size), _tile);
238 } else {
239 draw_texture_rect_region(texdraw, Rect2(ofs, size), _texture_region);
240 }
241 } else {
242 _position_rect = Rect2();
243 }
244
245 if (draw_focus) {
246 draw_texture_rect(focused, Rect2(ofs, size), false);
247 };
248 } break;
249 }
250}
251
252void TextureButton::_bind_methods() {
253 ClassDB::bind_method(D_METHOD("set_texture_normal", "texture"), &TextureButton::set_texture_normal);
254 ClassDB::bind_method(D_METHOD("set_texture_pressed", "texture"), &TextureButton::set_texture_pressed);
255 ClassDB::bind_method(D_METHOD("set_texture_hover", "texture"), &TextureButton::set_texture_hover);
256 ClassDB::bind_method(D_METHOD("set_texture_disabled", "texture"), &TextureButton::set_texture_disabled);
257 ClassDB::bind_method(D_METHOD("set_texture_focused", "texture"), &TextureButton::set_texture_focused);
258 ClassDB::bind_method(D_METHOD("set_click_mask", "mask"), &TextureButton::set_click_mask);
259 ClassDB::bind_method(D_METHOD("set_ignore_texture_size", "ignore"), &TextureButton::set_ignore_texture_size);
260 ClassDB::bind_method(D_METHOD("set_stretch_mode", "mode"), &TextureButton::set_stretch_mode);
261 ClassDB::bind_method(D_METHOD("set_flip_h", "enable"), &TextureButton::set_flip_h);
262 ClassDB::bind_method(D_METHOD("is_flipped_h"), &TextureButton::is_flipped_h);
263 ClassDB::bind_method(D_METHOD("set_flip_v", "enable"), &TextureButton::set_flip_v);
264 ClassDB::bind_method(D_METHOD("is_flipped_v"), &TextureButton::is_flipped_v);
265
266 ClassDB::bind_method(D_METHOD("get_texture_normal"), &TextureButton::get_texture_normal);
267 ClassDB::bind_method(D_METHOD("get_texture_pressed"), &TextureButton::get_texture_pressed);
268 ClassDB::bind_method(D_METHOD("get_texture_hover"), &TextureButton::get_texture_hover);
269 ClassDB::bind_method(D_METHOD("get_texture_disabled"), &TextureButton::get_texture_disabled);
270 ClassDB::bind_method(D_METHOD("get_texture_focused"), &TextureButton::get_texture_focused);
271 ClassDB::bind_method(D_METHOD("get_click_mask"), &TextureButton::get_click_mask);
272 ClassDB::bind_method(D_METHOD("get_ignore_texture_size"), &TextureButton::get_ignore_texture_size);
273 ClassDB::bind_method(D_METHOD("get_stretch_mode"), &TextureButton::get_stretch_mode);
274
275 ADD_GROUP("Textures", "texture_");
276 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_normal", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_normal", "get_texture_normal");
277 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_pressed", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_pressed", "get_texture_pressed");
278 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_hover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_hover", "get_texture_hover");
279 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_disabled", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_disabled", "get_texture_disabled");
280 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_focused", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_focused", "get_texture_focused");
281 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_click_mask", PROPERTY_HINT_RESOURCE_TYPE, "BitMap"), "set_click_mask", "get_click_mask");
282 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_texture_size", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_ignore_texture_size", "get_ignore_texture_size");
283 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");
284 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_h", "is_flipped_h");
285 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v", PROPERTY_HINT_RESOURCE_TYPE, "bool"), "set_flip_v", "is_flipped_v");
286
287 BIND_ENUM_CONSTANT(STRETCH_SCALE);
288 BIND_ENUM_CONSTANT(STRETCH_TILE);
289 BIND_ENUM_CONSTANT(STRETCH_KEEP);
290 BIND_ENUM_CONSTANT(STRETCH_KEEP_CENTERED);
291 BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT);
292 BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_CENTERED);
293 BIND_ENUM_CONSTANT(STRETCH_KEEP_ASPECT_COVERED);
294}
295
296void TextureButton::set_texture_normal(const Ref<Texture2D> &p_normal) {
297 _set_texture(&normal, p_normal);
298}
299
300void TextureButton::set_texture_pressed(const Ref<Texture2D> &p_pressed) {
301 _set_texture(&pressed, p_pressed);
302}
303
304void TextureButton::set_texture_hover(const Ref<Texture2D> &p_hover) {
305 _set_texture(&hover, p_hover);
306}
307
308void TextureButton::set_texture_disabled(const Ref<Texture2D> &p_disabled) {
309 _set_texture(&disabled, p_disabled);
310}
311
312void TextureButton::set_click_mask(const Ref<BitMap> &p_click_mask) {
313 if (click_mask == p_click_mask) {
314 return;
315 }
316 click_mask = p_click_mask;
317 _texture_changed();
318}
319
320Ref<Texture2D> TextureButton::get_texture_normal() const {
321 return normal;
322}
323
324Ref<Texture2D> TextureButton::get_texture_pressed() const {
325 return pressed;
326}
327
328Ref<Texture2D> TextureButton::get_texture_hover() const {
329 return hover;
330}
331
332Ref<Texture2D> TextureButton::get_texture_disabled() const {
333 return disabled;
334}
335
336Ref<BitMap> TextureButton::get_click_mask() const {
337 return click_mask;
338}
339
340Ref<Texture2D> TextureButton::get_texture_focused() const {
341 return focused;
342};
343
344void TextureButton::set_texture_focused(const Ref<Texture2D> &p_focused) {
345 focused = p_focused;
346};
347
348void TextureButton::_set_texture(Ref<Texture2D> *p_destination, const Ref<Texture2D> &p_texture) {
349 DEV_ASSERT(p_destination);
350 Ref<Texture2D> &destination = *p_destination;
351 if (destination == p_texture) {
352 return;
353 }
354 if (destination.is_valid()) {
355 destination->disconnect_changed(callable_mp(this, &TextureButton::_texture_changed));
356 }
357 destination = p_texture;
358 if (destination.is_valid()) {
359 // Pass `CONNECT_REFERENCE_COUNTED` to avoid early disconnect in case the same texture is assigned to different "slots".
360 destination->connect_changed(callable_mp(this, &TextureButton::_texture_changed), CONNECT_REFERENCE_COUNTED);
361 }
362 _texture_changed();
363}
364
365void TextureButton::_texture_changed() {
366 queue_redraw();
367 update_minimum_size();
368}
369
370bool TextureButton::get_ignore_texture_size() const {
371 return ignore_texture_size;
372}
373
374void TextureButton::set_ignore_texture_size(bool p_ignore) {
375 if (ignore_texture_size == p_ignore) {
376 return;
377 }
378
379 ignore_texture_size = p_ignore;
380 update_minimum_size();
381 queue_redraw();
382}
383
384void TextureButton::set_stretch_mode(StretchMode p_stretch_mode) {
385 if (stretch_mode == p_stretch_mode) {
386 return;
387 }
388
389 stretch_mode = p_stretch_mode;
390 queue_redraw();
391}
392
393TextureButton::StretchMode TextureButton::get_stretch_mode() const {
394 return stretch_mode;
395}
396
397void TextureButton::set_flip_h(bool p_flip) {
398 if (hflip == p_flip) {
399 return;
400 }
401
402 hflip = p_flip;
403 queue_redraw();
404}
405
406bool TextureButton::is_flipped_h() const {
407 return hflip;
408}
409
410void TextureButton::set_flip_v(bool p_flip) {
411 if (vflip == p_flip) {
412 return;
413 }
414
415 vflip = p_flip;
416 queue_redraw();
417}
418
419bool TextureButton::is_flipped_v() const {
420 return vflip;
421}
422
423TextureButton::TextureButton() {}
424