1 | /**************************************************************************/ |
2 | /* scroll_container.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 "scroll_container.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/os/os.h" |
35 | #include "scene/main/window.h" |
36 | #include "scene/theme/theme_db.h" |
37 | |
38 | Size2 ScrollContainer::get_minimum_size() const { |
39 | Size2 min_size; |
40 | |
41 | // Calculated in this function, as it needs to traverse all child controls once to calculate; |
42 | // and needs to be calculated before being used by update_scrollbars(). |
43 | largest_child_min_size = Size2(); |
44 | |
45 | for (int i = 0; i < get_child_count(); i++) { |
46 | Control *c = Object::cast_to<Control>(get_child(i)); |
47 | if (!c || !c->is_visible()) { |
48 | continue; |
49 | } |
50 | if (c->is_set_as_top_level()) { |
51 | continue; |
52 | } |
53 | if (c == h_scroll || c == v_scroll) { |
54 | continue; |
55 | } |
56 | |
57 | Size2 child_min_size = c->get_combined_minimum_size(); |
58 | |
59 | largest_child_min_size.x = MAX(largest_child_min_size.x, child_min_size.x); |
60 | largest_child_min_size.y = MAX(largest_child_min_size.y, child_min_size.y); |
61 | } |
62 | |
63 | if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) { |
64 | min_size.x = MAX(min_size.x, largest_child_min_size.x); |
65 | } |
66 | if (vertical_scroll_mode == SCROLL_MODE_DISABLED) { |
67 | min_size.y = MAX(min_size.y, largest_child_min_size.y); |
68 | } |
69 | |
70 | bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x); |
71 | bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y); |
72 | |
73 | if (h_scroll_show && h_scroll->get_parent() == this) { |
74 | min_size.y += h_scroll->get_minimum_size().y; |
75 | } |
76 | if (v_scroll_show && v_scroll->get_parent() == this) { |
77 | min_size.x += v_scroll->get_minimum_size().x; |
78 | } |
79 | |
80 | min_size += theme_cache.panel_style->get_minimum_size(); |
81 | return min_size; |
82 | } |
83 | |
84 | void ScrollContainer::_cancel_drag() { |
85 | set_physics_process_internal(false); |
86 | drag_touching_deaccel = false; |
87 | drag_touching = false; |
88 | drag_speed = Vector2(); |
89 | drag_accum = Vector2(); |
90 | last_drag_accum = Vector2(); |
91 | drag_from = Vector2(); |
92 | |
93 | if (beyond_deadzone) { |
94 | emit_signal(SNAME("scroll_ended" )); |
95 | propagate_notification(NOTIFICATION_SCROLL_END); |
96 | beyond_deadzone = false; |
97 | } |
98 | } |
99 | |
100 | void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { |
101 | ERR_FAIL_COND(p_gui_input.is_null()); |
102 | |
103 | double prev_v_scroll = v_scroll->get_value(); |
104 | double prev_h_scroll = h_scroll->get_value(); |
105 | bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED; |
106 | bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED; |
107 | |
108 | Ref<InputEventMouseButton> mb = p_gui_input; |
109 | |
110 | if (mb.is_valid()) { |
111 | if (mb->is_pressed()) { |
112 | bool scroll_value_modified = false; |
113 | |
114 | bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER; |
115 | if (mb->get_button_index() == MouseButton::WHEEL_UP) { |
116 | // By default, the vertical orientation takes precedence. This is an exception. |
117 | if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { |
118 | h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); |
119 | scroll_value_modified = true; |
120 | } else if (v_scroll_enabled) { |
121 | v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); |
122 | scroll_value_modified = true; |
123 | } |
124 | } |
125 | if (mb->get_button_index() == MouseButton::WHEEL_DOWN) { |
126 | if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) { |
127 | h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); |
128 | scroll_value_modified = true; |
129 | } else if (v_scroll_enabled) { |
130 | v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); |
131 | scroll_value_modified = true; |
132 | } |
133 | } |
134 | |
135 | bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER; |
136 | if (mb->get_button_index() == MouseButton::WHEEL_LEFT) { |
137 | // By default, the horizontal orientation takes precedence. This is an exception. |
138 | if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { |
139 | v_scroll->set_value(prev_v_scroll - v_scroll->get_page() / 8 * mb->get_factor()); |
140 | scroll_value_modified = true; |
141 | } else if (h_scroll_enabled) { |
142 | h_scroll->set_value(prev_h_scroll - h_scroll->get_page() / 8 * mb->get_factor()); |
143 | scroll_value_modified = true; |
144 | } |
145 | } |
146 | if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) { |
147 | if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) { |
148 | v_scroll->set_value(prev_v_scroll + v_scroll->get_page() / 8 * mb->get_factor()); |
149 | scroll_value_modified = true; |
150 | } else if (h_scroll_enabled) { |
151 | h_scroll->set_value(prev_h_scroll + h_scroll->get_page() / 8 * mb->get_factor()); |
152 | scroll_value_modified = true; |
153 | } |
154 | } |
155 | |
156 | if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) { |
157 | accept_event(); // Accept event if scroll changed. |
158 | return; |
159 | } |
160 | } |
161 | |
162 | bool is_touchscreen_available = DisplayServer::get_singleton()->is_touchscreen_available(); |
163 | if (!is_touchscreen_available) { |
164 | return; |
165 | } |
166 | |
167 | if (mb->get_button_index() != MouseButton::LEFT) { |
168 | return; |
169 | } |
170 | |
171 | if (mb->is_pressed()) { |
172 | if (drag_touching) { |
173 | _cancel_drag(); |
174 | } |
175 | |
176 | drag_speed = Vector2(); |
177 | drag_accum = Vector2(); |
178 | last_drag_accum = Vector2(); |
179 | drag_from = Vector2(prev_h_scroll, prev_v_scroll); |
180 | drag_touching = true; |
181 | drag_touching_deaccel = false; |
182 | beyond_deadzone = false; |
183 | time_since_motion = 0; |
184 | set_physics_process_internal(true); |
185 | time_since_motion = 0; |
186 | |
187 | } else { |
188 | if (drag_touching) { |
189 | if (drag_speed == Vector2()) { |
190 | _cancel_drag(); |
191 | } else { |
192 | drag_touching_deaccel = true; |
193 | } |
194 | } |
195 | } |
196 | return; |
197 | } |
198 | |
199 | Ref<InputEventMouseMotion> mm = p_gui_input; |
200 | |
201 | if (mm.is_valid()) { |
202 | if (drag_touching && !drag_touching_deaccel) { |
203 | Vector2 motion = mm->get_relative(); |
204 | drag_accum -= motion; |
205 | |
206 | if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) { |
207 | if (!beyond_deadzone) { |
208 | propagate_notification(NOTIFICATION_SCROLL_BEGIN); |
209 | emit_signal(SNAME("scroll_started" )); |
210 | |
211 | beyond_deadzone = true; |
212 | // Resetting drag_accum here ensures smooth scrolling after reaching deadzone. |
213 | drag_accum = -motion; |
214 | } |
215 | Vector2 diff = drag_from + drag_accum; |
216 | if (h_scroll_enabled) { |
217 | h_scroll->set_value(diff.x); |
218 | } else { |
219 | drag_accum.x = 0; |
220 | } |
221 | if (v_scroll_enabled) { |
222 | v_scroll->set_value(diff.y); |
223 | } else { |
224 | drag_accum.y = 0; |
225 | } |
226 | time_since_motion = 0; |
227 | } |
228 | } |
229 | |
230 | if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { |
231 | accept_event(); // Accept event if scroll changed. |
232 | } |
233 | return; |
234 | } |
235 | |
236 | Ref<InputEventPanGesture> pan_gesture = p_gui_input; |
237 | if (pan_gesture.is_valid()) { |
238 | if (h_scroll_enabled) { |
239 | h_scroll->set_value(prev_h_scroll + h_scroll->get_page() * pan_gesture->get_delta().x / 8); |
240 | } |
241 | if (v_scroll_enabled) { |
242 | v_scroll->set_value(prev_v_scroll + v_scroll->get_page() * pan_gesture->get_delta().y / 8); |
243 | } |
244 | |
245 | if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) { |
246 | accept_event(); // Accept event if scroll changed. |
247 | } |
248 | return; |
249 | } |
250 | } |
251 | |
252 | void ScrollContainer::_update_scrollbar_position() { |
253 | if (!_updating_scrollbars) { |
254 | return; |
255 | } |
256 | |
257 | Size2 hmin = h_scroll->get_combined_minimum_size(); |
258 | Size2 vmin = v_scroll->get_combined_minimum_size(); |
259 | |
260 | h_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0); |
261 | h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); |
262 | h_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height); |
263 | h_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); |
264 | |
265 | v_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width); |
266 | v_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); |
267 | v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 0); |
268 | v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); |
269 | |
270 | _updating_scrollbars = false; |
271 | } |
272 | |
273 | void ScrollContainer::_gui_focus_changed(Control *p_control) { |
274 | if (follow_focus && is_ancestor_of(p_control)) { |
275 | ensure_control_visible(p_control); |
276 | } |
277 | } |
278 | |
279 | void ScrollContainer::ensure_control_visible(Control *p_control) { |
280 | ERR_FAIL_COND_MSG(!is_ancestor_of(p_control), "Must be an ancestor of the control." ); |
281 | |
282 | Rect2 global_rect = get_global_rect(); |
283 | Rect2 other_rect = p_control->get_global_rect(); |
284 | float right_margin = v_scroll->is_visible() ? v_scroll->get_size().x : 0.0f; |
285 | float bottom_margin = h_scroll->is_visible() ? h_scroll->get_size().y : 0.0f; |
286 | |
287 | Vector2 diff = Vector2(MAX(MIN(other_rect.position.x, global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + (!is_layout_rtl() ? right_margin : 0.0f)), |
288 | MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin)); |
289 | |
290 | set_h_scroll(get_h_scroll() + (diff.x - global_rect.position.x)); |
291 | set_v_scroll(get_v_scroll() + (diff.y - global_rect.position.y)); |
292 | } |
293 | |
294 | void ScrollContainer::_reposition_children() { |
295 | update_scrollbars(); |
296 | Size2 size = get_size(); |
297 | Point2 ofs; |
298 | |
299 | size -= theme_cache.panel_style->get_minimum_size(); |
300 | ofs += theme_cache.panel_style->get_offset(); |
301 | bool rtl = is_layout_rtl(); |
302 | |
303 | if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons |
304 | size.y -= h_scroll->get_minimum_size().y; |
305 | } |
306 | |
307 | if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) { //scrolls may have been moved out for reasons |
308 | size.x -= v_scroll->get_minimum_size().x; |
309 | } |
310 | |
311 | for (int i = 0; i < get_child_count(); i++) { |
312 | Control *c = Object::cast_to<Control>(get_child(i)); |
313 | if (!c || !c->is_visible()) { |
314 | continue; |
315 | } |
316 | if (c->is_set_as_top_level()) { |
317 | continue; |
318 | } |
319 | if (c == h_scroll || c == v_scroll) { |
320 | continue; |
321 | } |
322 | Size2 minsize = c->get_combined_minimum_size(); |
323 | |
324 | Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize); |
325 | if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) { |
326 | r.size.width = MAX(size.width, minsize.width); |
327 | } |
328 | if (c->get_v_size_flags().has_flag(SIZE_EXPAND)) { |
329 | r.size.height = MAX(size.height, minsize.height); |
330 | } |
331 | r.position += ofs; |
332 | if (rtl && v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) { |
333 | r.position.x += v_scroll->get_minimum_size().x; |
334 | } |
335 | r.position = r.position.floor(); |
336 | fit_child_in_rect(c, r); |
337 | } |
338 | |
339 | queue_redraw(); |
340 | } |
341 | |
342 | void ScrollContainer::_notification(int p_what) { |
343 | switch (p_what) { |
344 | case NOTIFICATION_ENTER_TREE: |
345 | case NOTIFICATION_THEME_CHANGED: |
346 | case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: |
347 | case NOTIFICATION_TRANSLATION_CHANGED: { |
348 | _updating_scrollbars = true; |
349 | call_deferred(SNAME("_update_scrollbar_position" )); |
350 | } break; |
351 | |
352 | case NOTIFICATION_READY: { |
353 | Viewport *viewport = get_viewport(); |
354 | ERR_FAIL_NULL(viewport); |
355 | viewport->connect("gui_focus_changed" , callable_mp(this, &ScrollContainer::_gui_focus_changed)); |
356 | _reposition_children(); |
357 | } break; |
358 | |
359 | case NOTIFICATION_SORT_CHILDREN: { |
360 | _reposition_children(); |
361 | } break; |
362 | |
363 | case NOTIFICATION_DRAW: { |
364 | draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size())); |
365 | } break; |
366 | |
367 | case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { |
368 | if (drag_touching) { |
369 | if (drag_touching_deaccel) { |
370 | Vector2 pos = Vector2(h_scroll->get_value(), v_scroll->get_value()); |
371 | pos += drag_speed * get_physics_process_delta_time(); |
372 | |
373 | bool turnoff_h = false; |
374 | bool turnoff_v = false; |
375 | |
376 | if (pos.x < 0) { |
377 | pos.x = 0; |
378 | turnoff_h = true; |
379 | } |
380 | if (pos.x > (h_scroll->get_max() - h_scroll->get_page())) { |
381 | pos.x = h_scroll->get_max() - h_scroll->get_page(); |
382 | turnoff_h = true; |
383 | } |
384 | |
385 | if (pos.y < 0) { |
386 | pos.y = 0; |
387 | turnoff_v = true; |
388 | } |
389 | if (pos.y > (v_scroll->get_max() - v_scroll->get_page())) { |
390 | pos.y = v_scroll->get_max() - v_scroll->get_page(); |
391 | turnoff_v = true; |
392 | } |
393 | |
394 | if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) { |
395 | h_scroll->set_value(pos.x); |
396 | } |
397 | if (vertical_scroll_mode != SCROLL_MODE_DISABLED) { |
398 | v_scroll->set_value(pos.y); |
399 | } |
400 | |
401 | float sgn_x = drag_speed.x < 0 ? -1 : 1; |
402 | float val_x = Math::abs(drag_speed.x); |
403 | val_x -= 1000 * get_physics_process_delta_time(); |
404 | |
405 | if (val_x < 0) { |
406 | turnoff_h = true; |
407 | } |
408 | |
409 | float sgn_y = drag_speed.y < 0 ? -1 : 1; |
410 | float val_y = Math::abs(drag_speed.y); |
411 | val_y -= 1000 * get_physics_process_delta_time(); |
412 | |
413 | if (val_y < 0) { |
414 | turnoff_v = true; |
415 | } |
416 | |
417 | drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y); |
418 | |
419 | if (turnoff_h && turnoff_v) { |
420 | _cancel_drag(); |
421 | } |
422 | |
423 | } else { |
424 | if (time_since_motion == 0 || time_since_motion > 0.1) { |
425 | Vector2 diff = drag_accum - last_drag_accum; |
426 | last_drag_accum = drag_accum; |
427 | drag_speed = diff / get_physics_process_delta_time(); |
428 | } |
429 | |
430 | time_since_motion += get_physics_process_delta_time(); |
431 | } |
432 | } |
433 | } break; |
434 | } |
435 | } |
436 | |
437 | void ScrollContainer::update_scrollbars() { |
438 | Size2 size = get_size(); |
439 | size -= theme_cache.panel_style->get_minimum_size(); |
440 | |
441 | Size2 hmin = h_scroll->get_combined_minimum_size(); |
442 | Size2 vmin = v_scroll->get_combined_minimum_size(); |
443 | |
444 | h_scroll->set_visible(horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.width > size.width)); |
445 | v_scroll->set_visible(vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.height > size.height)); |
446 | |
447 | h_scroll->set_max(largest_child_min_size.width); |
448 | h_scroll->set_page((v_scroll->is_visible() && v_scroll->get_parent() == this) ? size.width - vmin.width : size.width); |
449 | |
450 | v_scroll->set_max(largest_child_min_size.height); |
451 | v_scroll->set_page((h_scroll->is_visible() && h_scroll->get_parent() == this) ? size.height - hmin.height : size.height); |
452 | |
453 | // Avoid scrollbar overlapping. |
454 | h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, (v_scroll->is_visible() && v_scroll->get_parent() == this) ? -vmin.width : 0); |
455 | v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, (h_scroll->is_visible() && h_scroll->get_parent() == this) ? -hmin.height : 0); |
456 | } |
457 | |
458 | void ScrollContainer::_scroll_moved(float) { |
459 | queue_sort(); |
460 | }; |
461 | |
462 | void ScrollContainer::set_h_scroll(int p_pos) { |
463 | h_scroll->set_value(p_pos); |
464 | _cancel_drag(); |
465 | } |
466 | |
467 | int ScrollContainer::get_h_scroll() const { |
468 | return h_scroll->get_value(); |
469 | } |
470 | |
471 | void ScrollContainer::set_v_scroll(int p_pos) { |
472 | v_scroll->set_value(p_pos); |
473 | _cancel_drag(); |
474 | } |
475 | |
476 | int ScrollContainer::get_v_scroll() const { |
477 | return v_scroll->get_value(); |
478 | } |
479 | |
480 | void ScrollContainer::set_horizontal_custom_step(float p_custom_step) { |
481 | h_scroll->set_custom_step(p_custom_step); |
482 | } |
483 | |
484 | float ScrollContainer::get_horizontal_custom_step() const { |
485 | return h_scroll->get_custom_step(); |
486 | } |
487 | |
488 | void ScrollContainer::set_vertical_custom_step(float p_custom_step) { |
489 | v_scroll->set_custom_step(p_custom_step); |
490 | } |
491 | |
492 | float ScrollContainer::get_vertical_custom_step() const { |
493 | return v_scroll->get_custom_step(); |
494 | } |
495 | |
496 | void ScrollContainer::set_horizontal_scroll_mode(ScrollMode p_mode) { |
497 | if (horizontal_scroll_mode == p_mode) { |
498 | return; |
499 | } |
500 | |
501 | horizontal_scroll_mode = p_mode; |
502 | update_minimum_size(); |
503 | queue_sort(); |
504 | } |
505 | |
506 | ScrollContainer::ScrollMode ScrollContainer::get_horizontal_scroll_mode() const { |
507 | return horizontal_scroll_mode; |
508 | } |
509 | |
510 | void ScrollContainer::set_vertical_scroll_mode(ScrollMode p_mode) { |
511 | if (vertical_scroll_mode == p_mode) { |
512 | return; |
513 | } |
514 | |
515 | vertical_scroll_mode = p_mode; |
516 | update_minimum_size(); |
517 | queue_sort(); |
518 | } |
519 | |
520 | ScrollContainer::ScrollMode ScrollContainer::get_vertical_scroll_mode() const { |
521 | return vertical_scroll_mode; |
522 | } |
523 | |
524 | int ScrollContainer::get_deadzone() const { |
525 | return deadzone; |
526 | } |
527 | |
528 | void ScrollContainer::set_deadzone(int p_deadzone) { |
529 | deadzone = p_deadzone; |
530 | } |
531 | |
532 | bool ScrollContainer::is_following_focus() const { |
533 | return follow_focus; |
534 | } |
535 | |
536 | void ScrollContainer::set_follow_focus(bool p_follow) { |
537 | follow_focus = p_follow; |
538 | } |
539 | |
540 | PackedStringArray ScrollContainer::get_configuration_warnings() const { |
541 | PackedStringArray warnings = Container::get_configuration_warnings(); |
542 | |
543 | int found = 0; |
544 | |
545 | for (int i = 0; i < get_child_count(); i++) { |
546 | Control *c = Object::cast_to<Control>(get_child(i)); |
547 | if (!c) { |
548 | continue; |
549 | } |
550 | if (c->is_set_as_top_level()) { |
551 | continue; |
552 | } |
553 | if (c == h_scroll || c == v_scroll) { |
554 | continue; |
555 | } |
556 | |
557 | found++; |
558 | } |
559 | |
560 | if (found != 1) { |
561 | warnings.push_back(RTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually." )); |
562 | } |
563 | |
564 | return warnings; |
565 | } |
566 | |
567 | HScrollBar *ScrollContainer::get_h_scroll_bar() { |
568 | return h_scroll; |
569 | } |
570 | |
571 | VScrollBar *ScrollContainer::get_v_scroll_bar() { |
572 | return v_scroll; |
573 | } |
574 | |
575 | void ScrollContainer::_bind_methods() { |
576 | ClassDB::bind_method(D_METHOD("_update_scrollbar_position" ), &ScrollContainer::_update_scrollbar_position); |
577 | |
578 | ClassDB::bind_method(D_METHOD("set_h_scroll" , "value" ), &ScrollContainer::set_h_scroll); |
579 | ClassDB::bind_method(D_METHOD("get_h_scroll" ), &ScrollContainer::get_h_scroll); |
580 | |
581 | ClassDB::bind_method(D_METHOD("set_v_scroll" , "value" ), &ScrollContainer::set_v_scroll); |
582 | ClassDB::bind_method(D_METHOD("get_v_scroll" ), &ScrollContainer::get_v_scroll); |
583 | |
584 | ClassDB::bind_method(D_METHOD("set_horizontal_custom_step" , "value" ), &ScrollContainer::set_horizontal_custom_step); |
585 | ClassDB::bind_method(D_METHOD("get_horizontal_custom_step" ), &ScrollContainer::get_horizontal_custom_step); |
586 | |
587 | ClassDB::bind_method(D_METHOD("set_vertical_custom_step" , "value" ), &ScrollContainer::set_vertical_custom_step); |
588 | ClassDB::bind_method(D_METHOD("get_vertical_custom_step" ), &ScrollContainer::get_vertical_custom_step); |
589 | |
590 | ClassDB::bind_method(D_METHOD("set_horizontal_scroll_mode" , "enable" ), &ScrollContainer::set_horizontal_scroll_mode); |
591 | ClassDB::bind_method(D_METHOD("get_horizontal_scroll_mode" ), &ScrollContainer::get_horizontal_scroll_mode); |
592 | |
593 | ClassDB::bind_method(D_METHOD("set_vertical_scroll_mode" , "enable" ), &ScrollContainer::set_vertical_scroll_mode); |
594 | ClassDB::bind_method(D_METHOD("get_vertical_scroll_mode" ), &ScrollContainer::get_vertical_scroll_mode); |
595 | |
596 | ClassDB::bind_method(D_METHOD("set_deadzone" , "deadzone" ), &ScrollContainer::set_deadzone); |
597 | ClassDB::bind_method(D_METHOD("get_deadzone" ), &ScrollContainer::get_deadzone); |
598 | |
599 | ClassDB::bind_method(D_METHOD("set_follow_focus" , "enabled" ), &ScrollContainer::set_follow_focus); |
600 | ClassDB::bind_method(D_METHOD("is_following_focus" ), &ScrollContainer::is_following_focus); |
601 | |
602 | ClassDB::bind_method(D_METHOD("get_h_scroll_bar" ), &ScrollContainer::get_h_scroll_bar); |
603 | ClassDB::bind_method(D_METHOD("get_v_scroll_bar" ), &ScrollContainer::get_v_scroll_bar); |
604 | ClassDB::bind_method(D_METHOD("ensure_control_visible" , "control" ), &ScrollContainer::ensure_control_visible); |
605 | |
606 | ADD_SIGNAL(MethodInfo("scroll_started" )); |
607 | ADD_SIGNAL(MethodInfo("scroll_ended" )); |
608 | |
609 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus" ), "set_follow_focus" , "is_following_focus" ); |
610 | |
611 | ADD_GROUP("Scroll" , "scroll_" ); |
612 | ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal" , PROPERTY_HINT_NONE, "suffix:px" ), "set_h_scroll" , "get_h_scroll" ); |
613 | ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical" , PROPERTY_HINT_NONE, "suffix:px" ), "set_v_scroll" , "get_v_scroll" ); |
614 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step" , PROPERTY_HINT_RANGE, "-1,4096,suffix:px" ), "set_horizontal_custom_step" , "get_horizontal_custom_step" ); |
615 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical_custom_step" , PROPERTY_HINT_RANGE, "-1,4096,suffix:px" ), "set_vertical_custom_step" , "get_vertical_custom_step" ); |
616 | ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode" , PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show" ), "set_horizontal_scroll_mode" , "get_horizontal_scroll_mode" ); |
617 | ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode" , PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show" ), "set_vertical_scroll_mode" , "get_vertical_scroll_mode" ); |
618 | ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone" ), "set_deadzone" , "get_deadzone" ); |
619 | |
620 | BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED); |
621 | BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO); |
622 | BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS); |
623 | BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER); |
624 | |
625 | BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel" ); |
626 | |
627 | GLOBAL_DEF("gui/common/default_scroll_deadzone" , 0); |
628 | }; |
629 | |
630 | ScrollContainer::ScrollContainer() { |
631 | h_scroll = memnew(HScrollBar); |
632 | h_scroll->set_name("_h_scroll" ); |
633 | add_child(h_scroll, false, INTERNAL_MODE_BACK); |
634 | h_scroll->connect("value_changed" , callable_mp(this, &ScrollContainer::_scroll_moved)); |
635 | |
636 | v_scroll = memnew(VScrollBar); |
637 | v_scroll->set_name("_v_scroll" ); |
638 | add_child(v_scroll, false, INTERNAL_MODE_BACK); |
639 | v_scroll->connect("value_changed" , callable_mp(this, &ScrollContainer::_scroll_moved)); |
640 | |
641 | deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone" ); |
642 | |
643 | set_clip_contents(true); |
644 | }; |
645 | |