1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/commands/filters/color_curve_editor.h"
12
13 #include "filters/color_curve.h"
14 #include "ui/alert.h"
15 #include "ui/entry.h"
16 #include "ui/manager.h"
17 #include "ui/message.h"
18 #include "ui/paint_event.h"
19 #include "ui/scale.h"
20 #include "ui/size_hint_event.h"
21 #include "ui/system.h"
22 #include "ui/theme.h"
23 #include "ui/view.h"
24 #include "ui/widget.h"
25 #include "ui/window.h"
26
27 #include "color_curve_point.xml.h"
28
29 #include <cmath>
30 #include <cstdio>
31 #include <cstdlib>
32 #include <vector>
33
34 namespace app {
35
36 using namespace ui;
37 using namespace filters;
38
39 enum {
40 STATUS_STANDBY,
41 STATUS_MOVING_POINT,
42 STATUS_SCALING,
43 };
44
ColorCurveEditor(ColorCurve * curve,const gfx::Rect & viewBounds)45 ColorCurveEditor::ColorCurveEditor(ColorCurve* curve, const gfx::Rect& viewBounds)
46 : Widget(kGenericWidget)
47 , m_curve(curve)
48 , m_viewBounds(viewBounds)
49 , m_hotPoint(nullptr)
50 , m_editPoint(nullptr)
51 {
52 setFocusStop(true);
53 setDoubleBuffered(true);
54
55 setBorder(gfx::Border(1));
56 setChildSpacing(0);
57
58 m_status = STATUS_STANDBY;
59
60 // TODO
61 // m_curve->type = CURVE_SPLINE;
62 }
63
onProcessMessage(Message * msg)64 bool ColorCurveEditor::onProcessMessage(Message* msg)
65 {
66 switch (msg->type()) {
67
68 case kKeyDownMessage: {
69 switch (static_cast<KeyMessage*>(msg)->scancode()) {
70
71 case kKeyInsert: {
72 addPoint(screenToView(get_mouse_position()));
73 break;
74 }
75
76 case kKeyDel: {
77 gfx::Point* point = getClosestPoint(screenToView(get_mouse_position()));
78 if (point)
79 removePoint(point);
80 break;
81 }
82
83 default:
84 return false;
85 }
86 return true;
87 }
88
89 case kMouseDownMessage: {
90 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
91 gfx::Point viewPos = screenToView(mousePos);
92 m_editPoint = getClosestPoint(viewPos);
93
94 if (!m_editPoint) {
95 addPoint(viewPos);
96
97 invalidate();
98 CurveEditorChange();
99 break;
100 }
101
102 // Show manual-entry dialog
103 if (static_cast<MouseMessage*>(msg)->right()) {
104 invalidate();
105 flushRedraw();
106
107 if (editNodeManually(*m_editPoint))
108 CurveEditorChange();
109
110 m_hotPoint = nullptr;
111 m_editPoint = nullptr;
112 invalidate();
113 return true;
114 }
115 // Edit node
116 else {
117 m_status = STATUS_MOVING_POINT;
118 ui::set_mouse_cursor(kHandCursor);
119 }
120
121 captureMouse();
122
123 // continue in motion message...
124 }
125
126 case kMouseMoveMessage: {
127 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
128 gfx::Point* oldHotPoint = m_hotPoint;
129 m_hotPoint = getClosestPoint(screenToView(mousePos));
130
131 switch (m_status) {
132
133 case STATUS_STANDBY:
134 if (!m_hotPoint || m_hotPoint != oldHotPoint)
135 invalidate();
136 break;
137
138 case STATUS_MOVING_POINT:
139 if (m_editPoint) {
140 gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
141 *m_editPoint = screenToView(mousePos);
142 m_editPoint->x = MID(m_viewBounds.x, m_editPoint->x, m_viewBounds.x+m_viewBounds.w-1);
143 m_editPoint->y = MID(m_viewBounds.y, m_editPoint->y, m_viewBounds.y+m_viewBounds.h-1);
144
145 // TODO this should be optional
146 CurveEditorChange();
147
148 invalidate();
149 return true;
150 }
151 break;
152 }
153 break;
154 }
155
156 case kMouseUpMessage:
157 if (hasCapture()) {
158 releaseMouse();
159
160 switch (m_status) {
161
162 case STATUS_MOVING_POINT:
163 ui::set_mouse_cursor(kArrowCursor);
164 CurveEditorChange();
165 m_hotPoint = nullptr;
166 m_editPoint = nullptr;
167 invalidate();
168 break;
169 }
170
171 m_status = STATUS_STANDBY;
172 return true;
173 }
174 break;
175 }
176
177 return Widget::onProcessMessage(msg);
178 }
179
onSizeHint(SizeHintEvent & ev)180 void ColorCurveEditor::onSizeHint(SizeHintEvent& ev)
181 {
182 ev.setSizeHint(gfx::Size(1 + border().width(),
183 1 + border().height()));
184 }
185
onPaint(ui::PaintEvent & ev)186 void ColorCurveEditor::onPaint(ui::PaintEvent& ev)
187 {
188 ui::Graphics* g = ev.graphics();
189 gfx::Rect rc = clientBounds();
190 gfx::Rect client = clientChildrenBounds();
191 gfx::Point pt;
192 int c;
193
194 g->fillRect(gfx::rgba(0, 0, 0), rc);
195 g->drawRect(gfx::rgba(255, 255, 0), rc);
196
197 // Draw guides
198 for (c=1; c<=3; c++)
199 g->drawVLine(gfx::rgba(128, 128, 0), c*client.w/4, client.y, client.h);
200
201 for (c=1; c<=3; c++)
202 g->drawHLine(gfx::rgba(128, 128, 0), client.x, c*client.h/4, client.w);
203
204 // Get curve values
205 std::vector<int> values(m_viewBounds.w);
206 m_curve->getValues(m_viewBounds.x, m_viewBounds.x+m_viewBounds.w-1, values);
207
208 // Draw curve
209 for (c = client.x; c < client.x+client.w; ++c) {
210 pt = clientToView(gfx::Point(c, 0));
211 pt.x = MID(m_viewBounds.x, pt.x, m_viewBounds.x+m_viewBounds.w-1);
212 pt.y = values[pt.x - m_viewBounds.x];
213 pt.y = MID(m_viewBounds.y, pt.y, m_viewBounds.y+m_viewBounds.h-1);
214 pt = viewToClient(pt);
215
216 g->putPixel(gfx::rgba(255, 255, 255), c, pt.y);
217 }
218
219 // Draw nodes
220 for (const gfx::Point& point : *m_curve) {
221 pt = viewToClient(point);
222
223 gfx::Rect box(0, 0, 5*guiscale(), 5*guiscale());
224 box.offset(pt.x-box.w/2, pt.y-box.h/2);
225
226 g->drawRect(gfx::rgba(0, 0, 255), box);
227
228 if (m_editPoint == &point) {
229 box.enlarge(4*guiscale());
230 g->drawRect(gfx::rgba(255, 255, 0), box);
231 }
232 else if (m_hotPoint == &point) {
233 box.enlarge(2*guiscale());
234 g->drawRect(gfx::rgba(255, 255, 0), box);
235 }
236 }
237 }
238
getClosestPoint(const gfx::Point & viewPt)239 gfx::Point* ColorCurveEditor::getClosestPoint(const gfx::Point& viewPt)
240 {
241 gfx::Point* point_found = NULL;
242 double dist_min = 0;
243
244 for (gfx::Point& point : *m_curve) {
245 int dx = point.x - viewPt.x;
246 int dy = point.y - viewPt.y;
247 double dist = std::sqrt(static_cast<double>(dx*dx + dy*dy));
248
249 if (dist < 16*guiscale() &&
250 (!point_found || dist <= dist_min)) {
251 point_found = &point;
252 dist_min = dist;
253 }
254 }
255
256 return point_found;
257 }
258
editNodeManually(gfx::Point & viewPt)259 bool ColorCurveEditor::editNodeManually(gfx::Point& viewPt)
260 {
261 gfx::Point point_copy = viewPt;
262
263 app::gen::ColorCurvePoint window;
264 window.x()->setTextf("%d", viewPt.x);
265 window.y()->setTextf("%d", viewPt.y);
266
267 window.openWindowInForeground();
268
269 if (window.closer() == window.ok()) {
270 viewPt.x = window.x()->textInt();
271 viewPt.y = window.y()->textInt();
272 viewPt.x = MID(0, viewPt.x, 255);
273 viewPt.y = MID(0, viewPt.y, 255);
274 return true;
275 }
276 else if (window.closer() == window.deleteButton()) {
277 removePoint(&viewPt);
278 return true;
279 }
280 else {
281 viewPt = point_copy;
282 return false;
283 }
284 }
285
viewToClient(const gfx::Point & viewPt)286 gfx::Point ColorCurveEditor::viewToClient(const gfx::Point& viewPt)
287 {
288 gfx::Rect client = clientChildrenBounds();
289 return gfx::Point(
290 client.x + client.w * (viewPt.x - m_viewBounds.x) / m_viewBounds.w,
291 client.y + client.h-1 - (client.h-1) * (viewPt.y - m_viewBounds.y) / m_viewBounds.h);
292 }
293
screenToView(const gfx::Point & screenPt)294 gfx::Point ColorCurveEditor::screenToView(const gfx::Point& screenPt)
295 {
296 return clientToView(screenPt - bounds().origin());
297 }
298
clientToView(const gfx::Point & clientPt)299 gfx::Point ColorCurveEditor::clientToView(const gfx::Point& clientPt)
300 {
301 gfx::Rect client = clientChildrenBounds();
302 return gfx::Point(
303 m_viewBounds.x + m_viewBounds.w * (clientPt.x - client.x) / client.w,
304 m_viewBounds.y + m_viewBounds.h-1 - (m_viewBounds.h-1) * (clientPt.y - client.y) / client.h);
305 }
306
addPoint(const gfx::Point & viewPoint)307 void ColorCurveEditor::addPoint(const gfx::Point& viewPoint)
308 {
309 // TODO Undo history
310 m_curve->addPoint(viewPoint);
311
312 invalidate();
313 CurveEditorChange();
314 }
315
removePoint(gfx::Point * viewPoint)316 void ColorCurveEditor::removePoint(gfx::Point* viewPoint)
317 {
318 // TODO Undo history
319 m_curve->removePoint(*viewPoint);
320
321 m_hotPoint = nullptr;
322 m_editPoint = nullptr;
323
324 invalidate();
325 CurveEditorChange();
326 }
327
328 } // namespace app
329