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