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 "CursorTool.h"
28#include "FormEditorWidget.h"
29#include "FormWidget.h"
30#include "WidgetTreeModel.h"
31#include <AK/LogStream.h>
32#include <LibGfx/Palette.h>
33
34void CursorTool::on_mousedown(GUI::MouseEvent& event)
35{
36 dbg() << "CursorTool::on_mousedown";
37 auto& form_widget = m_editor.form_widget();
38 auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
39
40 if (event.button() == GUI::MouseButton::Left) {
41 if (result.widget && result.widget != &form_widget) {
42 if (event.modifiers() & Mod_Ctrl) {
43 m_editor.selection().toggle(*result.widget);
44 } else if (!event.modifiers()) {
45 if (!m_editor.selection().contains(*result.widget)) {
46 dbg() << "Selection didn't contain " << *result.widget << ", making it the only selected one";
47 m_editor.selection().set(*result.widget);
48 }
49
50 m_drag_origin = event.position();
51 m_positions_before_drag.clear();
52 m_editor.selection().for_each([&](auto& widget) {
53 m_positions_before_drag.set(&widget, widget.relative_position());
54 return IterationDecision::Continue;
55 });
56 }
57 } else {
58 m_editor.selection().clear();
59 m_rubber_banding = true;
60 m_rubber_band_origin = event.position();
61 m_rubber_band_position = event.position();
62 form_widget.update();
63 }
64 // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
65 form_widget.update();
66 }
67}
68
69void CursorTool::on_mouseup(GUI::MouseEvent& event)
70{
71 dbg() << "CursorTool::on_mouseup";
72 if (event.button() == GUI::MouseButton::Left) {
73 auto& form_widget = m_editor.form_widget();
74 auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
75 if (!m_dragging && !(event.modifiers() & Mod_Ctrl)) {
76 if (result.widget && result.widget != &form_widget) {
77 m_editor.selection().set(*result.widget);
78 // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
79 form_widget.update();
80 }
81 }
82 m_dragging = false;
83 m_rubber_banding = false;
84 form_widget.update();
85 }
86}
87
88void CursorTool::on_mousemove(GUI::MouseEvent& event)
89{
90 dbg() << "CursorTool::on_mousemove";
91 auto& form_widget = m_editor.form_widget();
92
93 if (m_rubber_banding) {
94 set_rubber_band_position(event.position());
95 return;
96 }
97
98 if (!m_dragging && event.buttons() & GUI::MouseButton::Left && event.position() != m_drag_origin) {
99 auto result = form_widget.hit_test(event.position(), GUI::Widget::ShouldRespectGreediness::No);
100 if (result.widget && result.widget != &form_widget) {
101 if (!m_editor.selection().contains(*result.widget)) {
102 m_editor.selection().set(*result.widget);
103 // FIXME: Do we need to update any part of the FormEditorWidget outside the FormWidget?
104 form_widget.update();
105 }
106 }
107 m_dragging = true;
108 }
109
110 if (m_dragging) {
111 auto movement_delta = event.position() - m_drag_origin;
112 m_editor.selection().for_each([&](auto& widget) {
113 auto new_rect = widget.relative_rect();
114 new_rect.set_location(m_positions_before_drag.get(&widget).value_or({}).translated(movement_delta));
115 new_rect.set_x(new_rect.x() - (new_rect.x() % m_editor.form_widget().grid_size()));
116 new_rect.set_y(new_rect.y() - (new_rect.y() % m_editor.form_widget().grid_size()));
117 widget.set_relative_rect(new_rect);
118 return IterationDecision::Continue;
119 });
120 m_editor.model().update();
121 return;
122 }
123}
124
125void CursorTool::on_keydown(GUI::KeyEvent& event)
126{
127 dbg() << "CursorTool::on_keydown";
128
129 auto move_selected_widgets_by = [this](int x, int y) {
130 m_editor.selection().for_each([&](auto& widget) {
131 widget.move_by(x, y);
132 return IterationDecision::Continue;
133 });
134 };
135
136 if (event.modifiers() == 0) {
137 switch (event.key()) {
138 case Key_Down:
139 move_selected_widgets_by(0, m_editor.form_widget().grid_size());
140 break;
141 case Key_Up:
142 move_selected_widgets_by(0, -m_editor.form_widget().grid_size());
143 break;
144 case Key_Left:
145 move_selected_widgets_by(-m_editor.form_widget().grid_size(), 0);
146 break;
147 case Key_Right:
148 move_selected_widgets_by(m_editor.form_widget().grid_size(), 0);
149 break;
150 }
151 }
152}
153
154void CursorTool::set_rubber_band_position(const Gfx::Point& position)
155{
156 if (m_rubber_band_position == position)
157 return;
158 m_rubber_band_position = position;
159
160 auto rubber_band_rect = this->rubber_band_rect();
161
162 m_editor.selection().clear();
163 m_editor.form_widget().for_each_child_widget([&](auto& child) {
164 if (child.relative_rect().intersects(rubber_band_rect))
165 m_editor.selection().add(child);
166 return IterationDecision::Continue;
167 });
168
169 m_editor.form_widget().update();
170}
171
172Gfx::Rect CursorTool::rubber_band_rect() const
173{
174 if (!m_rubber_banding)
175 return {};
176 return Gfx::Rect::from_two_points(m_rubber_band_origin, m_rubber_band_position);
177}
178
179void CursorTool::on_second_paint(GUI::Painter& painter, GUI::PaintEvent&)
180{
181 if (!m_rubber_banding)
182 return;
183 auto rect = rubber_band_rect();
184 painter.fill_rect(rect, m_editor.palette().rubber_band_fill());
185 painter.draw_rect(rect, m_editor.palette().rubber_band_border());
186}