split_container.cpp 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321
  1. /**************************************************************************/
  2. /* split_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. #include "split_container.h"
  31. #include "split_container.compat.inc"
  32. #include "scene/gui/texture_rect.h"
  33. #include "scene/main/viewport.h"
  34. #include "scene/theme/theme_db.h"
  35. void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
  36. ERR_FAIL_COND(p_event.is_null());
  37. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  38. if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
  39. return;
  40. }
  41. Ref<InputEventMouseButton> mb = p_event;
  42. if (mb.is_valid()) {
  43. if (mb->get_button_index() == MouseButton::LEFT) {
  44. if (mb->is_pressed()) {
  45. // To match the visual position, clamp on the first split.
  46. sc->_update_dragger_positions(0);
  47. dragging = true;
  48. sc->emit_signal(SNAME("drag_started"));
  49. start_drag_split_offset = sc->get_split_offset(dragger_index);
  50. if (sc->vertical) {
  51. drag_from = (int)get_transform().xform(mb->get_position()).y;
  52. } else {
  53. drag_from = (int)get_transform().xform(mb->get_position()).x;
  54. }
  55. } else {
  56. dragging = false;
  57. queue_redraw();
  58. sc->emit_signal(SNAME("drag_ended"));
  59. }
  60. }
  61. }
  62. Ref<InputEventMouseMotion> mm = p_event;
  63. if (mm.is_valid()) {
  64. if (!dragging) {
  65. return;
  66. }
  67. Vector2i in_parent_pos = get_transform().xform(mm->get_position());
  68. int new_drag_offset;
  69. if (!sc->vertical && is_layout_rtl()) {
  70. new_drag_offset = start_drag_split_offset - (in_parent_pos.x - drag_from);
  71. } else {
  72. new_drag_offset = start_drag_split_offset + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
  73. }
  74. sc->set_split_offset(new_drag_offset, dragger_index);
  75. sc->_update_dragger_positions(dragger_index);
  76. sc->queue_sort();
  77. sc->emit_signal(SNAME("dragged"), sc->get_split_offset(dragger_index));
  78. }
  79. }
  80. Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
  81. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  82. if (!sc->collapsed && sc->dragging_enabled) {
  83. return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
  84. }
  85. return Control::get_cursor_shape(p_pos);
  86. }
  87. void SplitContainerDragger::_accessibility_action_inc(const Variant &p_data) {
  88. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  89. if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
  90. return;
  91. }
  92. sc->set_split_offset(sc->get_split_offset(dragger_index) - 10, dragger_index);
  93. sc->clamp_split_offset(dragger_index);
  94. }
  95. void SplitContainerDragger::_accessibility_action_dec(const Variant &p_data) {
  96. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  97. if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
  98. return;
  99. }
  100. sc->set_split_offset(sc->get_split_offset(dragger_index) + 10, dragger_index);
  101. sc->clamp_split_offset(dragger_index);
  102. }
  103. void SplitContainerDragger::_accessibility_action_set_value(const Variant &p_data) {
  104. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  105. if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
  106. return;
  107. }
  108. sc->set_split_offset(p_data, dragger_index);
  109. sc->clamp_split_offset(dragger_index);
  110. }
  111. void SplitContainerDragger::_touch_dragger_mouse_exited() {
  112. if (!dragging) {
  113. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  114. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color);
  115. }
  116. }
  117. void SplitContainerDragger::_touch_dragger_gui_input(const Ref<InputEvent> &p_event) {
  118. if (!touch_dragger) {
  119. return;
  120. }
  121. Ref<InputEventMouseMotion> mm = p_event;
  122. Ref<InputEventMouseButton> mb = p_event;
  123. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  124. if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
  125. if (mb->is_pressed()) {
  126. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_pressed_color);
  127. } else {
  128. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color);
  129. }
  130. }
  131. if (mm.is_valid() && !dragging) {
  132. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_hover_color);
  133. }
  134. }
  135. void SplitContainerDragger::set_touch_dragger_enabled(bool p_enabled) {
  136. if (p_enabled) {
  137. touch_dragger = memnew(TextureRect);
  138. update_touch_dragger();
  139. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  140. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color);
  141. touch_dragger->connect(SceneStringName(gui_input), callable_mp(this, &SplitContainerDragger::_touch_dragger_gui_input));
  142. touch_dragger->connect(SceneStringName(mouse_exited), callable_mp(this, &SplitContainerDragger::_touch_dragger_mouse_exited));
  143. add_child(touch_dragger, false, Node::INTERNAL_MODE_FRONT);
  144. } else {
  145. if (touch_dragger) {
  146. touch_dragger->queue_free();
  147. touch_dragger = nullptr;
  148. }
  149. }
  150. queue_redraw();
  151. }
  152. void SplitContainerDragger::update_touch_dragger() {
  153. if (!touch_dragger) {
  154. return;
  155. }
  156. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  157. touch_dragger->set_texture(sc->_get_touch_dragger_icon());
  158. touch_dragger->set_anchors_and_offsets_preset(Control::PRESET_CENTER);
  159. touch_dragger->set_default_cursor_shape(sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
  160. }
  161. void SplitContainerDragger::_notification(int p_what) {
  162. switch (p_what) {
  163. case NOTIFICATION_ACCESSIBILITY_UPDATE: {
  164. RID ae = get_accessibility_element();
  165. ERR_FAIL_COND(ae.is_null());
  166. DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPLITTER);
  167. DisplayServer::get_singleton()->accessibility_update_set_name(ae, RTR("Drag to resize"));
  168. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  169. if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
  170. return;
  171. }
  172. sc->clamp_split_offset(dragger_index);
  173. DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->get_split_offset(dragger_index));
  174. DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_dec));
  175. DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_inc));
  176. DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &SplitContainerDragger::_accessibility_action_set_value));
  177. } break;
  178. case NOTIFICATION_THEME_CHANGED: {
  179. if (touch_dragger) {
  180. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  181. touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color);
  182. touch_dragger->set_texture(sc->_get_touch_dragger_icon());
  183. }
  184. } break;
  185. case NOTIFICATION_MOUSE_ENTER: {
  186. mouse_inside = true;
  187. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  188. if (sc->theme_cache.autohide) {
  189. queue_redraw();
  190. }
  191. } break;
  192. case NOTIFICATION_MOUSE_EXIT: {
  193. mouse_inside = false;
  194. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  195. if (sc->theme_cache.autohide) {
  196. queue_redraw();
  197. }
  198. } break;
  199. case NOTIFICATION_FOCUS_EXIT: {
  200. if (dragging) {
  201. dragging = false;
  202. queue_redraw();
  203. }
  204. } break;
  205. case NOTIFICATION_VISIBILITY_CHANGED: {
  206. if (dragging && !is_visible_in_tree()) {
  207. dragging = false;
  208. }
  209. } break;
  210. case NOTIFICATION_DRAW: {
  211. SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
  212. draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect);
  213. if (sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide) && !sc->touch_dragger_enabled) {
  214. Ref<Texture2D> tex = sc->_get_grabber_icon();
  215. float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y);
  216. if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits.
  217. draw_texture(tex, (split_bar_rect.get_position() + (split_bar_rect.get_size() - tex->get_size()) * 0.5));
  218. }
  219. }
  220. if (sc->show_drag_area && Engine::get_singleton()->is_editor_hint()) {
  221. draw_rect(Rect2(Vector2(0, 0), get_size()), sc->dragging_enabled ? Color(1, 1, 0, 0.3) : Color(1, 0, 0, 0.3));
  222. }
  223. } break;
  224. }
  225. }
  226. SplitContainerDragger::SplitContainerDragger() {
  227. set_focus_mode(FOCUS_ACCESSIBILITY);
  228. }
  229. Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
  230. if (is_fixed) {
  231. return theme_cache.grabber_icon;
  232. } else {
  233. if (vertical) {
  234. return theme_cache.grabber_icon_v;
  235. } else {
  236. return theme_cache.grabber_icon_h;
  237. }
  238. }
  239. }
  240. Ref<Texture2D> SplitContainer::_get_touch_dragger_icon() const {
  241. if (is_fixed) {
  242. return theme_cache.touch_dragger_icon;
  243. } else {
  244. if (vertical) {
  245. return theme_cache.touch_dragger_icon_v;
  246. } else {
  247. return theme_cache.touch_dragger_icon_h;
  248. }
  249. }
  250. }
  251. int SplitContainer::_get_separation() const {
  252. if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) {
  253. return 0;
  254. }
  255. if (touch_dragger_enabled) {
  256. return theme_cache.separation;
  257. }
  258. // DRAGGER_VISIBLE or DRAGGER_HIDDEN.
  259. Ref<Texture2D> g = _get_grabber_icon();
  260. return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width());
  261. }
  262. Point2i SplitContainer::_get_valid_range(int p_dragger_index) const {
  263. ERR_FAIL_INDEX_V(p_dragger_index, (int)dragger_positions.size(), Point2i());
  264. const int axis = vertical ? 1 : 0;
  265. const int sep = _get_separation();
  266. // Sum the minimum sizes on the left and right sides of the dragger.
  267. Point2i position_range = Point2i(0, (int)get_size()[axis]);
  268. position_range.x += sep * p_dragger_index;
  269. position_range.y -= sep * ((int)dragger_positions.size() - p_dragger_index);
  270. for (int i = 0; i < (int)valid_children.size(); i++) {
  271. Control *child = valid_children[i];
  272. ERR_FAIL_NULL_V(child, Point2i());
  273. if (i <= p_dragger_index) {
  274. position_range.x += (int)child->get_combined_minimum_size()[axis];
  275. } else if (i > p_dragger_index) {
  276. position_range.y -= (int)child->get_combined_minimum_size()[axis];
  277. }
  278. }
  279. return position_range;
  280. }
  281. PackedInt32Array SplitContainer::_get_desired_sizes() const {
  282. ERR_FAIL_COND_V((int)default_dragger_positions.size() != split_offsets.size() || (int)valid_children.size() - 1 != split_offsets.size(), PackedInt32Array());
  283. PackedInt32Array desired_sizes;
  284. desired_sizes.resize_uninitialized((int)valid_children.size());
  285. const int sep = _get_separation();
  286. const int axis = vertical ? 1 : 0;
  287. int desired_start_pos = 0;
  288. for (int i = 0; i < (int)valid_children.size() - 1; i++) {
  289. const int desired_end_pos = default_dragger_positions[i] + split_offsets[i];
  290. desired_sizes.write[i] = desired_end_pos - desired_start_pos;
  291. desired_start_pos = desired_end_pos + sep;
  292. }
  293. desired_sizes.write[(int)valid_children.size() - 1] = (int)get_size()[axis] - desired_start_pos;
  294. return desired_sizes;
  295. }
  296. void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes, int p_priority_index) {
  297. const int sep = _get_separation();
  298. const int axis = vertical ? 1 : 0;
  299. const real_t size = get_size()[axis];
  300. real_t total_desired_size = 0;
  301. if (!p_desired_sizes.is_empty()) {
  302. ERR_FAIL_COND((int)valid_children.size() != p_desired_sizes.size());
  303. total_desired_size += sep * (p_desired_sizes.size() - 1);
  304. }
  305. struct StretchData {
  306. real_t min_size = 0;
  307. real_t stretch_ratio = 0.0;
  308. real_t final_size = 0;
  309. bool priority = false;
  310. };
  311. // First pass, determine the total stretch amount.
  312. real_t stretch_total = 0;
  313. LocalVector<StretchData> stretch_data;
  314. for (int i = 0; i < (int)valid_children.size(); i++) {
  315. Control *child = valid_children[i];
  316. StretchData sdata;
  317. sdata.min_size = child->get_combined_minimum_size()[axis];
  318. sdata.final_size = MAX(sdata.min_size, p_desired_sizes.is_empty() ? 0 : p_desired_sizes[i]);
  319. total_desired_size += sdata.final_size;
  320. sdata.priority = i == p_priority_index;
  321. // Treat the priority child as not expanded, so it doesn't shrink with other expanded children.
  322. if (i != p_priority_index && child->get_stretch_ratio() > 0 && (vertical ? child->get_v_size_flags() : child->get_h_size_flags()).has_flag(SIZE_EXPAND)) {
  323. sdata.stretch_ratio = child->get_stretch_ratio();
  324. stretch_total += sdata.stretch_ratio;
  325. }
  326. stretch_data.push_back(sdata);
  327. }
  328. real_t available_space = size - total_desired_size;
  329. // Grow expanding children.
  330. if (available_space > 0) {
  331. const real_t grow_amount = available_space / stretch_total;
  332. for (StretchData &sdata : stretch_data) {
  333. if (sdata.stretch_ratio <= 0) {
  334. continue;
  335. }
  336. const real_t prev_size = sdata.final_size;
  337. sdata.final_size = prev_size + grow_amount * sdata.stretch_ratio;
  338. const real_t size_diff = prev_size - sdata.final_size;
  339. available_space += size_diff;
  340. }
  341. }
  342. // Shrink expanding children.
  343. while (available_space < 0) {
  344. real_t shrinkable_stretch_ratio = 0.0;
  345. real_t shrinkable_amount = 0.0;
  346. for (const StretchData &sdata : stretch_data) {
  347. if (sdata.stretch_ratio <= 0 || sdata.final_size <= sdata.min_size) {
  348. continue;
  349. }
  350. shrinkable_stretch_ratio += sdata.stretch_ratio;
  351. shrinkable_amount += sdata.final_size - sdata.min_size;
  352. }
  353. if (shrinkable_stretch_ratio == 0) {
  354. break;
  355. }
  356. const real_t shrink_amount = MIN(-available_space, shrinkable_amount) / shrinkable_stretch_ratio;
  357. if (Math::is_zero_approx(shrink_amount)) {
  358. break;
  359. }
  360. const real_t prev_available_space = available_space;
  361. for (StretchData &sdata : stretch_data) {
  362. if (sdata.stretch_ratio <= 0 || sdata.final_size <= sdata.min_size) {
  363. continue;
  364. }
  365. const real_t prev_size = sdata.final_size;
  366. sdata.final_size = CLAMP(prev_size - shrink_amount * sdata.stretch_ratio, sdata.min_size, sdata.final_size);
  367. const real_t size_diff = prev_size - sdata.final_size;
  368. available_space += size_diff;
  369. }
  370. if (Math::is_equal_approx(available_space, prev_available_space)) {
  371. // Shrinking can fail due to values being too small to have an effect but too large for `is_zero_approx`.
  372. break;
  373. }
  374. }
  375. // Shrink non-expanding children.
  376. bool skip_priority_child = true;
  377. while (available_space < 0) {
  378. // Get largest and target sizes. The target size is the second largest size.
  379. real_t largest_size = 0;
  380. real_t target_size = 0;
  381. int largest_count = 0;
  382. for (const StretchData &sdata : stretch_data) {
  383. if (sdata.final_size <= sdata.min_size || (skip_priority_child && sdata.priority)) {
  384. continue;
  385. }
  386. if (sdata.final_size > largest_size) {
  387. target_size = largest_size;
  388. largest_size = sdata.final_size;
  389. largest_count = 1;
  390. } else if (sdata.final_size == largest_size) {
  391. largest_count++;
  392. } else if (sdata.final_size < largest_size && sdata.final_size > target_size) {
  393. target_size = sdata.final_size;
  394. }
  395. }
  396. if (largest_size <= 0) {
  397. if (skip_priority_child) {
  398. // Retry with priority child.
  399. skip_priority_child = false;
  400. continue;
  401. } else {
  402. // No more children to shrink.
  403. break;
  404. }
  405. }
  406. // Don't shrink smaller than needed.
  407. target_size = MAX(target_size, largest_size + available_space / largest_count);
  408. const real_t prev_available_space = available_space;
  409. for (StretchData &sdata : stretch_data) {
  410. if (sdata.final_size <= sdata.min_size || (skip_priority_child && sdata.priority)) {
  411. continue;
  412. }
  413. // Shrink all largest elements.
  414. if (sdata.final_size == largest_size) {
  415. sdata.final_size = CLAMP(target_size, sdata.min_size, sdata.final_size);
  416. const real_t size_diff = largest_size - sdata.final_size;
  417. available_space += size_diff;
  418. }
  419. }
  420. if (Math::is_zero_approx(available_space) || Math::is_equal_approx(available_space, prev_available_space)) {
  421. break;
  422. }
  423. }
  424. ERR_FAIL_COND((int)default_dragger_positions.size() != (int)stretch_data.size() - 1);
  425. // Update the split offsets to match the desired sizes.
  426. split_offsets.resize(MAX(1, (int)default_dragger_positions.size()));
  427. int pos = 0;
  428. real_t error_accumulator = 0.0;
  429. for (int i = 0; i < (int)default_dragger_positions.size(); i++) {
  430. int final_size = (int)stretch_data[i].final_size;
  431. if (final_size == stretch_data[i].final_size) {
  432. error_accumulator += stretch_data[i].final_size - final_size;
  433. if (error_accumulator > 1.0) {
  434. error_accumulator -= 1.0;
  435. final_size += 1;
  436. }
  437. }
  438. pos += final_size;
  439. split_offsets.write[i] = pos - default_dragger_positions[i];
  440. pos += sep;
  441. }
  442. }
  443. void SplitContainer::_update_default_dragger_positions() {
  444. if (valid_children.size() <= 1u) {
  445. default_dragger_positions.clear();
  446. return;
  447. }
  448. default_dragger_positions.resize((int)valid_children.size() - 1);
  449. const int sep = _get_separation();
  450. const int axis = vertical ? 1 : 0;
  451. const int size = (int)get_size()[axis];
  452. struct StretchData {
  453. int min_size = 0;
  454. real_t stretch_ratio = 0.0;
  455. int final_size = 0;
  456. bool expand_flag = false;
  457. bool will_stretch = false;
  458. };
  459. // First pass, determine the total stretch amount.
  460. real_t stretchable_space = size - sep * ((int)valid_children.size() - 1);
  461. real_t stretch_total = 0;
  462. int expand_count = 0;
  463. LocalVector<StretchData> stretch_data;
  464. for (const Control *child : valid_children) {
  465. StretchData sdata;
  466. sdata.min_size = (int)child->get_combined_minimum_size()[axis];
  467. sdata.final_size = sdata.min_size;
  468. if ((vertical ? child->get_v_size_flags() : child->get_h_size_flags()).has_flag(SIZE_EXPAND) && child->get_stretch_ratio() > 0) {
  469. sdata.stretch_ratio = child->get_stretch_ratio();
  470. stretch_total += sdata.stretch_ratio;
  471. sdata.expand_flag = true;
  472. sdata.will_stretch = true;
  473. expand_count++;
  474. } else {
  475. stretchable_space -= sdata.min_size;
  476. }
  477. stretch_data.push_back(sdata);
  478. }
  479. #ifndef DISABLE_DEPRECATED
  480. if (expand_count == 2 && valid_children.size() == 2u) {
  481. // Special case when there are 2 expanded children, ignore minimum sizes.
  482. const real_t ratio = stretch_data[0].stretch_ratio / (stretch_data[0].stretch_ratio + stretch_data[1].stretch_ratio);
  483. default_dragger_positions[0] = (int)(size * ratio - sep * 0.5);
  484. return;
  485. }
  486. #endif // DISABLE_DEPRECATED
  487. // Determine final sizes if stretching.
  488. while (stretch_total > 0.0 && stretchable_space > 0.0) {
  489. bool refit_successful = true;
  490. // Keep track of accumulated error in pixels.
  491. float error = 0.0;
  492. for (StretchData &sdata : stretch_data) {
  493. if (!sdata.will_stretch) {
  494. continue;
  495. }
  496. // Check if it reaches its minimum size.
  497. const float desired_stretch_size = sdata.stretch_ratio / stretch_total * stretchable_space;
  498. error += desired_stretch_size - (int)desired_stretch_size;
  499. if (desired_stretch_size < sdata.min_size) {
  500. // Will not be stretched, remove and retry.
  501. stretch_total -= sdata.stretch_ratio;
  502. stretchable_space -= sdata.min_size;
  503. sdata.will_stretch = false;
  504. sdata.final_size = sdata.min_size;
  505. refit_successful = false;
  506. break;
  507. } else {
  508. sdata.final_size = (int)desired_stretch_size;
  509. // Dump accumulated error if one pixel or more.
  510. if (error >= 1.0) {
  511. sdata.final_size += 1;
  512. error -= 1;
  513. }
  514. }
  515. }
  516. if (refit_successful) {
  517. break;
  518. }
  519. }
  520. // Set the default positions.
  521. int pos = 0;
  522. int expands_seen = 0;
  523. for (int i = 0; i < (int)default_dragger_positions.size(); i++) {
  524. pos += stretch_data[i].final_size;
  525. if (stretch_data[i].expand_flag) {
  526. expands_seen += 1;
  527. }
  528. if (expands_seen == 0) {
  529. // Before all expand flags.
  530. default_dragger_positions[i] = 0;
  531. } else if (expands_seen >= expand_count) {
  532. // After all expand flags.
  533. default_dragger_positions[i] = size - sep;
  534. } else {
  535. default_dragger_positions[i] = pos;
  536. }
  537. pos += sep;
  538. }
  539. }
  540. void SplitContainer::_update_dragger_positions(int p_clamp_index) {
  541. if (p_clamp_index != -1) {
  542. ERR_FAIL_INDEX(p_clamp_index, (int)dragger_positions.size());
  543. }
  544. const int sep = _get_separation();
  545. const int axis = vertical ? 1 : 0;
  546. const int size = (int)get_size()[axis];
  547. dragger_positions.resize(default_dragger_positions.size());
  548. if (split_offsets.size() < (int)default_dragger_positions.size() || split_offsets.is_empty()) {
  549. split_offsets.resize_initialized(MAX(1, (int)default_dragger_positions.size()));
  550. }
  551. if (collapsed) {
  552. for (int i = 0; i < (int)dragger_positions.size(); i++) {
  553. dragger_positions[i] = default_dragger_positions[i];
  554. const Point2i valid_range = _get_valid_range(i);
  555. dragger_positions[i] = CLAMP(dragger_positions[i], valid_range.x, valid_range.y);
  556. if (p_clamp_index != -1) {
  557. split_offsets.write[i] = dragger_positions[i] - default_dragger_positions[i];
  558. }
  559. if (!vertical && is_layout_rtl()) {
  560. dragger_positions[i] = size - dragger_positions[i] - sep;
  561. }
  562. }
  563. return;
  564. }
  565. // Use split_offsets to find the desired dragger positions.
  566. for (int i = 0; i < (int)dragger_positions.size(); i++) {
  567. // Clamp the desired position to acceptable values.
  568. const Point2i valid_range = _get_valid_range(i);
  569. dragger_positions[i] = CLAMP(default_dragger_positions[i] + split_offsets[i], valid_range.x, valid_range.y);
  570. }
  571. // Prevent overlaps.
  572. if (p_clamp_index == -1) {
  573. // Check each dragger with the one to the right of it.
  574. for (int i = 0; i < (int)dragger_positions.size() - 1; i++) {
  575. const int check_min_size = (int)valid_children[i + 1]->get_combined_minimum_size()[axis];
  576. const int push_pos = dragger_positions[i] + sep + check_min_size;
  577. if (dragger_positions[i + 1] < push_pos) {
  578. dragger_positions[i + 1] = push_pos;
  579. const Point2i valid_range = _get_valid_range(i);
  580. dragger_positions[i] = CLAMP(dragger_positions[i], valid_range.x, valid_range.y);
  581. }
  582. }
  583. } else {
  584. // Prioritize the active dragger.
  585. const int dragging_position = dragger_positions[p_clamp_index];
  586. // Push overlapping draggers to the left.
  587. int accumulated_min_size = (int)valid_children[p_clamp_index]->get_combined_minimum_size()[axis];
  588. for (int i = p_clamp_index - 1; i >= 0; i--) {
  589. const int push_pos = dragging_position - sep * (p_clamp_index - i) - accumulated_min_size;
  590. if (dragger_positions[i] > push_pos) {
  591. dragger_positions[i] = push_pos;
  592. }
  593. accumulated_min_size += (int)valid_children[i]->get_combined_minimum_size()[axis];
  594. }
  595. // Push overlapping draggers to the right.
  596. accumulated_min_size = 0;
  597. for (int i = p_clamp_index + 1; i < (int)dragger_positions.size(); i++) {
  598. accumulated_min_size += (int)valid_children[i]->get_combined_minimum_size()[axis];
  599. const int push_pos = dragging_position + sep * (i - p_clamp_index) + accumulated_min_size;
  600. if (dragger_positions[i] < push_pos) {
  601. dragger_positions[i] = push_pos;
  602. }
  603. }
  604. }
  605. // Clamp the split_offset if requested.
  606. if (p_clamp_index != -1) {
  607. for (int i = 0; i < (int)dragger_positions.size(); i++) {
  608. split_offsets.write[i] = dragger_positions[i] - default_dragger_positions[i];
  609. }
  610. }
  611. // Invert if rtl.
  612. if (!vertical && is_layout_rtl()) {
  613. for (int i = 0; i < (int)dragger_positions.size(); i++) {
  614. dragger_positions[i] = size - dragger_positions[i] - sep;
  615. }
  616. }
  617. }
  618. void SplitContainer::_resort() {
  619. if (!is_visible_in_tree()) {
  620. return;
  621. }
  622. if (valid_children.size() < 2u) {
  623. if (valid_children.size() == 1u) {
  624. // Only one valid child.
  625. Control *child = valid_children[0];
  626. fit_child_in_rect(child, Rect2(Point2(), get_size()));
  627. }
  628. for (SplitContainerDragger *dragger : dragging_area_controls) {
  629. dragger->hide();
  630. }
  631. return;
  632. }
  633. for (SplitContainerDragger *dragger : dragging_area_controls) {
  634. dragger->set_visible(!collapsed);
  635. if (touch_dragger_enabled) {
  636. dragger->touch_dragger->set_visible(dragging_enabled);
  637. }
  638. }
  639. _update_default_dragger_positions();
  640. _update_dragger_positions();
  641. const int sep = _get_separation();
  642. const int axis = vertical ? 1 : 0;
  643. const Size2i new_size = get_size();
  644. const bool rtl = is_layout_rtl();
  645. // Move the children.
  646. for (int i = 0; i < (int)valid_children.size(); i++) {
  647. Control *child = valid_children[i];
  648. int start_pos;
  649. int end_pos;
  650. if (!vertical && rtl) {
  651. start_pos = i >= (int)dragger_positions.size() ? 0 : dragger_positions[i] + sep;
  652. end_pos = i == 0 ? new_size[axis] : dragger_positions[i - 1];
  653. } else {
  654. start_pos = i == 0 ? 0 : dragger_positions[i - 1] + sep;
  655. end_pos = i >= (int)dragger_positions.size() ? new_size[axis] : dragger_positions[i];
  656. }
  657. int size = end_pos - start_pos;
  658. if (vertical) {
  659. fit_child_in_rect(child, Rect2(Point2(0, start_pos), Size2(new_size.width, size)));
  660. } else {
  661. fit_child_in_rect(child, Rect2(Point2(start_pos, 0), Size2(size, new_size.height)));
  662. }
  663. }
  664. _update_draggers();
  665. // Update dragger positions.
  666. const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
  667. const float split_bar_offset = (dragger_ctrl_size - sep) * 0.5;
  668. ERR_FAIL_COND(dragging_area_controls.size() != dragger_positions.size());
  669. for (int i = 0; i < (int)dragger_positions.size(); i++) {
  670. dragging_area_controls[i]->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE);
  671. if (vertical) {
  672. const Rect2 split_bar_rect = Rect2(rtl ? drag_area_margin_end : drag_area_margin_begin, dragger_positions[i], new_size.width - drag_area_margin_begin - drag_area_margin_end, sep);
  673. dragging_area_controls[i]->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size));
  674. dragging_area_controls[i]->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size);
  675. } else {
  676. const Rect2 split_bar_rect = Rect2(dragger_positions[i], drag_area_margin_begin, sep, new_size.height - drag_area_margin_begin - drag_area_margin_end);
  677. dragging_area_controls[i]->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y));
  678. dragging_area_controls[i]->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (rtl ? -1 : 1), 0.0), split_bar_rect.size);
  679. }
  680. dragging_area_controls[i]->queue_redraw();
  681. }
  682. queue_redraw();
  683. }
  684. void SplitContainer::_update_draggers() {
  685. const int valid_child_count = (int)valid_children.size();
  686. const int dragger_count = MAX(valid_child_count - 1, 1);
  687. const int draggers_size_diff = dragger_count - (int)dragging_area_controls.size();
  688. // Add new draggers.
  689. for (int i = 0; i < draggers_size_diff; i++) {
  690. SplitContainerDragger *dragger = memnew(SplitContainerDragger);
  691. dragging_area_controls.push_back(dragger);
  692. add_child(dragger, false, Node::INTERNAL_MODE_BACK);
  693. if (touch_dragger_enabled) {
  694. dragger->set_touch_dragger_enabled(true);
  695. }
  696. }
  697. // Remove extra draggers.
  698. for (int i = 0; i < -draggers_size_diff; i++) {
  699. const int remove_at = (int)dragging_area_controls.size() - 1;
  700. SplitContainerDragger *dragger = dragging_area_controls[remove_at];
  701. dragging_area_controls.remove_at(remove_at);
  702. // replace_by removes all children, so make sure it is a child before removing.
  703. if (dragger->get_parent() == this) {
  704. remove_child(dragger);
  705. }
  706. memdelete(dragger);
  707. }
  708. // Make sure draggers have the correct index.
  709. for (int i = 0; i < (int)dragging_area_controls.size(); i++) {
  710. dragging_area_controls[i]->dragger_index = i;
  711. }
  712. }
  713. Size2 SplitContainer::get_minimum_size() const {
  714. const int sep = _get_separation();
  715. const int axis = vertical ? 1 : 0;
  716. const int other_axis = vertical ? 0 : 1;
  717. Size2i minimum;
  718. if (valid_children.size() >= 2u) {
  719. minimum[axis] += sep * ((int)valid_children.size() - 1);
  720. }
  721. for (const Control *child : valid_children) {
  722. const Size2 min_size = child->get_combined_minimum_size();
  723. minimum[axis] += (int)min_size[axis];
  724. minimum[other_axis] = (int)MAX(minimum[other_axis], min_size[other_axis]);
  725. }
  726. return minimum;
  727. }
  728. void SplitContainer::_validate_property(PropertyInfo &p_property) const {
  729. if (is_fixed && p_property.name == "vertical") {
  730. p_property.usage = PROPERTY_USAGE_NONE;
  731. }
  732. }
  733. void SplitContainer::_notification(int p_what) {
  734. switch (p_what) {
  735. case NOTIFICATION_TRANSLATION_CHANGED:
  736. case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
  737. queue_sort();
  738. } break;
  739. case NOTIFICATION_POSTINITIALIZE: {
  740. initialized = true;
  741. } break;
  742. case NOTIFICATION_SORT_CHILDREN: {
  743. _resort();
  744. } break;
  745. case NOTIFICATION_THEME_CHANGED: {
  746. update_minimum_size();
  747. } break;
  748. case NOTIFICATION_PREDELETE: {
  749. valid_children.clear();
  750. dragging_area_controls.clear();
  751. } break;
  752. }
  753. }
  754. void SplitContainer::add_child_notify(Node *p_child) {
  755. Container::add_child_notify(p_child);
  756. if (p_child->is_internal()) {
  757. return;
  758. }
  759. Control *child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE);
  760. if (!child) {
  761. return;
  762. }
  763. child->connect(SceneStringName(visibility_changed), callable_mp(this, &SplitContainer::_on_child_visibility_changed).bind(child));
  764. if (child->is_visible()) {
  765. _add_valid_child(child);
  766. }
  767. }
  768. void SplitContainer::remove_child_notify(Node *p_child) {
  769. Container::remove_child_notify(p_child);
  770. if (p_child->is_internal()) {
  771. return;
  772. }
  773. Control *child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE);
  774. if (!child) {
  775. return;
  776. }
  777. child->disconnect(SceneStringName(visibility_changed), callable_mp(this, &SplitContainer::_on_child_visibility_changed));
  778. if (child->is_visible()) {
  779. _remove_valid_child(child);
  780. }
  781. }
  782. void SplitContainer::move_child_notify(Node *p_child) {
  783. Container::move_child_notify(p_child);
  784. Control *moved_child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE);
  785. const int prev_index = valid_children.find(moved_child);
  786. if (prev_index == -1) {
  787. return;
  788. }
  789. PackedInt32Array desired_sizes;
  790. if (initialized && !split_offset_pending && valid_children.size() > 2u && split_offsets.size() == (int)default_dragger_positions.size()) {
  791. desired_sizes = _get_desired_sizes();
  792. }
  793. valid_children.remove_at(prev_index);
  794. // Get new index.
  795. int index = 0;
  796. for (int i = 0; i < get_child_count(false); i++) {
  797. Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
  798. if (!child) {
  799. continue;
  800. }
  801. if (child == moved_child) {
  802. break;
  803. }
  804. if (valid_children.has(child)) {
  805. index++;
  806. }
  807. }
  808. valid_children.insert(index, moved_child);
  809. if (desired_sizes.is_empty()) {
  810. return;
  811. }
  812. const int prev_desired_size = desired_sizes[prev_index];
  813. desired_sizes.remove_at(prev_index);
  814. desired_sizes.insert(index, prev_desired_size);
  815. _set_desired_sizes(desired_sizes, index);
  816. }
  817. void SplitContainer::_on_child_visibility_changed(Control *p_control) {
  818. if (p_control->is_visible()) {
  819. _add_valid_child(p_control);
  820. } else {
  821. _remove_valid_child(p_control);
  822. }
  823. }
  824. void SplitContainer::_add_valid_child(Control *p_control) {
  825. if (valid_children.has(p_control)) {
  826. return;
  827. }
  828. // Get index to insert.
  829. bool child_is_valid = false;
  830. int index = 0;
  831. for (int i = 0; i < get_child_count(false); i++) {
  832. Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
  833. if (!child) {
  834. continue;
  835. }
  836. if (child == p_control) {
  837. if (child->is_visible()) {
  838. child_is_valid = true;
  839. }
  840. break;
  841. }
  842. if (valid_children.has(child)) {
  843. index++;
  844. }
  845. }
  846. if (!child_is_valid) {
  847. return;
  848. }
  849. PackedInt32Array desired_sizes;
  850. if (initialized && can_use_desired_sizes && !split_offset_pending && valid_children.size() >= 2u && split_offsets.size() == (int)default_dragger_positions.size()) {
  851. desired_sizes = _get_desired_sizes();
  852. }
  853. valid_children.insert(index, p_control);
  854. if (!initialized) {
  855. // If not initialized, the theme cache isn't ready yet so return early.
  856. return;
  857. }
  858. _update_default_dragger_positions();
  859. queue_sort();
  860. if (valid_children.size() <= 2u) {
  861. // Already have first dragger.
  862. return;
  863. }
  864. // Call deferred in case already adding or removing children.
  865. callable_mp(this, &SplitContainer::_update_draggers).call_deferred();
  866. if (split_offset_pending && split_offsets.size() == (int)valid_children.size() - 1) {
  867. split_offset_pending = false;
  868. }
  869. if (desired_sizes.is_empty()) {
  870. return;
  871. }
  872. // Use the child's existing size as it's desired size.
  873. const int axis = vertical ? 1 : 0;
  874. desired_sizes.insert(index, (int)p_control->get_size()[axis]);
  875. _set_desired_sizes(desired_sizes, index);
  876. }
  877. void SplitContainer::_remove_valid_child(Control *p_control) {
  878. const int index = valid_children.find(p_control);
  879. if (index == -1) {
  880. return;
  881. }
  882. PackedInt32Array desired_sizes;
  883. if (initialized && !split_offset_pending && valid_children.size() > 2u && split_offsets.size() == (int)default_dragger_positions.size()) {
  884. desired_sizes = _get_desired_sizes();
  885. }
  886. valid_children.remove_at(index);
  887. if (!initialized) {
  888. return;
  889. }
  890. // Only use desired sizes to change the split offset after the first time a child is removed.
  891. // This allows adding children to not affect the split offsets when creating.
  892. can_use_desired_sizes = true;
  893. _update_default_dragger_positions();
  894. queue_sort();
  895. if (valid_children.size() <= 1u) {
  896. // Don't remove last dragger.
  897. return;
  898. }
  899. // Call deferred in case already adding or removing children.
  900. callable_mp(this, &SplitContainer::_update_draggers).call_deferred();
  901. if (split_offset_pending && split_offsets.size() == (int)valid_children.size() - 2) {
  902. split_offset_pending = false;
  903. }
  904. if (desired_sizes.is_empty()) {
  905. return;
  906. }
  907. desired_sizes.remove_at(index);
  908. _set_desired_sizes(desired_sizes);
  909. }
  910. void SplitContainer::set_split_offset(int p_offset, int p_index) {
  911. ERR_FAIL_INDEX(p_index, split_offsets.size());
  912. if (split_offsets[p_index] == p_offset) {
  913. return;
  914. }
  915. split_offsets.write[p_index] = p_offset;
  916. queue_sort();
  917. }
  918. int SplitContainer::get_split_offset(int p_index) const {
  919. ERR_FAIL_INDEX_V(p_index, split_offsets.size(), 0);
  920. return split_offsets[p_index];
  921. }
  922. void SplitContainer::set_split_offsets(const PackedInt32Array &p_offsets) {
  923. if (split_offsets == p_offsets) {
  924. return;
  925. }
  926. split_offsets = p_offsets;
  927. split_offset_pending = split_offsets.size() > 1 && (int)valid_children.size() - 1 != split_offsets.size();
  928. queue_sort();
  929. }
  930. PackedInt32Array SplitContainer::get_split_offsets() const {
  931. return split_offsets;
  932. }
  933. void SplitContainer::clamp_split_offset(int p_priority_index) {
  934. ERR_FAIL_INDEX(p_priority_index, split_offsets.size());
  935. if (valid_children.size() < 2u) {
  936. // Needs at least two children.
  937. return;
  938. }
  939. _update_dragger_positions(p_priority_index);
  940. queue_sort();
  941. }
  942. void SplitContainer::set_collapsed(bool p_collapsed) {
  943. if (collapsed == p_collapsed) {
  944. return;
  945. }
  946. collapsed = p_collapsed;
  947. queue_sort();
  948. }
  949. void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
  950. if (dragger_visibility == p_visibility) {
  951. return;
  952. }
  953. dragger_visibility = p_visibility;
  954. queue_sort();
  955. }
  956. SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const {
  957. return dragger_visibility;
  958. }
  959. bool SplitContainer::is_collapsed() const {
  960. return collapsed;
  961. }
  962. void SplitContainer::set_vertical(bool p_vertical) {
  963. ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
  964. if (vertical == p_vertical) {
  965. return;
  966. }
  967. vertical = p_vertical;
  968. for (SplitContainerDragger *dragger : dragging_area_controls) {
  969. dragger->update_touch_dragger();
  970. }
  971. update_minimum_size();
  972. _resort();
  973. }
  974. bool SplitContainer::is_vertical() const {
  975. return vertical;
  976. }
  977. void SplitContainer::set_dragging_enabled(bool p_enabled) {
  978. if (dragging_enabled == p_enabled) {
  979. return;
  980. }
  981. dragging_enabled = p_enabled;
  982. if (!dragging_enabled) {
  983. bool was_dragging = false;
  984. for (SplitContainerDragger *dragger : dragging_area_controls) {
  985. was_dragging |= dragger->dragging;
  986. dragger->dragging = false;
  987. }
  988. if (was_dragging) {
  989. emit_signal(SNAME("drag_ended"));
  990. }
  991. }
  992. if (get_viewport()) {
  993. get_viewport()->update_mouse_cursor_state();
  994. }
  995. _resort();
  996. }
  997. bool SplitContainer::is_dragging_enabled() const {
  998. return dragging_enabled;
  999. }
  1000. Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const {
  1001. Vector<int> flags;
  1002. flags.append(SIZE_FILL);
  1003. if (!vertical) {
  1004. flags.append(SIZE_EXPAND);
  1005. }
  1006. flags.append(SIZE_SHRINK_BEGIN);
  1007. flags.append(SIZE_SHRINK_CENTER);
  1008. flags.append(SIZE_SHRINK_END);
  1009. return flags;
  1010. }
  1011. Vector<int> SplitContainer::get_allowed_size_flags_vertical() const {
  1012. Vector<int> flags;
  1013. flags.append(SIZE_FILL);
  1014. if (vertical) {
  1015. flags.append(SIZE_EXPAND);
  1016. }
  1017. flags.append(SIZE_SHRINK_BEGIN);
  1018. flags.append(SIZE_SHRINK_CENTER);
  1019. flags.append(SIZE_SHRINK_END);
  1020. return flags;
  1021. }
  1022. void SplitContainer::set_drag_area_margin_begin(int p_margin) {
  1023. if (drag_area_margin_begin == p_margin) {
  1024. return;
  1025. }
  1026. drag_area_margin_begin = p_margin;
  1027. queue_sort();
  1028. }
  1029. int SplitContainer::get_drag_area_margin_begin() const {
  1030. return drag_area_margin_begin;
  1031. }
  1032. void SplitContainer::set_drag_area_margin_end(int p_margin) {
  1033. if (drag_area_margin_end == p_margin) {
  1034. return;
  1035. }
  1036. drag_area_margin_end = p_margin;
  1037. queue_sort();
  1038. }
  1039. int SplitContainer::get_drag_area_margin_end() const {
  1040. return drag_area_margin_end;
  1041. }
  1042. void SplitContainer::set_drag_area_offset(int p_offset) {
  1043. if (drag_area_offset == p_offset) {
  1044. return;
  1045. }
  1046. drag_area_offset = p_offset;
  1047. queue_sort();
  1048. }
  1049. int SplitContainer::get_drag_area_offset() const {
  1050. return drag_area_offset;
  1051. }
  1052. void SplitContainer::set_show_drag_area_enabled(bool p_enabled) {
  1053. show_drag_area = p_enabled;
  1054. for (SplitContainerDragger *dragger : dragging_area_controls) {
  1055. dragger->queue_redraw();
  1056. }
  1057. }
  1058. bool SplitContainer::is_show_drag_area_enabled() const {
  1059. return show_drag_area;
  1060. }
  1061. TypedArray<Control> SplitContainer::get_drag_area_controls() {
  1062. TypedArray<Control> controls;
  1063. controls.resize((int)dragging_area_controls.size());
  1064. for (int i = 0; i < (int)dragging_area_controls.size(); i++) {
  1065. controls[i] = dragging_area_controls[i];
  1066. }
  1067. return controls;
  1068. }
  1069. void SplitContainer::set_touch_dragger_enabled(bool p_enabled) {
  1070. if (touch_dragger_enabled == p_enabled) {
  1071. return;
  1072. }
  1073. touch_dragger_enabled = p_enabled;
  1074. for (SplitContainerDragger *dragger : dragging_area_controls) {
  1075. dragger->set_touch_dragger_enabled(p_enabled);
  1076. }
  1077. }
  1078. bool SplitContainer::is_touch_dragger_enabled() const {
  1079. return touch_dragger_enabled;
  1080. }
  1081. void SplitContainer::_bind_methods() {
  1082. ClassDB::bind_method(D_METHOD("set_split_offsets", "offsets"), &SplitContainer::set_split_offsets);
  1083. ClassDB::bind_method(D_METHOD("get_split_offsets"), &SplitContainer::get_split_offsets);
  1084. ClassDB::bind_method(D_METHOD("clamp_split_offset", "priority_index"), &SplitContainer::clamp_split_offset, DEFVAL(0));
  1085. ClassDB::bind_method(D_METHOD("set_collapsed", "collapsed"), &SplitContainer::set_collapsed);
  1086. ClassDB::bind_method(D_METHOD("is_collapsed"), &SplitContainer::is_collapsed);
  1087. ClassDB::bind_method(D_METHOD("set_dragger_visibility", "mode"), &SplitContainer::set_dragger_visibility);
  1088. ClassDB::bind_method(D_METHOD("get_dragger_visibility"), &SplitContainer::get_dragger_visibility);
  1089. ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical);
  1090. ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical);
  1091. ClassDB::bind_method(D_METHOD("set_dragging_enabled", "dragging_enabled"), &SplitContainer::set_dragging_enabled);
  1092. ClassDB::bind_method(D_METHOD("is_dragging_enabled"), &SplitContainer::is_dragging_enabled);
  1093. ClassDB::bind_method(D_METHOD("set_drag_area_margin_begin", "margin"), &SplitContainer::set_drag_area_margin_begin);
  1094. ClassDB::bind_method(D_METHOD("get_drag_area_margin_begin"), &SplitContainer::get_drag_area_margin_begin);
  1095. ClassDB::bind_method(D_METHOD("set_drag_area_margin_end", "margin"), &SplitContainer::set_drag_area_margin_end);
  1096. ClassDB::bind_method(D_METHOD("get_drag_area_margin_end"), &SplitContainer::get_drag_area_margin_end);
  1097. ClassDB::bind_method(D_METHOD("set_drag_area_offset", "offset"), &SplitContainer::set_drag_area_offset);
  1098. ClassDB::bind_method(D_METHOD("get_drag_area_offset"), &SplitContainer::get_drag_area_offset);
  1099. ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled);
  1100. ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled);
  1101. ClassDB::bind_method(D_METHOD("get_drag_area_controls"), &SplitContainer::get_drag_area_controls);
  1102. ClassDB::bind_method(D_METHOD("set_touch_dragger_enabled", "enabled"), &SplitContainer::set_touch_dragger_enabled);
  1103. ClassDB::bind_method(D_METHOD("is_touch_dragger_enabled"), &SplitContainer::is_touch_dragger_enabled);
  1104. ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
  1105. ADD_SIGNAL(MethodInfo("drag_started"));
  1106. ADD_SIGNAL(MethodInfo("drag_ended"));
  1107. ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "split_offsets", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offsets", "get_split_offsets");
  1108. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
  1109. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled");
  1110. ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
  1111. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
  1112. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "touch_dragger_enabled"), "set_touch_dragger_enabled", "is_touch_dragger_enabled");
  1113. ADD_GROUP("Drag Area", "drag_area_");
  1114. ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin");
  1115. ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_end", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_end", "get_drag_area_margin_end");
  1116. ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_offset", "get_drag_area_offset");
  1117. ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_area_highlight_in_editor"), "set_drag_area_highlight_in_editor", "is_drag_area_highlight_in_editor_enabled");
  1118. BIND_ENUM_CONSTANT(DRAGGER_VISIBLE);
  1119. BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
  1120. BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);
  1121. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, SplitContainer, touch_dragger_color);
  1122. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, SplitContainer, touch_dragger_pressed_color);
  1123. BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, SplitContainer, touch_dragger_hover_color);
  1124. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, separation);
  1125. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, minimum_grab_thickness);
  1126. BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, autohide);
  1127. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, touch_dragger_icon, "touch_dragger");
  1128. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, touch_dragger_icon_h, "h_touch_dragger");
  1129. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, touch_dragger_icon_v, "v_touch_dragger");
  1130. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber");
  1131. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber");
  1132. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber");
  1133. BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background");
  1134. #ifndef DISABLE_DEPRECATED
  1135. ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control);
  1136. ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::_set_split_offset_first);
  1137. ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::_get_split_offset_first);
  1138. ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_NO_EDITOR), "set_split_offset", "get_split_offset");
  1139. #endif // DISABLE_DEPRECATED
  1140. }
  1141. SplitContainer::SplitContainer(bool p_vertical) {
  1142. vertical = p_vertical;
  1143. split_offsets.push_back(0);
  1144. SplitContainerDragger *dragger = memnew(SplitContainerDragger);
  1145. dragging_area_controls.push_back(dragger);
  1146. add_child(dragger, false, Node::INTERNAL_MODE_BACK);
  1147. }