Splitter.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibGUI/BoxLayout.h>
  7. #include <LibGUI/Painter.h>
  8. #include <LibGUI/Splitter.h>
  9. #include <LibGUI/Window.h>
  10. #include <LibGfx/Palette.h>
  11. REGISTER_WIDGET(GUI, HorizontalSplitter)
  12. REGISTER_WIDGET(GUI, VerticalSplitter)
  13. namespace GUI {
  14. Splitter::Splitter(Orientation orientation)
  15. : m_orientation(orientation)
  16. {
  17. set_background_role(ColorRole::Button);
  18. set_layout<BoxLayout>(orientation);
  19. set_fill_with_background_color(true);
  20. layout()->set_spacing(3);
  21. }
  22. Splitter::~Splitter()
  23. {
  24. }
  25. void Splitter::paint_event(PaintEvent& event)
  26. {
  27. Painter painter(*this);
  28. painter.add_clip_rect(event.rect());
  29. painter.fill_rect(m_grabbable_rect, palette().hover_highlight());
  30. }
  31. void Splitter::resize_event(ResizeEvent& event)
  32. {
  33. Widget::resize_event(event);
  34. m_grabbable_rect = {};
  35. }
  36. void Splitter::override_cursor(bool do_override)
  37. {
  38. if (do_override) {
  39. if (!m_overriding_cursor) {
  40. set_override_cursor(m_orientation == Orientation::Horizontal ? Gfx::StandardCursor::ResizeColumn : Gfx::StandardCursor::ResizeRow);
  41. m_overriding_cursor = true;
  42. }
  43. } else {
  44. if (m_overriding_cursor) {
  45. set_override_cursor(Gfx::StandardCursor::None);
  46. m_overriding_cursor = false;
  47. }
  48. }
  49. }
  50. void Splitter::leave_event(Core::Event&)
  51. {
  52. if (!m_resizing)
  53. override_cursor(false);
  54. if (!m_grabbable_rect.is_empty()) {
  55. m_grabbable_rect = {};
  56. update();
  57. }
  58. }
  59. bool Splitter::get_resize_candidates_at(const Gfx::IntPoint& position, Widget*& first, Widget*& second)
  60. {
  61. int x_or_y = position.primary_offset_for_orientation(m_orientation);
  62. Widget* previous_widget = nullptr;
  63. bool found_candidates = false;
  64. for_each_child_widget([&](auto& child_widget) {
  65. if (!child_widget.is_visible()) {
  66. // We need to skip over widgets that are not visible as they
  67. // are not necessarily in the correct location (anymore)
  68. return IterationDecision::Continue;
  69. }
  70. if (!previous_widget) {
  71. previous_widget = &child_widget;
  72. return IterationDecision::Continue;
  73. }
  74. if (x_or_y > previous_widget->content_rect().last_edge_for_orientation(m_orientation)
  75. && x_or_y <= child_widget.content_rect().first_edge_for_orientation(m_orientation)) {
  76. first = previous_widget;
  77. second = &child_widget;
  78. found_candidates = true;
  79. return IterationDecision::Break;
  80. }
  81. previous_widget = &child_widget;
  82. return IterationDecision::Continue;
  83. });
  84. return found_candidates;
  85. }
  86. void Splitter::mousedown_event(MouseEvent& event)
  87. {
  88. if (event.button() != MouseButton::Left)
  89. return;
  90. m_resizing = true;
  91. Widget* first { nullptr };
  92. Widget* second { nullptr };
  93. if (!get_resize_candidates_at(event.position(), first, second))
  94. return;
  95. m_first_resizee = *first;
  96. m_second_resizee = *second;
  97. m_first_resizee_start_size = first->size();
  98. m_second_resizee_start_size = second->size();
  99. m_resize_origin = event.position();
  100. }
  101. void Splitter::recompute_grabbable_rect(const Widget& first, const Widget& second)
  102. {
  103. auto first_edge = first.content_rect().primary_offset_for_orientation(m_orientation) + first.content_rect().primary_size_for_orientation(m_orientation);
  104. auto second_edge = second.content_rect().primary_offset_for_orientation(m_orientation);
  105. Gfx::IntRect rect;
  106. rect.set_primary_offset_for_orientation(m_orientation, first_edge);
  107. rect.set_primary_size_for_orientation(m_orientation, second_edge - first_edge);
  108. rect.set_secondary_offset_for_orientation(m_orientation, first.content_rect().secondary_offset_for_orientation(m_orientation));
  109. rect.set_secondary_size_for_orientation(m_orientation, first.content_rect().secondary_size_for_orientation(m_orientation));
  110. if (m_grabbable_rect != rect) {
  111. m_grabbable_rect = rect;
  112. update();
  113. }
  114. }
  115. void Splitter::mousemove_event(MouseEvent& event)
  116. {
  117. if (!m_resizing) {
  118. Widget* first { nullptr };
  119. Widget* second { nullptr };
  120. if (!get_resize_candidates_at(event.position(), first, second)) {
  121. override_cursor(false);
  122. return;
  123. }
  124. recompute_grabbable_rect(*first, *second);
  125. override_cursor(m_grabbable_rect.contains(event.position()));
  126. return;
  127. }
  128. auto delta = event.position() - m_resize_origin;
  129. if (!m_first_resizee || !m_second_resizee) {
  130. // One or both of the resizees were deleted during an ongoing resize, screw this.
  131. m_resizing = false;
  132. return;
  133. }
  134. int minimum_size = 0;
  135. auto new_first_resizee_size = m_first_resizee_start_size;
  136. auto new_second_resizee_size = m_second_resizee_start_size;
  137. new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) + delta.primary_offset_for_orientation(m_orientation));
  138. new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) - delta.primary_offset_for_orientation(m_orientation));
  139. if (new_first_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
  140. int correction = minimum_size - new_first_resizee_size.primary_size_for_orientation(m_orientation);
  141. new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) + correction);
  142. new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) - correction);
  143. }
  144. if (new_second_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
  145. int correction = minimum_size - new_second_resizee_size.primary_size_for_orientation(m_orientation);
  146. new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) + correction);
  147. new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) - correction);
  148. }
  149. if (m_orientation == Orientation::Horizontal) {
  150. m_first_resizee->set_fixed_width(new_first_resizee_size.width());
  151. m_second_resizee->set_fixed_width(-1);
  152. } else {
  153. m_first_resizee->set_fixed_height(new_first_resizee_size.height());
  154. m_second_resizee->set_fixed_height(-1);
  155. }
  156. invalidate_layout();
  157. }
  158. void Splitter::did_layout()
  159. {
  160. if (m_first_resizee && m_second_resizee)
  161. recompute_grabbable_rect(*m_first_resizee, *m_second_resizee);
  162. }
  163. void Splitter::mouseup_event(MouseEvent& event)
  164. {
  165. if (event.button() != MouseButton::Left)
  166. return;
  167. m_resizing = false;
  168. m_first_resizee = nullptr;
  169. m_second_resizee = nullptr;
  170. if (!rect().contains(event.position()))
  171. set_override_cursor(Gfx::StandardCursor::None);
  172. }
  173. }