Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <LibGUI/BoxLayout.h>
28#include <LibGUI/Painter.h>
29#include <LibGUI/Splitter.h>
30#include <LibGUI/Window.h>
31#include <LibGfx/Palette.h>
32
33namespace GUI {
34
35Splitter::Splitter(Orientation orientation)
36 : m_orientation(orientation)
37{
38 set_background_role(ColorRole::Button);
39 set_layout(make<BoxLayout>(orientation));
40 set_fill_with_background_color(true);
41 layout()->set_spacing(4);
42}
43
44Splitter::~Splitter()
45{
46}
47
48void Splitter::paint_event(PaintEvent& event)
49{
50 Painter painter(*this);
51 painter.add_clip_rect(event.rect());
52 painter.fill_rect(m_grabbable_rect, palette().hover_highlight());
53}
54
55void Splitter::resize_event(ResizeEvent& event)
56{
57 Frame::resize_event(event);
58 m_grabbable_rect = {};
59}
60
61void Splitter::enter_event(Core::Event&)
62{
63 window()->set_override_cursor(m_orientation == Orientation::Horizontal ? StandardCursor::ResizeHorizontal : StandardCursor::ResizeVertical);
64}
65
66void Splitter::leave_event(Core::Event&)
67{
68 if (!m_resizing)
69 window()->set_override_cursor(StandardCursor::None);
70 if (!m_grabbable_rect.is_empty()) {
71 m_grabbable_rect = {};
72 update();
73 }
74}
75
76bool Splitter::get_resize_candidates_at(const Gfx::Point& position, Widget*& first, Widget*& second)
77{
78 int x_or_y = position.primary_offset_for_orientation(m_orientation);
79 int fudge = layout()->spacing();
80 for_each_child_widget([&](auto& child) {
81 int child_start = child.relative_rect().first_edge_for_orientation(m_orientation);
82 int child_end = child.relative_rect().last_edge_for_orientation(m_orientation);
83 if (x_or_y > child_end && (x_or_y - fudge) <= child_end)
84 first = &child;
85 if (x_or_y < child_start && (x_or_y + fudge) >= child_start)
86 second = &child;
87 return IterationDecision::Continue;
88 });
89 return first && second;
90}
91
92void Splitter::mousedown_event(MouseEvent& event)
93{
94 if (event.button() != MouseButton::Left)
95 return;
96 m_resizing = true;
97
98 Widget* first { nullptr };
99 Widget* second { nullptr };
100 if (!get_resize_candidates_at(event.position(), first, second))
101 return;
102
103 m_first_resizee = first->make_weak_ptr();
104 m_second_resizee = second->make_weak_ptr();
105 m_first_resizee_start_size = first->size();
106 m_second_resizee_start_size = second->size();
107 m_resize_origin = event.position();
108}
109
110void Splitter::recompute_grabbable_rect(const Widget& first, const Widget& second)
111{
112 auto first_edge = first.relative_rect().primary_offset_for_orientation(m_orientation) + first.relative_rect().primary_size_for_orientation(m_orientation);
113 auto second_edge = second.relative_rect().primary_offset_for_orientation(m_orientation);
114 Gfx::Rect rect;
115 rect.set_primary_offset_for_orientation(m_orientation, first_edge);
116 rect.set_primary_size_for_orientation(m_orientation, second_edge - first_edge);
117 rect.set_secondary_offset_for_orientation(m_orientation, first.relative_rect().secondary_offset_for_orientation(m_orientation));
118 rect.set_secondary_size_for_orientation(m_orientation, first.relative_rect().secondary_size_for_orientation(m_orientation));
119 if (m_grabbable_rect != rect) {
120 m_grabbable_rect = rect;
121 update();
122 }
123}
124
125void Splitter::mousemove_event(MouseEvent& event)
126{
127 if (!m_resizing) {
128 Widget* first { nullptr };
129 Widget* second { nullptr };
130 if (!get_resize_candidates_at(event.position(), first, second))
131 return;
132 recompute_grabbable_rect(*first, *second);
133 return;
134 }
135 auto delta = event.position() - m_resize_origin;
136 if (!m_first_resizee || !m_second_resizee) {
137 // One or both of the resizees were deleted during an ongoing resize, screw this.
138 m_resizing = false;
139 return;
140 }
141 int minimum_size = 0;
142 auto new_first_resizee_size = m_first_resizee_start_size;
143 auto new_second_resizee_size = m_second_resizee_start_size;
144
145 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));
146 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));
147
148 if (new_first_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
149 int correction = minimum_size - new_first_resizee_size.primary_size_for_orientation(m_orientation);
150 new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) + correction);
151 new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) - correction);
152 }
153 if (new_second_resizee_size.primary_size_for_orientation(m_orientation) < minimum_size) {
154 int correction = minimum_size - new_second_resizee_size.primary_size_for_orientation(m_orientation);
155 new_second_resizee_size.set_primary_size_for_orientation(m_orientation, new_second_resizee_size.primary_size_for_orientation(m_orientation) + correction);
156 new_first_resizee_size.set_primary_size_for_orientation(m_orientation, new_first_resizee_size.primary_size_for_orientation(m_orientation) - correction);
157 }
158 m_first_resizee->set_preferred_size(new_first_resizee_size);
159 m_second_resizee->set_preferred_size(new_second_resizee_size);
160
161 m_first_resizee->set_size_policy(m_orientation, SizePolicy::Fixed);
162 m_second_resizee->set_size_policy(m_orientation, SizePolicy::Fill);
163
164 invalidate_layout();
165}
166
167void Splitter::did_layout()
168{
169 if (m_first_resizee && m_second_resizee)
170 recompute_grabbable_rect(*m_first_resizee, *m_second_resizee);
171}
172
173void Splitter::mouseup_event(MouseEvent& event)
174{
175 if (event.button() != MouseButton::Left)
176 return;
177 m_resizing = false;
178 m_first_resizee = nullptr;
179 m_second_resizee = nullptr;
180 if (!rect().contains(event.position()))
181 window()->set_override_cursor(StandardCursor::None);
182}
183
184}