| 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 | |