1/**************************************************************************/
2/* item_list.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 "item_list.h"
32
33#include "core/config/project_settings.h"
34#include "core/os/os.h"
35#include "core/string/translation.h"
36#include "scene/theme/theme_db.h"
37
38void ItemList::_shape_text(int p_idx) {
39 Item &item = items.write[p_idx];
40
41 item.text_buf->clear();
42 if (item.text_direction == Control::TEXT_DIRECTION_INHERITED) {
43 item.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
44 } else {
45 item.text_buf->set_direction((TextServer::Direction)item.text_direction);
46 }
47 item.text_buf->add_string(item.text, theme_cache.font, theme_cache.font_size, item.language);
48 if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
49 item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
50 } else {
51 item.text_buf->set_break_flags(TextServer::BREAK_NONE);
52 }
53 item.text_buf->set_text_overrun_behavior(text_overrun_behavior);
54 item.text_buf->set_max_lines_visible(max_text_lines);
55}
56
57int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bool p_selectable) {
58 Item item;
59 item.icon = p_texture;
60 item.text = p_item;
61 item.selectable = p_selectable;
62 items.push_back(item);
63 int item_id = items.size() - 1;
64
65 _shape_text(items.size() - 1);
66
67 queue_redraw();
68 shape_changed = true;
69 notify_property_list_changed();
70 return item_id;
71}
72
73int ItemList::add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable) {
74 Item item;
75 item.icon = p_item;
76 item.selectable = p_selectable;
77 items.push_back(item);
78 int item_id = items.size() - 1;
79
80 queue_redraw();
81 shape_changed = true;
82 notify_property_list_changed();
83 return item_id;
84}
85
86void ItemList::set_item_text(int p_idx, const String &p_text) {
87 if (p_idx < 0) {
88 p_idx += get_item_count();
89 }
90 ERR_FAIL_INDEX(p_idx, items.size());
91
92 if (items[p_idx].text == p_text) {
93 return;
94 }
95
96 items.write[p_idx].text = p_text;
97 _shape_text(p_idx);
98 queue_redraw();
99 shape_changed = true;
100}
101
102String ItemList::get_item_text(int p_idx) const {
103 ERR_FAIL_INDEX_V(p_idx, items.size(), String());
104 return items[p_idx].text;
105}
106
107void ItemList::set_item_text_direction(int p_idx, Control::TextDirection p_text_direction) {
108 if (p_idx < 0) {
109 p_idx += get_item_count();
110 }
111 ERR_FAIL_INDEX(p_idx, items.size());
112 ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
113 if (items[p_idx].text_direction != p_text_direction) {
114 items.write[p_idx].text_direction = p_text_direction;
115 _shape_text(p_idx);
116 queue_redraw();
117 }
118}
119
120Control::TextDirection ItemList::get_item_text_direction(int p_idx) const {
121 ERR_FAIL_INDEX_V(p_idx, items.size(), TEXT_DIRECTION_INHERITED);
122 return items[p_idx].text_direction;
123}
124
125void ItemList::set_item_language(int p_idx, const String &p_language) {
126 if (p_idx < 0) {
127 p_idx += get_item_count();
128 }
129 ERR_FAIL_INDEX(p_idx, items.size());
130 if (items[p_idx].language != p_language) {
131 items.write[p_idx].language = p_language;
132 _shape_text(p_idx);
133 queue_redraw();
134 }
135}
136
137String ItemList::get_item_language(int p_idx) const {
138 ERR_FAIL_INDEX_V(p_idx, items.size(), "");
139 return items[p_idx].language;
140}
141
142void ItemList::set_item_tooltip_enabled(int p_idx, const bool p_enabled) {
143 if (p_idx < 0) {
144 p_idx += get_item_count();
145 }
146 ERR_FAIL_INDEX(p_idx, items.size());
147 items.write[p_idx].tooltip_enabled = p_enabled;
148}
149
150bool ItemList::is_item_tooltip_enabled(int p_idx) const {
151 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
152 return items[p_idx].tooltip_enabled;
153}
154
155void ItemList::set_item_tooltip(int p_idx, const String &p_tooltip) {
156 if (p_idx < 0) {
157 p_idx += get_item_count();
158 }
159 ERR_FAIL_INDEX(p_idx, items.size());
160
161 if (items[p_idx].tooltip == p_tooltip) {
162 return;
163 }
164
165 items.write[p_idx].tooltip = p_tooltip;
166 queue_redraw();
167 shape_changed = true;
168}
169
170String ItemList::get_item_tooltip(int p_idx) const {
171 ERR_FAIL_INDEX_V(p_idx, items.size(), String());
172 return items[p_idx].tooltip;
173}
174
175void ItemList::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
176 if (p_idx < 0) {
177 p_idx += get_item_count();
178 }
179 ERR_FAIL_INDEX(p_idx, items.size());
180
181 if (items[p_idx].icon == p_icon) {
182 return;
183 }
184
185 items.write[p_idx].icon = p_icon;
186 queue_redraw();
187 shape_changed = true;
188}
189
190Ref<Texture2D> ItemList::get_item_icon(int p_idx) const {
191 ERR_FAIL_INDEX_V(p_idx, items.size(), Ref<Texture2D>());
192
193 return items[p_idx].icon;
194}
195
196void ItemList::set_item_icon_transposed(int p_idx, const bool p_transposed) {
197 if (p_idx < 0) {
198 p_idx += get_item_count();
199 }
200 ERR_FAIL_INDEX(p_idx, items.size());
201
202 if (items[p_idx].icon_transposed == p_transposed) {
203 return;
204 }
205
206 items.write[p_idx].icon_transposed = p_transposed;
207 queue_redraw();
208 shape_changed = true;
209}
210
211bool ItemList::is_item_icon_transposed(int p_idx) const {
212 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
213
214 return items[p_idx].icon_transposed;
215}
216
217void ItemList::set_item_icon_region(int p_idx, const Rect2 &p_region) {
218 if (p_idx < 0) {
219 p_idx += get_item_count();
220 }
221 ERR_FAIL_INDEX(p_idx, items.size());
222
223 if (items[p_idx].icon_region == p_region) {
224 return;
225 }
226
227 items.write[p_idx].icon_region = p_region;
228 queue_redraw();
229 shape_changed = true;
230}
231
232Rect2 ItemList::get_item_icon_region(int p_idx) const {
233 ERR_FAIL_INDEX_V(p_idx, items.size(), Rect2());
234
235 return items[p_idx].icon_region;
236}
237
238void ItemList::set_item_icon_modulate(int p_idx, const Color &p_modulate) {
239 if (p_idx < 0) {
240 p_idx += get_item_count();
241 }
242 ERR_FAIL_INDEX(p_idx, items.size());
243
244 if (items[p_idx].icon_modulate == p_modulate) {
245 return;
246 }
247
248 items.write[p_idx].icon_modulate = p_modulate;
249 queue_redraw();
250}
251
252Color ItemList::get_item_icon_modulate(int p_idx) const {
253 ERR_FAIL_INDEX_V(p_idx, items.size(), Color());
254
255 return items[p_idx].icon_modulate;
256}
257
258void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_color) {
259 if (p_idx < 0) {
260 p_idx += get_item_count();
261 }
262 ERR_FAIL_INDEX(p_idx, items.size());
263
264 if (items[p_idx].custom_bg == p_custom_bg_color) {
265 return;
266 }
267
268 items.write[p_idx].custom_bg = p_custom_bg_color;
269 queue_redraw();
270}
271
272Color ItemList::get_item_custom_bg_color(int p_idx) const {
273 ERR_FAIL_INDEX_V(p_idx, items.size(), Color());
274
275 return items[p_idx].custom_bg;
276}
277
278void ItemList::set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color) {
279 if (p_idx < 0) {
280 p_idx += get_item_count();
281 }
282 ERR_FAIL_INDEX(p_idx, items.size());
283
284 if (items[p_idx].custom_fg == p_custom_fg_color) {
285 return;
286 }
287
288 items.write[p_idx].custom_fg = p_custom_fg_color;
289 queue_redraw();
290}
291
292Color ItemList::get_item_custom_fg_color(int p_idx) const {
293 ERR_FAIL_INDEX_V(p_idx, items.size(), Color());
294
295 return items[p_idx].custom_fg;
296}
297
298Rect2 ItemList::get_item_rect(int p_idx, bool p_expand) const {
299 ERR_FAIL_INDEX_V(p_idx, items.size(), Rect2());
300
301 Rect2 ret = items[p_idx].rect_cache;
302 ret.position += theme_cache.panel_style->get_offset();
303
304 if (p_expand && p_idx % current_columns == current_columns - 1) {
305 ret.size.width = get_size().width - ret.position.x;
306 }
307 return ret;
308}
309
310void ItemList::set_item_tag_icon(int p_idx, const Ref<Texture2D> &p_tag_icon) {
311 if (p_idx < 0) {
312 p_idx += get_item_count();
313 }
314 ERR_FAIL_INDEX(p_idx, items.size());
315
316 if (items[p_idx].tag_icon == p_tag_icon) {
317 return;
318 }
319
320 items.write[p_idx].tag_icon = p_tag_icon;
321 queue_redraw();
322 shape_changed = true;
323}
324
325void ItemList::set_item_selectable(int p_idx, bool p_selectable) {
326 if (p_idx < 0) {
327 p_idx += get_item_count();
328 }
329 ERR_FAIL_INDEX(p_idx, items.size());
330
331 items.write[p_idx].selectable = p_selectable;
332}
333
334bool ItemList::is_item_selectable(int p_idx) const {
335 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
336 return items[p_idx].selectable;
337}
338
339void ItemList::set_item_disabled(int p_idx, bool p_disabled) {
340 if (p_idx < 0) {
341 p_idx += get_item_count();
342 }
343 ERR_FAIL_INDEX(p_idx, items.size());
344
345 if (items[p_idx].disabled == p_disabled) {
346 return;
347 }
348
349 items.write[p_idx].disabled = p_disabled;
350 queue_redraw();
351}
352
353bool ItemList::is_item_disabled(int p_idx) const {
354 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
355 return items[p_idx].disabled;
356}
357
358void ItemList::set_item_metadata(int p_idx, const Variant &p_metadata) {
359 if (p_idx < 0) {
360 p_idx += get_item_count();
361 }
362 ERR_FAIL_INDEX(p_idx, items.size());
363
364 if (items[p_idx].metadata == p_metadata) {
365 return;
366 }
367
368 items.write[p_idx].metadata = p_metadata;
369 queue_redraw();
370 shape_changed = true;
371}
372
373Variant ItemList::get_item_metadata(int p_idx) const {
374 ERR_FAIL_INDEX_V(p_idx, items.size(), Variant());
375 return items[p_idx].metadata;
376}
377
378void ItemList::select(int p_idx, bool p_single) {
379 ERR_FAIL_INDEX(p_idx, items.size());
380
381 if (p_single || select_mode == SELECT_SINGLE) {
382 if (!items[p_idx].selectable || items[p_idx].disabled) {
383 return;
384 }
385
386 for (int i = 0; i < items.size(); i++) {
387 items.write[i].selected = p_idx == i;
388 }
389
390 current = p_idx;
391 ensure_selected_visible = false;
392 } else {
393 if (items[p_idx].selectable && !items[p_idx].disabled) {
394 items.write[p_idx].selected = true;
395 }
396 }
397 queue_redraw();
398}
399
400void ItemList::deselect(int p_idx) {
401 ERR_FAIL_INDEX(p_idx, items.size());
402
403 if (select_mode != SELECT_MULTI) {
404 items.write[p_idx].selected = false;
405 current = -1;
406 } else {
407 items.write[p_idx].selected = false;
408 }
409 queue_redraw();
410}
411
412void ItemList::deselect_all() {
413 if (items.size() < 1) {
414 return;
415 }
416
417 for (int i = 0; i < items.size(); i++) {
418 items.write[i].selected = false;
419 }
420 current = -1;
421 queue_redraw();
422}
423
424bool ItemList::is_selected(int p_idx) const {
425 ERR_FAIL_INDEX_V(p_idx, items.size(), false);
426
427 return items[p_idx].selected;
428}
429
430void ItemList::set_current(int p_current) {
431 ERR_FAIL_INDEX(p_current, items.size());
432
433 if (current == p_current) {
434 return;
435 }
436
437 if (select_mode == SELECT_SINGLE) {
438 select(p_current, true);
439 } else {
440 current = p_current;
441 queue_redraw();
442 }
443}
444
445int ItemList::get_current() const {
446 return current;
447}
448
449void ItemList::move_item(int p_from_idx, int p_to_idx) {
450 ERR_FAIL_INDEX(p_from_idx, items.size());
451 ERR_FAIL_INDEX(p_to_idx, items.size());
452
453 if (is_anything_selected() && get_selected_items()[0] == p_from_idx) {
454 current = p_to_idx;
455 }
456
457 Item item = items[p_from_idx];
458 items.remove_at(p_from_idx);
459 items.insert(p_to_idx, item);
460
461 queue_redraw();
462 shape_changed = true;
463 notify_property_list_changed();
464}
465
466void ItemList::set_item_count(int p_count) {
467 ERR_FAIL_COND(p_count < 0);
468
469 if (items.size() == p_count) {
470 return;
471 }
472
473 items.resize(p_count);
474 queue_redraw();
475 shape_changed = true;
476 notify_property_list_changed();
477}
478
479int ItemList::get_item_count() const {
480 return items.size();
481}
482
483void ItemList::remove_item(int p_idx) {
484 ERR_FAIL_INDEX(p_idx, items.size());
485
486 items.remove_at(p_idx);
487 if (current == p_idx) {
488 current = -1;
489 }
490 queue_redraw();
491 shape_changed = true;
492 defer_select_single = -1;
493 notify_property_list_changed();
494}
495
496void ItemList::clear() {
497 items.clear();
498 current = -1;
499 ensure_selected_visible = false;
500 queue_redraw();
501 shape_changed = true;
502 defer_select_single = -1;
503 notify_property_list_changed();
504}
505
506void ItemList::set_fixed_column_width(int p_size) {
507 ERR_FAIL_COND(p_size < 0);
508
509 if (fixed_column_width == p_size) {
510 return;
511 }
512
513 fixed_column_width = p_size;
514 queue_redraw();
515 shape_changed = true;
516}
517
518int ItemList::get_fixed_column_width() const {
519 return fixed_column_width;
520}
521
522void ItemList::set_same_column_width(bool p_enable) {
523 if (same_column_width == p_enable) {
524 return;
525 }
526
527 same_column_width = p_enable;
528 queue_redraw();
529 shape_changed = true;
530}
531
532bool ItemList::is_same_column_width() const {
533 return same_column_width;
534}
535
536void ItemList::set_max_text_lines(int p_lines) {
537 ERR_FAIL_COND(p_lines < 1);
538 if (max_text_lines != p_lines) {
539 max_text_lines = p_lines;
540 for (int i = 0; i < items.size(); i++) {
541 if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
542 items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
543 items.write[i].text_buf->set_max_lines_visible(p_lines);
544 } else {
545 items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE);
546 }
547 }
548 shape_changed = true;
549 queue_redraw();
550 }
551}
552
553int ItemList::get_max_text_lines() const {
554 return max_text_lines;
555}
556
557void ItemList::set_max_columns(int p_amount) {
558 ERR_FAIL_COND(p_amount < 0);
559
560 if (max_columns == p_amount) {
561 return;
562 }
563
564 max_columns = p_amount;
565 queue_redraw();
566 shape_changed = true;
567}
568
569int ItemList::get_max_columns() const {
570 return max_columns;
571}
572
573void ItemList::set_select_mode(SelectMode p_mode) {
574 if (select_mode == p_mode) {
575 return;
576 }
577
578 select_mode = p_mode;
579 queue_redraw();
580}
581
582ItemList::SelectMode ItemList::get_select_mode() const {
583 return select_mode;
584}
585
586void ItemList::set_icon_mode(IconMode p_mode) {
587 ERR_FAIL_INDEX((int)p_mode, 2);
588 if (icon_mode != p_mode) {
589 icon_mode = p_mode;
590 for (int i = 0; i < items.size(); i++) {
591 if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
592 items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES);
593 } else {
594 items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE);
595 }
596 }
597 shape_changed = true;
598 queue_redraw();
599 }
600}
601
602ItemList::IconMode ItemList::get_icon_mode() const {
603 return icon_mode;
604}
605
606void ItemList::set_fixed_icon_size(const Size2i &p_size) {
607 if (fixed_icon_size == p_size) {
608 return;
609 }
610
611 fixed_icon_size = p_size;
612 queue_redraw();
613 shape_changed = true;
614}
615
616Size2i ItemList::get_fixed_icon_size() const {
617 return fixed_icon_size;
618}
619
620Size2 ItemList::Item::get_icon_size() const {
621 if (icon.is_null()) {
622 return Size2();
623 }
624
625 Size2 size_result = Size2(icon_region.size).abs();
626 if (icon_region.size.x == 0 || icon_region.size.y == 0) {
627 size_result = icon->get_size();
628 }
629
630 if (icon_transposed) {
631 Size2 size_tmp = size_result;
632 size_result.x = size_tmp.y;
633 size_result.y = size_tmp.x;
634 }
635
636 return size_result;
637}
638
639void ItemList::gui_input(const Ref<InputEvent> &p_event) {
640 ERR_FAIL_COND(p_event.is_null());
641
642#define CAN_SELECT(i) (items[i].selectable && !items[i].disabled)
643#define IS_SAME_ROW(i, row) (i / current_columns == row)
644
645 double prev_scroll = scroll_bar->get_value();
646
647 Ref<InputEventMouseMotion> mm = p_event;
648 if (defer_select_single >= 0 && mm.is_valid()) {
649 defer_select_single = -1;
650 return;
651 }
652
653 Ref<InputEventMouseButton> mb = p_event;
654
655 if (defer_select_single >= 0 && mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
656 select(defer_select_single, true);
657
658 emit_signal(SNAME("multi_selected"), defer_select_single, true);
659 defer_select_single = -1;
660 return;
661 }
662
663 if (mm.is_valid()) {
664 int closest = get_item_at_position(mm->get_position(), true);
665 if (closest != hovered) {
666 hovered = closest;
667 queue_redraw();
668 }
669 }
670
671 if (mb.is_valid() && mb->is_pressed()) {
672 search_string = ""; //any mousepress cancels
673
674 int closest = get_item_at_position(mb->get_position(), true);
675
676 if (closest != -1 && (mb->get_button_index() == MouseButton::LEFT || (allow_rmb_select && mb->get_button_index() == MouseButton::RIGHT))) {
677 int i = closest;
678
679 if (items[i].disabled) {
680 // Don't emit any signal or do any action with clicked item when disabled.
681 return;
682 }
683
684 if (select_mode == SELECT_MULTI && items[i].selected && mb->is_command_or_control_pressed()) {
685 deselect(i);
686 emit_signal(SNAME("multi_selected"), i, false);
687
688 } else if (select_mode == SELECT_MULTI && mb->is_shift_pressed() && current >= 0 && current < items.size() && current != i) {
689 // Range selection.
690
691 int from = current;
692 int to = i;
693 if (i < current) {
694 SWAP(from, to);
695 }
696 for (int j = from; j <= to; j++) {
697 if (!CAN_SELECT(j)) {
698 // Item is not selectable during a range selection, so skip it.
699 continue;
700 }
701 bool selected = !items[j].selected;
702 select(j, false);
703 if (selected) {
704 emit_signal(SNAME("multi_selected"), j, true);
705 }
706 }
707 emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
708
709 } else {
710 if (!mb->is_double_click() &&
711 !mb->is_command_or_control_pressed() &&
712 select_mode == SELECT_MULTI &&
713 items[i].selectable &&
714 items[i].selected &&
715 mb->get_button_index() == MouseButton::LEFT) {
716 defer_select_single = i;
717 return;
718 }
719
720 if (items[i].selectable && (!items[i].selected || allow_reselect)) {
721 select(i, select_mode == SELECT_SINGLE || !mb->is_command_or_control_pressed());
722
723 if (select_mode == SELECT_SINGLE) {
724 emit_signal(SNAME("item_selected"), i);
725 } else {
726 emit_signal(SNAME("multi_selected"), i, true);
727 }
728 }
729
730 emit_signal(SNAME("item_clicked"), i, get_local_mouse_position(), mb->get_button_index());
731
732 if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) {
733 emit_signal(SNAME("item_activated"), i);
734 }
735 }
736
737 return;
738 } else if (closest != -1) {
739 if (!items[closest].disabled) {
740 emit_signal(SNAME("item_clicked"), closest, get_local_mouse_position(), mb->get_button_index());
741 }
742 } else {
743 // Since closest is null, more likely we clicked on empty space, so send signal to interested controls. Allows, for example, implement items deselecting.
744 emit_signal(SNAME("empty_clicked"), get_local_mouse_position(), mb->get_button_index());
745 }
746 }
747 if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) {
748 scroll_bar->set_value(scroll_bar->get_value() - scroll_bar->get_page() * mb->get_factor() / 8);
749 }
750 if (mb.is_valid() && mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) {
751 scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * mb->get_factor() / 8);
752 }
753
754 if (p_event->is_pressed() && items.size() > 0) {
755 if (p_event->is_action("ui_up", true)) {
756 if (!search_string.is_empty()) {
757 uint64_t now = OS::get_singleton()->get_ticks_msec();
758 uint64_t diff = now - search_time_msec;
759
760 if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) {
761 for (int i = current - 1; i >= 0; i--) {
762 if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
763 set_current(i);
764 ensure_current_is_visible();
765 if (select_mode == SELECT_SINGLE) {
766 emit_signal(SNAME("item_selected"), current);
767 }
768
769 break;
770 }
771 }
772 accept_event();
773 return;
774 }
775 }
776
777 if (current >= current_columns) {
778 int next = current - current_columns;
779 while (next >= 0 && !CAN_SELECT(next)) {
780 next = next - current_columns;
781 }
782 if (next < 0) {
783 accept_event();
784 return;
785 }
786 set_current(next);
787 ensure_current_is_visible();
788 if (select_mode == SELECT_SINGLE) {
789 emit_signal(SNAME("item_selected"), current);
790 }
791 accept_event();
792 }
793 } else if (p_event->is_action("ui_down", true)) {
794 if (!search_string.is_empty()) {
795 uint64_t now = OS::get_singleton()->get_ticks_msec();
796 uint64_t diff = now - search_time_msec;
797
798 if (diff < uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec")) * 2) {
799 for (int i = current + 1; i < items.size(); i++) {
800 if (CAN_SELECT(i) && items[i].text.begins_with(search_string)) {
801 set_current(i);
802 ensure_current_is_visible();
803 if (select_mode == SELECT_SINGLE) {
804 emit_signal(SNAME("item_selected"), current);
805 }
806 break;
807 }
808 }
809 accept_event();
810 return;
811 }
812 }
813
814 if (current < items.size() - current_columns) {
815 int next = current + current_columns;
816 while (next < items.size() && !CAN_SELECT(next)) {
817 next = next + current_columns;
818 }
819 if (next >= items.size()) {
820 accept_event();
821 return;
822 }
823 set_current(next);
824 ensure_current_is_visible();
825 if (select_mode == SELECT_SINGLE) {
826 emit_signal(SNAME("item_selected"), current);
827 }
828 accept_event();
829 }
830 } else if (p_event->is_action("ui_page_up", true)) {
831 search_string = ""; //any mousepress cancels
832
833 for (int i = 4; i > 0; i--) {
834 int index = current - current_columns * i;
835 if (index >= 0 && index < items.size() && CAN_SELECT(index)) {
836 set_current(index);
837 ensure_current_is_visible();
838 if (select_mode == SELECT_SINGLE) {
839 emit_signal(SNAME("item_selected"), current);
840 }
841 accept_event();
842 break;
843 }
844 }
845 } else if (p_event->is_action("ui_page_down", true)) {
846 search_string = ""; //any mousepress cancels
847
848 for (int i = 4; i > 0; i--) {
849 int index = current + current_columns * i;
850 if (index >= 0 && index < items.size() && CAN_SELECT(index)) {
851 set_current(index);
852 ensure_current_is_visible();
853 if (select_mode == SELECT_SINGLE) {
854 emit_signal(SNAME("item_selected"), current);
855 }
856 accept_event();
857
858 break;
859 }
860 }
861 } else if (p_event->is_action("ui_left", true)) {
862 search_string = ""; //any mousepress cancels
863
864 if (current % current_columns != 0) {
865 int current_row = current / current_columns;
866 int next = current - 1;
867 while (next >= 0 && !CAN_SELECT(next)) {
868 next = next - 1;
869 }
870 if (next < 0 || !IS_SAME_ROW(next, current_row)) {
871 accept_event();
872 return;
873 }
874 set_current(next);
875 ensure_current_is_visible();
876 if (select_mode == SELECT_SINGLE) {
877 emit_signal(SNAME("item_selected"), current);
878 }
879 accept_event();
880 }
881 } else if (p_event->is_action("ui_right", true)) {
882 search_string = ""; //any mousepress cancels
883
884 if (current % current_columns != (current_columns - 1) && current + 1 < items.size()) {
885 int current_row = current / current_columns;
886 int next = current + 1;
887 while (next < items.size() && !CAN_SELECT(next)) {
888 next = next + 1;
889 }
890 if (items.size() <= next || !IS_SAME_ROW(next, current_row)) {
891 accept_event();
892 return;
893 }
894 set_current(next);
895 ensure_current_is_visible();
896 if (select_mode == SELECT_SINGLE) {
897 emit_signal(SNAME("item_selected"), current);
898 }
899 accept_event();
900 }
901 } else if (p_event->is_action("ui_cancel", true)) {
902 search_string = "";
903 } else if (p_event->is_action("ui_select", true) && select_mode == SELECT_MULTI) {
904 if (current >= 0 && current < items.size()) {
905 if (CAN_SELECT(current) && !items[current].selected) {
906 select(current, false);
907 emit_signal(SNAME("multi_selected"), current, true);
908 } else if (items[current].selected) {
909 deselect(current);
910 emit_signal(SNAME("multi_selected"), current, false);
911 }
912 }
913 } else if (p_event->is_action("ui_accept", true)) {
914 search_string = ""; //any mousepress cancels
915
916 if (current >= 0 && current < items.size() && !items[current].disabled) {
917 emit_signal(SNAME("item_activated"), current);
918 }
919 } else {
920 Ref<InputEventKey> k = p_event;
921
922 if (allow_search && k.is_valid() && k->get_unicode()) {
923 uint64_t now = OS::get_singleton()->get_ticks_msec();
924 uint64_t diff = now - search_time_msec;
925 uint64_t max_interval = uint64_t(GLOBAL_GET("gui/timers/incremental_search_max_interval_msec"));
926 search_time_msec = now;
927
928 if (diff > max_interval) {
929 search_string = "";
930 }
931
932 if (String::chr(k->get_unicode()) != search_string) {
933 search_string += String::chr(k->get_unicode());
934 }
935
936 for (int i = current + 1; i <= items.size(); i++) {
937 if (i == items.size()) {
938 if (current == 0 || current == -1) {
939 break;
940 } else {
941 i = 0;
942 }
943 }
944
945 if (i == current) {
946 break;
947 }
948
949 if (items[i].text.findn(search_string) == 0) {
950 set_current(i);
951 ensure_current_is_visible();
952 if (select_mode == SELECT_SINGLE) {
953 emit_signal(SNAME("item_selected"), current);
954 }
955 break;
956 }
957 }
958 }
959 }
960 }
961
962 Ref<InputEventPanGesture> pan_gesture = p_event;
963 if (pan_gesture.is_valid()) {
964 scroll_bar->set_value(scroll_bar->get_value() + scroll_bar->get_page() * pan_gesture->get_delta().y / 8);
965 }
966
967 if (scroll_bar->get_value() != prev_scroll) {
968 accept_event(); //accept event if scroll changed
969 }
970
971#undef CAN_SELECT
972#undef IS_SAME_ROW
973}
974
975void ItemList::ensure_current_is_visible() {
976 ensure_selected_visible = true;
977 queue_redraw();
978}
979
980static Rect2 _adjust_to_max_size(Size2 p_size, Size2 p_max_size) {
981 Size2 size = p_max_size;
982 int tex_width = p_size.width * size.height / p_size.height;
983 int tex_height = size.height;
984
985 if (tex_width > size.width) {
986 tex_width = size.width;
987 tex_height = p_size.height * tex_width / p_size.width;
988 }
989
990 int ofs_x = (size.width - tex_width) / 2;
991 int ofs_y = (size.height - tex_height) / 2;
992
993 return Rect2(ofs_x, ofs_y, tex_width, tex_height);
994}
995
996void ItemList::_notification(int p_what) {
997 switch (p_what) {
998 case NOTIFICATION_RESIZED: {
999 shape_changed = true;
1000 queue_redraw();
1001 } break;
1002
1003 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
1004 case NOTIFICATION_TRANSLATION_CHANGED:
1005 case NOTIFICATION_THEME_CHANGED: {
1006 for (int i = 0; i < items.size(); i++) {
1007 _shape_text(i);
1008 }
1009 shape_changed = true;
1010 queue_redraw();
1011 } break;
1012
1013 case NOTIFICATION_DRAW: {
1014 _check_shape_changed();
1015
1016 int scroll_bar_minwidth = scroll_bar->get_minimum_size().x;
1017 scroll_bar->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -scroll_bar_minwidth);
1018 scroll_bar->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);
1019 scroll_bar->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.panel_style->get_margin(SIDE_TOP));
1020 scroll_bar->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM));
1021
1022 Size2 size = get_size();
1023 int width = size.width - theme_cache.panel_style->get_minimum_size().width;
1024 if (scroll_bar->is_visible()) {
1025 width -= scroll_bar_minwidth;
1026 }
1027
1028 draw_style_box(theme_cache.panel_style, Rect2(Point2(), size));
1029
1030 Ref<StyleBox> sbsel;
1031 Ref<StyleBox> cursor;
1032
1033 if (has_focus()) {
1034 sbsel = theme_cache.selected_focus_style;
1035 cursor = theme_cache.cursor_focus_style;
1036 } else {
1037 sbsel = theme_cache.selected_style;
1038 cursor = theme_cache.cursor_style;
1039 }
1040 bool rtl = is_layout_rtl();
1041
1042 if (has_focus()) {
1043 RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), true);
1044 draw_style_box(theme_cache.focus_style, Rect2(Point2(), size));
1045 RenderingServer::get_singleton()->canvas_item_add_clip_ignore(get_canvas_item(), false);
1046 }
1047
1048 // Ensure_selected_visible needs to be checked before we draw the list.
1049 if (ensure_selected_visible && current >= 0 && current < items.size()) {
1050 Rect2 r = items[current].rect_cache;
1051 int from = scroll_bar->get_value();
1052 int to = from + scroll_bar->get_page();
1053
1054 if (r.position.y < from) {
1055 scroll_bar->set_value(r.position.y);
1056 } else if (r.position.y + r.size.y > to) {
1057 scroll_bar->set_value(r.position.y + r.size.y - (to - from));
1058 }
1059 }
1060
1061 ensure_selected_visible = false;
1062
1063 Vector2 base_ofs = theme_cache.panel_style->get_offset();
1064 base_ofs.y -= int(scroll_bar->get_value());
1065
1066 // Define a visible frame to check against and optimize drawing.
1067 const Rect2 clip(-base_ofs, size);
1068
1069 // Do a binary search to find the first separator that is below clip_position.y.
1070 int first_visible_separator = 0;
1071 {
1072 int lo = 0;
1073 int hi = separators.size();
1074 while (lo < hi) {
1075 const int mid = (lo + hi) / 2;
1076 if (separators[mid] < clip.position.y) {
1077 lo = mid + 1;
1078 } else {
1079 hi = mid;
1080 }
1081 }
1082 first_visible_separator = lo;
1083 }
1084
1085 // Draw visible separators.
1086 for (int i = first_visible_separator; i < separators.size(); i++) {
1087 if (separators[i] > clip.position.y + clip.size.y) {
1088 break; // done
1089 }
1090
1091 const int y = base_ofs.y + separators[i];
1092 draw_line(Vector2(theme_cache.panel_style->get_margin(SIDE_LEFT), y), Vector2(width, y), theme_cache.guide_color);
1093 }
1094
1095 // Do a binary search to find the first item whose rect reaches below clip.position.y.
1096 int first_item_visible;
1097 {
1098 int lo = 0;
1099 int hi = items.size();
1100 while (lo < hi) {
1101 const int mid = (lo + hi) / 2;
1102 const Rect2 &rcache = items[mid].rect_cache;
1103 if (rcache.position.y + rcache.size.y < clip.position.y) {
1104 lo = mid + 1;
1105 } else {
1106 hi = mid;
1107 }
1108 }
1109
1110 // We might end up with an item in columns 2, 3, etc, but we need the one from the first column.
1111 // We can also end up in a state where lo reached hi, and so no items can be rendered; we skip that.
1112 while (lo < hi && lo > 0 && items[lo].column > 0) {
1113 lo -= 1;
1114 }
1115
1116 first_item_visible = lo;
1117 }
1118
1119 // Draw visible items.
1120 for (int i = first_item_visible; i < items.size(); i++) {
1121 Rect2 rcache = items[i].rect_cache;
1122
1123 if (rcache.position.y > clip.position.y + clip.size.y) {
1124 break; // done
1125 }
1126
1127 if (!clip.intersects(rcache)) {
1128 continue;
1129 }
1130
1131 if (current_columns == 1) {
1132 rcache.size.width = width - rcache.position.x;
1133 }
1134
1135 bool should_draw_selected_bg = items[i].selected;
1136 bool should_draw_hovered_bg = hovered == i && !items[i].selected;
1137 bool should_draw_custom_bg = items[i].custom_bg.a > 0.001;
1138
1139 if (should_draw_selected_bg || should_draw_hovered_bg || should_draw_custom_bg) {
1140 Rect2 r = rcache;
1141 r.position += base_ofs;
1142 r.position.y -= theme_cache.v_separation / 2;
1143 r.size.y += theme_cache.v_separation;
1144 r.position.x -= theme_cache.h_separation / 2;
1145 r.size.x += theme_cache.h_separation;
1146
1147 if (rtl) {
1148 r.position.x = size.width - r.position.x - r.size.x;
1149 }
1150
1151 if (should_draw_selected_bg) {
1152 draw_style_box(sbsel, r);
1153 }
1154 if (should_draw_hovered_bg) {
1155 draw_style_box(theme_cache.hovered_style, r);
1156 }
1157 if (should_draw_custom_bg) {
1158 draw_rect(r, items[i].custom_bg);
1159 }
1160 }
1161
1162 Vector2 text_ofs;
1163 if (items[i].icon.is_valid()) {
1164 Size2 icon_size;
1165 //= _adjust_to_max_size(items[i].get_icon_size(),fixed_icon_size) * icon_scale;
1166
1167 if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) {
1168 icon_size = fixed_icon_size * icon_scale;
1169 } else {
1170 icon_size = items[i].get_icon_size() * icon_scale;
1171 }
1172
1173 Vector2 icon_ofs;
1174
1175 Point2 pos = items[i].rect_cache.position + icon_ofs + base_ofs;
1176
1177 if (icon_mode == ICON_MODE_TOP) {
1178 pos.x += Math::floor((items[i].rect_cache.size.width - icon_size.width) / 2);
1179 pos.y += theme_cache.icon_margin;
1180 text_ofs.y = icon_size.height + theme_cache.icon_margin * 2;
1181 } else {
1182 pos.y += Math::floor((items[i].rect_cache.size.height - icon_size.height) / 2);
1183 text_ofs.x = icon_size.width + theme_cache.icon_margin;
1184 }
1185
1186 Rect2 draw_rect = Rect2(pos, icon_size);
1187
1188 if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) {
1189 Rect2 adj = _adjust_to_max_size(items[i].get_icon_size() * icon_scale, icon_size);
1190 draw_rect.position += adj.position;
1191 draw_rect.size = adj.size;
1192 }
1193
1194 Color icon_modulate = items[i].icon_modulate;
1195 if (items[i].disabled) {
1196 icon_modulate.a *= 0.5;
1197 }
1198
1199 // If the icon is transposed, we have to switch the size so that it is drawn correctly
1200 if (items[i].icon_transposed) {
1201 Size2 size_tmp = draw_rect.size;
1202 draw_rect.size.x = size_tmp.y;
1203 draw_rect.size.y = size_tmp.x;
1204 }
1205
1206 Rect2 region = (items[i].icon_region.size.x == 0 || items[i].icon_region.size.y == 0) ? Rect2(Vector2(), items[i].icon->get_size()) : Rect2(items[i].icon_region);
1207
1208 if (rtl) {
1209 draw_rect.position.x = size.width - draw_rect.position.x - draw_rect.size.x;
1210 }
1211 draw_texture_rect_region(items[i].icon, draw_rect, region, icon_modulate, items[i].icon_transposed);
1212 }
1213
1214 if (items[i].tag_icon.is_valid()) {
1215 Point2 draw_pos = items[i].rect_cache.position;
1216 if (rtl) {
1217 draw_pos.x = size.width - draw_pos.x - items[i].tag_icon->get_width();
1218 }
1219 draw_texture(items[i].tag_icon, draw_pos + base_ofs);
1220 }
1221
1222 if (!items[i].text.is_empty()) {
1223 int max_len = -1;
1224
1225 Vector2 size2 = items[i].text_buf->get_size();
1226 if (fixed_column_width) {
1227 max_len = fixed_column_width;
1228 } else if (same_column_width) {
1229 max_len = items[i].rect_cache.size.x;
1230 } else {
1231 max_len = size2.x;
1232 }
1233
1234 Color txt_modulate;
1235 if (items[i].selected) {
1236 txt_modulate = theme_cache.font_selected_color;
1237 } else if (hovered == i) {
1238 txt_modulate = theme_cache.font_hovered_color;
1239 } else if (items[i].custom_fg != Color()) {
1240 txt_modulate = items[i].custom_fg;
1241 } else {
1242 txt_modulate = theme_cache.font_color;
1243 }
1244
1245 if (items[i].disabled) {
1246 txt_modulate.a *= 0.5;
1247 }
1248
1249 if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) {
1250 text_ofs += base_ofs;
1251 text_ofs += items[i].rect_cache.position;
1252
1253 if (rtl) {
1254 text_ofs.x = size.width - text_ofs.x - max_len;
1255 }
1256
1257 items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1258
1259 if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
1260 items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color);
1261 }
1262
1263 items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate);
1264 } else {
1265 if (fixed_column_width > 0) {
1266 size2.x = MIN(size2.x, fixed_column_width);
1267 }
1268
1269 if (icon_mode == ICON_MODE_TOP) {
1270 text_ofs.x += (items[i].rect_cache.size.width - size2.x) / 2;
1271 } else {
1272 text_ofs.y += (items[i].rect_cache.size.height - size2.y) / 2;
1273 }
1274
1275 text_ofs += base_ofs;
1276 text_ofs += items[i].rect_cache.position;
1277
1278 float text_w = width - text_ofs.x;
1279 items.write[i].text_buf->set_width(text_w);
1280
1281 if (rtl) {
1282 text_ofs.x = size.width - width;
1283 items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
1284 } else {
1285 items.write[i].text_buf->set_alignment(HORIZONTAL_ALIGNMENT_LEFT);
1286 }
1287
1288 if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
1289 items[i].text_buf->draw_outline(get_canvas_item(), text_ofs, theme_cache.font_outline_size, theme_cache.font_outline_color);
1290 }
1291
1292 if (width - text_ofs.x > 0) {
1293 items[i].text_buf->draw(get_canvas_item(), text_ofs, txt_modulate);
1294 }
1295 }
1296 }
1297
1298 if (select_mode == SELECT_MULTI && i == current) {
1299 Rect2 r = rcache;
1300 r.position += base_ofs;
1301 r.position.y -= theme_cache.v_separation / 2;
1302 r.size.y += theme_cache.v_separation;
1303 r.position.x -= theme_cache.h_separation / 2;
1304 r.size.x += theme_cache.h_separation;
1305
1306 if (rtl) {
1307 r.position.x = size.width - r.position.x - r.size.x;
1308 }
1309
1310 draw_style_box(cursor, r);
1311 }
1312 }
1313 } break;
1314 }
1315}
1316
1317void ItemList::_check_shape_changed() {
1318 if (!shape_changed) {
1319 return;
1320 }
1321
1322 int scroll_bar_minwidth = scroll_bar->get_minimum_size().x;
1323 Size2 size = get_size();
1324 float max_column_width = 0.0;
1325
1326 //1- compute item minimum sizes
1327 for (int i = 0; i < items.size(); i++) {
1328 Size2 minsize;
1329 if (items[i].icon.is_valid()) {
1330 if (fixed_icon_size.x > 0 && fixed_icon_size.y > 0) {
1331 minsize = fixed_icon_size * icon_scale;
1332 } else {
1333 minsize = items[i].get_icon_size() * icon_scale;
1334 }
1335
1336 if (!items[i].text.is_empty()) {
1337 if (icon_mode == ICON_MODE_TOP) {
1338 minsize.y += theme_cache.icon_margin;
1339 } else {
1340 minsize.x += theme_cache.icon_margin;
1341 }
1342 }
1343 }
1344
1345 if (!items[i].text.is_empty()) {
1346 int max_width = -1;
1347 if (fixed_column_width) {
1348 max_width = fixed_column_width;
1349 } else if (same_column_width) {
1350 max_width = items[i].rect_cache.size.x;
1351 }
1352 items.write[i].text_buf->set_width(max_width);
1353 Size2 s = items[i].text_buf->get_size();
1354
1355 if (icon_mode == ICON_MODE_TOP) {
1356 minsize.x = MAX(minsize.x, s.width);
1357 if (max_text_lines > 0) {
1358 minsize.y += s.height + theme_cache.line_separation * max_text_lines;
1359 } else {
1360 minsize.y += s.height;
1361 }
1362
1363 } else {
1364 minsize.y = MAX(minsize.y, s.height);
1365 minsize.x += s.width;
1366 }
1367 }
1368
1369 if (fixed_column_width > 0) {
1370 minsize.x = fixed_column_width;
1371 }
1372 max_column_width = MAX(max_column_width, minsize.x);
1373
1374 // elements need to adapt to the selected size
1375 minsize.y += theme_cache.v_separation;
1376 minsize.x += theme_cache.h_separation;
1377 items.write[i].rect_cache.size = minsize;
1378 items.write[i].min_rect_cache.size = minsize;
1379 }
1380
1381 int fit_size = size.x - theme_cache.panel_style->get_minimum_size().width - scroll_bar_minwidth;
1382
1383 //2-attempt best fit
1384 current_columns = 0x7FFFFFFF;
1385 if (max_columns > 0) {
1386 current_columns = max_columns;
1387 }
1388
1389 // Repeat until all items fit.
1390 while (true) {
1391 bool all_fit = true;
1392 Vector2 ofs;
1393 int col = 0;
1394 int max_h = 0;
1395
1396 separators.clear();
1397
1398 for (int i = 0; i < items.size(); i++) {
1399 if (current_columns > 1 && items[i].rect_cache.size.width + ofs.x > fit_size) {
1400 // Went past.
1401 current_columns = MAX(col, 1);
1402 all_fit = false;
1403 break;
1404 }
1405
1406 if (same_column_width) {
1407 items.write[i].rect_cache.size.x = max_column_width;
1408 }
1409 items.write[i].rect_cache.position = ofs;
1410
1411 max_h = MAX(max_h, items[i].rect_cache.size.y);
1412 ofs.x += items[i].rect_cache.size.x + theme_cache.h_separation;
1413
1414 items.write[i].column = col;
1415 col++;
1416 if (col == current_columns) {
1417 if (i < items.size() - 1) {
1418 separators.push_back(ofs.y + max_h + theme_cache.v_separation / 2);
1419 }
1420
1421 for (int j = i; j >= 0 && col > 0; j--, col--) {
1422 items.write[j].rect_cache.size.y = max_h;
1423 }
1424
1425 ofs.x = 0;
1426 ofs.y += max_h + theme_cache.v_separation;
1427 col = 0;
1428 max_h = 0;
1429 }
1430 }
1431
1432 for (int j = items.size() - 1; j >= 0 && col > 0; j--, col--) {
1433 items.write[j].rect_cache.size.y = max_h;
1434 }
1435
1436 if (all_fit) {
1437 float page = MAX(0, size.height - theme_cache.panel_style->get_minimum_size().height);
1438 float max = MAX(page, ofs.y + max_h);
1439 if (auto_height) {
1440 auto_height_value = ofs.y + max_h + theme_cache.panel_style->get_minimum_size().height;
1441 }
1442 scroll_bar->set_max(max);
1443 scroll_bar->set_page(page);
1444 if (max <= page) {
1445 scroll_bar->set_value(0);
1446 scroll_bar->hide();
1447 } else {
1448 scroll_bar->show();
1449
1450 if (do_autoscroll_to_bottom) {
1451 scroll_bar->set_value(max);
1452 }
1453 }
1454 break;
1455 }
1456 }
1457
1458 update_minimum_size();
1459 shape_changed = false;
1460}
1461
1462void ItemList::_scroll_changed(double) {
1463 queue_redraw();
1464}
1465
1466void ItemList::_mouse_exited() {
1467 if (hovered > -1) {
1468 hovered = -1;
1469 queue_redraw();
1470 }
1471}
1472
1473int ItemList::get_item_at_position(const Point2 &p_pos, bool p_exact) const {
1474 Vector2 pos = p_pos;
1475 pos -= theme_cache.panel_style->get_offset();
1476 pos.y += scroll_bar->get_value();
1477
1478 if (is_layout_rtl()) {
1479 pos.x = get_size().width - pos.x;
1480 }
1481
1482 int closest = -1;
1483 int closest_dist = 0x7FFFFFFF;
1484
1485 for (int i = 0; i < items.size(); i++) {
1486 Rect2 rc = items[i].rect_cache;
1487 if (i % current_columns == current_columns - 1) {
1488 rc.size.width = get_size().width - rc.position.x; // Make sure you can still select the last item when clicking past the column.
1489 }
1490
1491 if (rc.has_point(pos)) {
1492 closest = i;
1493 break;
1494 }
1495
1496 float dist = rc.distance_to(pos);
1497 if (!p_exact && dist < closest_dist) {
1498 closest = i;
1499 closest_dist = dist;
1500 }
1501 }
1502
1503 return closest;
1504}
1505
1506bool ItemList::is_pos_at_end_of_items(const Point2 &p_pos) const {
1507 if (items.is_empty()) {
1508 return true;
1509 }
1510
1511 Vector2 pos = p_pos;
1512 pos -= theme_cache.panel_style->get_offset();
1513 pos.y += scroll_bar->get_value();
1514
1515 if (is_layout_rtl()) {
1516 pos.x = get_size().width - pos.x;
1517 }
1518
1519 Rect2 endrect = items[items.size() - 1].rect_cache;
1520 return (pos.y > endrect.position.y + endrect.size.y);
1521}
1522
1523String ItemList::get_tooltip(const Point2 &p_pos) const {
1524 int closest = get_item_at_position(p_pos, true);
1525
1526 if (closest != -1) {
1527 if (!items[closest].tooltip_enabled) {
1528 return "";
1529 }
1530 if (!items[closest].tooltip.is_empty()) {
1531 return items[closest].tooltip;
1532 }
1533 if (!items[closest].text.is_empty()) {
1534 return items[closest].text;
1535 }
1536 }
1537
1538 return Control::get_tooltip(p_pos);
1539}
1540
1541void ItemList::sort_items_by_text() {
1542 items.sort();
1543 queue_redraw();
1544 shape_changed = true;
1545
1546 if (select_mode == SELECT_SINGLE) {
1547 for (int i = 0; i < items.size(); i++) {
1548 if (items[i].selected) {
1549 select(i);
1550 return;
1551 }
1552 }
1553 }
1554}
1555
1556int ItemList::find_metadata(const Variant &p_metadata) const {
1557 for (int i = 0; i < items.size(); i++) {
1558 if (items[i].metadata == p_metadata) {
1559 return i;
1560 }
1561 }
1562
1563 return -1;
1564}
1565
1566void ItemList::set_allow_rmb_select(bool p_allow) {
1567 allow_rmb_select = p_allow;
1568}
1569
1570bool ItemList::get_allow_rmb_select() const {
1571 return allow_rmb_select;
1572}
1573
1574void ItemList::set_allow_reselect(bool p_allow) {
1575 allow_reselect = p_allow;
1576}
1577
1578bool ItemList::get_allow_reselect() const {
1579 return allow_reselect;
1580}
1581
1582void ItemList::set_allow_search(bool p_allow) {
1583 allow_search = p_allow;
1584}
1585
1586bool ItemList::get_allow_search() const {
1587 return allow_search;
1588}
1589
1590void ItemList::set_icon_scale(real_t p_scale) {
1591 ERR_FAIL_COND(!Math::is_finite(p_scale));
1592
1593 if (icon_scale == p_scale) {
1594 return;
1595 }
1596
1597 icon_scale = p_scale;
1598 queue_redraw();
1599 shape_changed = true;
1600}
1601
1602real_t ItemList::get_icon_scale() const {
1603 return icon_scale;
1604}
1605
1606Vector<int> ItemList::get_selected_items() {
1607 Vector<int> selected;
1608 for (int i = 0; i < items.size(); i++) {
1609 if (items[i].selected) {
1610 selected.push_back(i);
1611 if (this->select_mode == SELECT_SINGLE) {
1612 break;
1613 }
1614 }
1615 }
1616 return selected;
1617}
1618
1619bool ItemList::is_anything_selected() {
1620 for (int i = 0; i < items.size(); i++) {
1621 if (items[i].selected) {
1622 return true;
1623 }
1624 }
1625
1626 return false;
1627}
1628
1629Size2 ItemList::get_minimum_size() const {
1630 if (auto_height) {
1631 return Size2(0, auto_height_value);
1632 }
1633 return Size2();
1634}
1635
1636void ItemList::set_autoscroll_to_bottom(const bool p_enable) {
1637 do_autoscroll_to_bottom = p_enable;
1638}
1639
1640void ItemList::set_auto_height(bool p_enable) {
1641 if (auto_height == p_enable) {
1642 return;
1643 }
1644
1645 auto_height = p_enable;
1646 shape_changed = true;
1647 queue_redraw();
1648}
1649
1650bool ItemList::has_auto_height() const {
1651 return auto_height;
1652}
1653
1654void ItemList::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) {
1655 if (text_overrun_behavior != p_behavior) {
1656 text_overrun_behavior = p_behavior;
1657 for (int i = 0; i < items.size(); i++) {
1658 items.write[i].text_buf->set_text_overrun_behavior(p_behavior);
1659 }
1660 shape_changed = true;
1661 queue_redraw();
1662 }
1663}
1664
1665TextServer::OverrunBehavior ItemList::get_text_overrun_behavior() const {
1666 return text_overrun_behavior;
1667}
1668
1669bool ItemList::_set(const StringName &p_name, const Variant &p_value) {
1670 Vector<String> components = String(p_name).split("/", true, 2);
1671 if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
1672 int item_index = components[0].trim_prefix("item_").to_int();
1673 if (components[1] == "text") {
1674 set_item_text(item_index, p_value);
1675 return true;
1676 } else if (components[1] == "icon") {
1677 set_item_icon(item_index, p_value);
1678 return true;
1679 } else if (components[1] == "disabled") {
1680 set_item_disabled(item_index, p_value);
1681 return true;
1682 } else if (components[1] == "selectable") {
1683 set_item_selectable(item_index, p_value);
1684 return true;
1685 }
1686 }
1687#ifndef DISABLE_DEPRECATED
1688 // Compatibility.
1689 if (p_name == "items") {
1690 Array arr = p_value;
1691 ERR_FAIL_COND_V(arr.size() % 3, false);
1692 clear();
1693
1694 for (int i = 0; i < arr.size(); i += 3) {
1695 String text = arr[i + 0];
1696 Ref<Texture2D> icon = arr[i + 1];
1697 bool disabled = arr[i + 2];
1698
1699 int idx = get_item_count();
1700 add_item(text, icon);
1701 set_item_disabled(idx, disabled);
1702 }
1703 }
1704#endif
1705 return false;
1706}
1707
1708bool ItemList::_get(const StringName &p_name, Variant &r_ret) const {
1709 Vector<String> components = String(p_name).split("/", true, 2);
1710 if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) {
1711 int item_index = components[0].trim_prefix("item_").to_int();
1712 if (components[1] == "text") {
1713 r_ret = get_item_text(item_index);
1714 return true;
1715 } else if (components[1] == "icon") {
1716 r_ret = get_item_icon(item_index);
1717 return true;
1718 } else if (components[1] == "disabled") {
1719 r_ret = is_item_disabled(item_index);
1720 return true;
1721 } else if (components[1] == "selectable") {
1722 r_ret = is_item_selectable(item_index);
1723 return true;
1724 }
1725 }
1726 return false;
1727}
1728
1729void ItemList::_get_property_list(List<PropertyInfo> *p_list) const {
1730 for (int i = 0; i < items.size(); i++) {
1731 p_list->push_back(PropertyInfo(Variant::STRING, vformat("item_%d/text", i)));
1732
1733 PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D");
1734 pi.usage &= ~(get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0);
1735 p_list->push_back(pi);
1736
1737 pi = PropertyInfo(Variant::BOOL, vformat("item_%d/selectable", i));
1738 pi.usage &= ~(is_item_selectable(i) ? PROPERTY_USAGE_STORAGE : 0);
1739 p_list->push_back(pi);
1740
1741 pi = PropertyInfo(Variant::BOOL, vformat("item_%d/disabled", i));
1742 pi.usage &= ~(!is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0);
1743 p_list->push_back(pi);
1744 }
1745}
1746
1747void ItemList::_bind_methods() {
1748 ClassDB::bind_method(D_METHOD("add_item", "text", "icon", "selectable"), &ItemList::add_item, DEFVAL(Variant()), DEFVAL(true));
1749 ClassDB::bind_method(D_METHOD("add_icon_item", "icon", "selectable"), &ItemList::add_icon_item, DEFVAL(true));
1750
1751 ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &ItemList::set_item_text);
1752 ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &ItemList::get_item_text);
1753
1754 ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &ItemList::set_item_icon);
1755 ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &ItemList::get_item_icon);
1756
1757 ClassDB::bind_method(D_METHOD("set_item_text_direction", "idx", "direction"), &ItemList::set_item_text_direction);
1758 ClassDB::bind_method(D_METHOD("get_item_text_direction", "idx"), &ItemList::get_item_text_direction);
1759
1760 ClassDB::bind_method(D_METHOD("set_item_language", "idx", "language"), &ItemList::set_item_language);
1761 ClassDB::bind_method(D_METHOD("get_item_language", "idx"), &ItemList::get_item_language);
1762
1763 ClassDB::bind_method(D_METHOD("set_item_icon_transposed", "idx", "transposed"), &ItemList::set_item_icon_transposed);
1764 ClassDB::bind_method(D_METHOD("is_item_icon_transposed", "idx"), &ItemList::is_item_icon_transposed);
1765
1766 ClassDB::bind_method(D_METHOD("set_item_icon_region", "idx", "rect"), &ItemList::set_item_icon_region);
1767 ClassDB::bind_method(D_METHOD("get_item_icon_region", "idx"), &ItemList::get_item_icon_region);
1768
1769 ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "idx", "modulate"), &ItemList::set_item_icon_modulate);
1770 ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "idx"), &ItemList::get_item_icon_modulate);
1771
1772 ClassDB::bind_method(D_METHOD("set_item_selectable", "idx", "selectable"), &ItemList::set_item_selectable);
1773 ClassDB::bind_method(D_METHOD("is_item_selectable", "idx"), &ItemList::is_item_selectable);
1774
1775 ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &ItemList::set_item_disabled);
1776 ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &ItemList::is_item_disabled);
1777
1778 ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &ItemList::set_item_metadata);
1779 ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &ItemList::get_item_metadata);
1780
1781 ClassDB::bind_method(D_METHOD("set_item_custom_bg_color", "idx", "custom_bg_color"), &ItemList::set_item_custom_bg_color);
1782 ClassDB::bind_method(D_METHOD("get_item_custom_bg_color", "idx"), &ItemList::get_item_custom_bg_color);
1783
1784 ClassDB::bind_method(D_METHOD("set_item_custom_fg_color", "idx", "custom_fg_color"), &ItemList::set_item_custom_fg_color);
1785 ClassDB::bind_method(D_METHOD("get_item_custom_fg_color", "idx"), &ItemList::get_item_custom_fg_color);
1786
1787 ClassDB::bind_method(D_METHOD("get_item_rect", "idx", "expand"), &ItemList::get_item_rect, DEFVAL(true));
1788
1789 ClassDB::bind_method(D_METHOD("set_item_tooltip_enabled", "idx", "enable"), &ItemList::set_item_tooltip_enabled);
1790 ClassDB::bind_method(D_METHOD("is_item_tooltip_enabled", "idx"), &ItemList::is_item_tooltip_enabled);
1791
1792 ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &ItemList::set_item_tooltip);
1793 ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &ItemList::get_item_tooltip);
1794
1795 ClassDB::bind_method(D_METHOD("select", "idx", "single"), &ItemList::select, DEFVAL(true));
1796 ClassDB::bind_method(D_METHOD("deselect", "idx"), &ItemList::deselect);
1797 ClassDB::bind_method(D_METHOD("deselect_all"), &ItemList::deselect_all);
1798
1799 ClassDB::bind_method(D_METHOD("is_selected", "idx"), &ItemList::is_selected);
1800 ClassDB::bind_method(D_METHOD("get_selected_items"), &ItemList::get_selected_items);
1801
1802 ClassDB::bind_method(D_METHOD("move_item", "from_idx", "to_idx"), &ItemList::move_item);
1803
1804 ClassDB::bind_method(D_METHOD("set_item_count", "count"), &ItemList::set_item_count);
1805 ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count);
1806 ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item);
1807
1808 ClassDB::bind_method(D_METHOD("clear"), &ItemList::clear);
1809 ClassDB::bind_method(D_METHOD("sort_items_by_text"), &ItemList::sort_items_by_text);
1810
1811 ClassDB::bind_method(D_METHOD("set_fixed_column_width", "width"), &ItemList::set_fixed_column_width);
1812 ClassDB::bind_method(D_METHOD("get_fixed_column_width"), &ItemList::get_fixed_column_width);
1813
1814 ClassDB::bind_method(D_METHOD("set_same_column_width", "enable"), &ItemList::set_same_column_width);
1815 ClassDB::bind_method(D_METHOD("is_same_column_width"), &ItemList::is_same_column_width);
1816
1817 ClassDB::bind_method(D_METHOD("set_max_text_lines", "lines"), &ItemList::set_max_text_lines);
1818 ClassDB::bind_method(D_METHOD("get_max_text_lines"), &ItemList::get_max_text_lines);
1819
1820 ClassDB::bind_method(D_METHOD("set_max_columns", "amount"), &ItemList::set_max_columns);
1821 ClassDB::bind_method(D_METHOD("get_max_columns"), &ItemList::get_max_columns);
1822
1823 ClassDB::bind_method(D_METHOD("set_select_mode", "mode"), &ItemList::set_select_mode);
1824 ClassDB::bind_method(D_METHOD("get_select_mode"), &ItemList::get_select_mode);
1825
1826 ClassDB::bind_method(D_METHOD("set_icon_mode", "mode"), &ItemList::set_icon_mode);
1827 ClassDB::bind_method(D_METHOD("get_icon_mode"), &ItemList::get_icon_mode);
1828
1829 ClassDB::bind_method(D_METHOD("set_fixed_icon_size", "size"), &ItemList::set_fixed_icon_size);
1830 ClassDB::bind_method(D_METHOD("get_fixed_icon_size"), &ItemList::get_fixed_icon_size);
1831
1832 ClassDB::bind_method(D_METHOD("set_icon_scale", "scale"), &ItemList::set_icon_scale);
1833 ClassDB::bind_method(D_METHOD("get_icon_scale"), &ItemList::get_icon_scale);
1834
1835 ClassDB::bind_method(D_METHOD("set_allow_rmb_select", "allow"), &ItemList::set_allow_rmb_select);
1836 ClassDB::bind_method(D_METHOD("get_allow_rmb_select"), &ItemList::get_allow_rmb_select);
1837
1838 ClassDB::bind_method(D_METHOD("set_allow_reselect", "allow"), &ItemList::set_allow_reselect);
1839 ClassDB::bind_method(D_METHOD("get_allow_reselect"), &ItemList::get_allow_reselect);
1840
1841 ClassDB::bind_method(D_METHOD("set_allow_search", "allow"), &ItemList::set_allow_search);
1842 ClassDB::bind_method(D_METHOD("get_allow_search"), &ItemList::get_allow_search);
1843
1844 ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height);
1845 ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height);
1846
1847 ClassDB::bind_method(D_METHOD("is_anything_selected"), &ItemList::is_anything_selected);
1848
1849 ClassDB::bind_method(D_METHOD("get_item_at_position", "position", "exact"), &ItemList::get_item_at_position, DEFVAL(false));
1850
1851 ClassDB::bind_method(D_METHOD("ensure_current_is_visible"), &ItemList::ensure_current_is_visible);
1852
1853 ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &ItemList::get_v_scroll_bar);
1854
1855 ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &ItemList::set_text_overrun_behavior);
1856 ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &ItemList::get_text_overrun_behavior);
1857
1858 ADD_PROPERTY(PropertyInfo(Variant::INT, "select_mode", PROPERTY_HINT_ENUM, "Single,Multi"), "set_select_mode", "get_select_mode");
1859 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
1860 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_rmb_select"), "set_allow_rmb_select", "get_allow_rmb_select");
1861 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_search"), "set_allow_search", "get_allow_search");
1862 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_text_lines", PROPERTY_HINT_RANGE, "1,10,1,or_greater"), "set_max_text_lines", "get_max_text_lines");
1863 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_height"), "set_auto_height", "has_auto_height");
1864 ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
1865 ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "item_");
1866 ADD_GROUP("Columns", "");
1867 ADD_PROPERTY(PropertyInfo(Variant::INT, "max_columns", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), "set_max_columns", "get_max_columns");
1868 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "same_column_width"), "set_same_column_width", "is_same_column_width");
1869 ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_column_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater,suffix:px"), "set_fixed_column_width", "get_fixed_column_width");
1870 ADD_GROUP("Icon", "");
1871 ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_mode", PROPERTY_HINT_ENUM, "Top,Left"), "set_icon_mode", "get_icon_mode");
1872 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "icon_scale"), "set_icon_scale", "get_icon_scale");
1873 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "fixed_icon_size", PROPERTY_HINT_NONE, "suffix:px"), "set_fixed_icon_size", "get_fixed_icon_size");
1874
1875 BIND_ENUM_CONSTANT(ICON_MODE_TOP);
1876 BIND_ENUM_CONSTANT(ICON_MODE_LEFT);
1877
1878 BIND_ENUM_CONSTANT(SELECT_SINGLE);
1879 BIND_ENUM_CONSTANT(SELECT_MULTI);
1880
1881 ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));
1882 ADD_SIGNAL(MethodInfo("empty_clicked", PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index")));
1883 ADD_SIGNAL(MethodInfo("item_clicked", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::VECTOR2, "at_position"), PropertyInfo(Variant::INT, "mouse_button_index")));
1884 ADD_SIGNAL(MethodInfo("multi_selected", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "selected")));
1885 ADD_SIGNAL(MethodInfo("item_activated", PropertyInfo(Variant::INT, "index")));
1886
1887 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ItemList, h_separation);
1888 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ItemList, v_separation);
1889
1890 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, panel_style, "panel");
1891 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, focus_style, "focus");
1892
1893 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, ItemList, font);
1894 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, ItemList, font_size);
1895 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ItemList, font_color);
1896 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ItemList, font_hovered_color);
1897 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ItemList, font_selected_color);
1898 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, ItemList, font_outline_size, "outline_size");
1899 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ItemList, font_outline_color);
1900
1901 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ItemList, line_separation);
1902 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ItemList, icon_margin);
1903 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, hovered_style, "hovered");
1904 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, selected_style, "selected");
1905 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, selected_focus_style, "selected_focus");
1906 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, cursor_style, "cursor_unfocused");
1907 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ItemList, cursor_focus_style, "cursor");
1908 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ItemList, guide_color);
1909}
1910
1911ItemList::ItemList() {
1912 scroll_bar = memnew(VScrollBar);
1913 add_child(scroll_bar, false, INTERNAL_MODE_FRONT);
1914 scroll_bar->connect("value_changed", callable_mp(this, &ItemList::_scroll_changed));
1915
1916 connect("mouse_exited", callable_mp(this, &ItemList::_mouse_exited));
1917
1918 set_focus_mode(FOCUS_ALL);
1919 set_clip_contents(true);
1920}
1921
1922ItemList::~ItemList() {
1923}
1924