1 // Aseprite
2 // Copyright (C) 2001-2017 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/ui/editor/state_with_wheel_behavior.h"
12
13 #include "app/app.h"
14 #include "app/commands/commands.h"
15 #include "app/commands/params.h"
16 #include "app/modules/palettes.h"
17 #include "app/pref/preferences.h"
18 #include "app/site.h"
19 #include "app/tools/active_tool.h"
20 #include "app/tools/tool_box.h"
21 #include "app/ui/color_bar.h"
22 #include "app/ui/editor/editor.h"
23 #include "app/ui/keyboard_shortcuts.h"
24 #include "app/ui/toolbar.h"
25 #include "app/ui_context.h"
26 #include "base/string.h"
27 #include "doc/layer.h"
28 #include "doc/palette.h"
29 #include "ui/message.h"
30 #include "ui/system.h"
31 #include "ui/theme.h"
32
33 namespace app {
34
35 using namespace ui;
36
onMouseWheel(Editor * editor,MouseMessage * msg)37 bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
38 {
39 gfx::Point delta = msg->wheelDelta();
40 double dz = delta.x + delta.y;
41 WheelAction wheelAction = WheelAction::None;
42 bool scrollBigSteps = false;
43
44 if (KeyboardShortcuts::instance()->hasMouseWheelCustomization()) {
45 if (!Preferences::instance().editor.zoomWithSlide() && msg->preciseWheel())
46 wheelAction = WheelAction::VScroll;
47 else
48 wheelAction = KeyboardShortcuts::instance()
49 ->getWheelActionFromMouseMessage(KeyContext::MouseWheel, msg);
50 }
51 // Default behavior
52 // TODO replace this code using KeyboardShortcuts::getDefaultMouseWheelTable()
53 else {
54 // Alt+mouse wheel changes the fg/bg colors
55 if (msg->altPressed()) {
56 if (msg->shiftPressed())
57 wheelAction = WheelAction::BgColor;
58 else
59 wheelAction = WheelAction::FgColor;
60 }
61 // Normal behavior: mouse wheel zooms If the message is from a
62 // precise wheel i.e. a trackpad/touch-like device, we scroll by
63 // default.
64 else if (Preferences::instance().editor.zoomWithWheel() && !msg->preciseWheel()) {
65 if (msg->ctrlPressed() && msg->shiftPressed())
66 wheelAction = WheelAction::Frame;
67 else if (msg->ctrlPressed())
68 wheelAction = WheelAction::BrushSize;
69 else if (delta.x != 0 || msg->shiftPressed())
70 wheelAction = WheelAction::HScroll;
71 else
72 wheelAction = WheelAction::Zoom;
73 }
74 // Zoom sliding two fingers
75 else if (Preferences::instance().editor.zoomWithSlide() && msg->preciseWheel()) {
76 if (msg->ctrlPressed() && msg->shiftPressed())
77 wheelAction = WheelAction::Frame;
78 else if (msg->ctrlPressed())
79 wheelAction = WheelAction::BrushSize;
80 else if (std::abs(delta.x) > std::abs(delta.y)) {
81 delta.y = 0;
82 dz = delta.x;
83 wheelAction = WheelAction::HScroll;
84 }
85 else if (msg->shiftPressed()) {
86 delta.x = 0;
87 dz = delta.y;
88 wheelAction = WheelAction::VScroll;
89 }
90 else {
91 delta.x = 0;
92 dz = delta.y;
93 wheelAction = WheelAction::Zoom;
94 }
95 }
96 // For laptops, it's convenient to that Ctrl+wheel zoom (because
97 // it's the "pinch" gesture).
98 else {
99 if (msg->ctrlPressed())
100 wheelAction = WheelAction::Zoom;
101 else if (delta.x != 0 || msg->shiftPressed())
102 wheelAction = WheelAction::HScroll;
103 else
104 wheelAction = WheelAction::VScroll;
105 }
106 }
107
108 switch (wheelAction) {
109
110 case WheelAction::None:
111 // Do nothing
112 break;
113
114 case WheelAction::FgColor: {
115 int lastIndex = get_current_palette()->size()-1;
116 int newIndex = ColorBar::instance()->getFgColor().getIndex() + int(dz);
117 newIndex = MID(0, newIndex, lastIndex);
118 ColorBar::instance()->setFgColor(app::Color::fromIndex(newIndex));
119 break;
120 }
121
122 case WheelAction::BgColor: {
123 int lastIndex = get_current_palette()->size()-1;
124 int newIndex = ColorBar::instance()->getBgColor().getIndex() + int(dz);
125 newIndex = MID(0, newIndex, lastIndex);
126 ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex));
127 break;
128 }
129
130 case WheelAction::Frame: {
131 Command* command = nullptr;
132
133 if (dz < 0.0)
134 command = Commands::instance()->byId(CommandId::GotoNextFrame());
135 else if (dz > 0.0)
136 command = Commands::instance()->byId(CommandId::GotoPreviousFrame());
137
138 if (command)
139 UIContext::instance()->executeCommand(command);
140 break;
141 }
142
143 case WheelAction::Zoom: {
144 render::Zoom zoom = editor->zoom();
145
146 if (msg->preciseWheel()) {
147 dz /= 1.5;
148 if (dz < -1.0) dz = -1.0;
149 else if (dz > 1.0) dz = 1.0;
150 }
151
152 zoom = render::Zoom::fromLinearScale(zoom.linearScale() - int(dz));
153
154 setZoom(editor, zoom, msg->position());
155 break;
156 }
157
158 case WheelAction::HScroll:
159 case WheelAction::VScroll: {
160 View* view = View::getView(editor);
161 gfx::Point scroll = view->viewScroll();
162
163 if (!msg->preciseWheel()) {
164 gfx::Rect vp = view->viewportBounds();
165
166 if (wheelAction == WheelAction::HScroll) {
167 delta.x = int(dz * vp.w);
168 }
169 else {
170 delta.y = int(dz * vp.h);
171 }
172
173 if (scrollBigSteps) {
174 delta /= 2;
175 }
176 else {
177 delta /= 10;
178 }
179 }
180
181 editor->setEditorScroll(scroll+delta);
182 break;
183 }
184
185 case WheelAction::BrushSize: {
186 tools::Tool* tool = getActiveTool();
187 ToolPreferences::Brush& brush =
188 Preferences::instance().tool(tool).brush;
189
190 brush.size(MID(doc::Brush::kMinBrushSize,
191 brush.size()+dz,
192 doc::Brush::kMaxBrushSize));
193 break;
194 }
195
196 case WheelAction::BrushAngle: {
197 tools::Tool* tool = getActiveTool();
198 ToolPreferences::Brush& brush =
199 Preferences::instance().tool(tool).brush;
200
201 int angle = brush.angle()+dz;
202 while (angle < 0)
203 angle += 180;
204 angle %= 181;
205
206 brush.angle(MID(0, angle, 180));
207 break;
208 }
209
210 case WheelAction::ToolSameGroup: {
211 tools::Tool* tool = getActiveTool();
212
213 auto toolBox = App::instance()->toolBox();
214 std::vector<tools::Tool*> tools;
215 for (tools::Tool* t : *toolBox) {
216 if (tool->getGroup() == t->getGroup())
217 tools.push_back(t);
218 }
219
220 auto begin = tools.begin();
221 auto end = tools.end();
222 auto it = std::find(begin, end, tool);
223 if (it != end) {
224 if (dz < 0) {
225 if (it == begin)
226 it = end;
227 --it;
228 }
229 else {
230 ++it;
231 if (it == end)
232 it = begin;
233 }
234 if (tool != *it)
235 ToolBar::instance()->selectTool(*it);
236 }
237 break;
238 }
239
240 case WheelAction::ToolOtherGroup: {
241 tools::Tool* tool = getActiveTool();
242 auto toolBox = App::instance()->toolBox();
243 auto begin = toolBox->begin_group();
244 auto end = toolBox->end_group();
245 auto it = std::find(begin, end, tool->getGroup());
246 if (it != end) {
247 if (dz < 0) {
248 if (it == begin)
249 it = end;
250 --it;
251 }
252 else {
253 ++it;
254 if (it == end)
255 it = begin;
256 }
257 ToolBar::instance()->selectToolGroup(*it);
258 }
259 break;
260 }
261
262 case WheelAction::Layer: {
263 Command* command = nullptr;
264 if (dz < 0.0)
265 command = Commands::instance()->byId(CommandId::GotoNextLayer());
266 else if (dz > 0.0)
267 command = Commands::instance()->byId(CommandId::GotoPreviousLayer());
268 if (command)
269 UIContext::instance()->executeCommand(command);
270 break;
271 }
272
273 case WheelAction::InkOpacity: {
274 tools::Tool* tool = getActiveTool();
275 auto& toolPref = Preferences::instance().tool(tool);
276 int opacity = toolPref.opacity();
277 opacity = MID(0, opacity+dz*255/10, 255);
278 toolPref.opacity(opacity);
279 break;
280 }
281
282 case WheelAction::LayerOpacity: {
283 Site site = UIContext::instance()->activeSite();
284 if (site.layer() &&
285 site.layer()->isImage() &&
286 site.layer()->isEditable()) {
287 Command* command = Commands::instance()->byId(CommandId::LayerOpacity());
288 if (command) {
289 int opacity = static_cast<doc::LayerImage*>(site.layer())->opacity();
290 opacity = MID(0, opacity+dz*255/10, 255);
291
292 Params params;
293 params.set("opacity",
294 base::convert_to<std::string>(opacity).c_str());
295 UIContext::instance()->executeCommand(command, params);
296 }
297 }
298 break;
299 }
300
301 case WheelAction::CelOpacity: {
302 Site site = UIContext::instance()->activeSite();
303 if (site.layer() &&
304 site.layer()->isImage() &&
305 site.layer()->isEditable() &&
306 site.cel()) {
307 Command* command = Commands::instance()->byId(CommandId::CelOpacity());
308 if (command) {
309 int opacity = site.cel()->opacity();
310 opacity = MID(0, opacity+dz*255/10, 255);
311 Params params;
312 params.set("opacity",
313 base::convert_to<std::string>(opacity).c_str());
314 UIContext::instance()->executeCommand(command, params);
315 }
316 }
317 break;
318 }
319
320 case WheelAction::Alpha: {
321 disableQuickTool();
322
323 ColorBar* colorBar = ColorBar::instance();
324 Color c = colorBar->getFgColor();
325 int a = c.getAlpha();
326 a = MID(0, a+dz*255/10, 255);
327 c.setAlpha(a);
328 colorBar->setFgColor(c);
329 break;
330 }
331
332 case WheelAction::HslHue:
333 case WheelAction::HslSaturation:
334 case WheelAction::HslLightness: {
335 disableQuickTool();
336
337 ColorBar* colorBar = ColorBar::instance();
338 Color c = colorBar->getFgColor();
339 double
340 h = c.getHslHue(),
341 s = c.getHslSaturation(),
342 l = c.getHslLightness();
343 switch (wheelAction) {
344 case WheelAction::HslHue: h = h+dz*10.0; break;
345 case WheelAction::HslSaturation: s = s+dz/10.0; break;
346 case WheelAction::HslLightness: l = l+dz/10.0; break;
347 }
348 colorBar->setFgColor(Color::fromHsl(MID(0.0, h, 360.0),
349 MID(0.0, s, 1.0),
350 MID(0.0, l, 1.0)));
351 break;
352 }
353
354 case WheelAction::HsvHue:
355 case WheelAction::HsvSaturation:
356 case WheelAction::HsvValue: {
357 disableQuickTool();
358
359 ColorBar* colorBar = ColorBar::instance();
360 Color c = colorBar->getFgColor();
361 double
362 h = c.getHsvHue(),
363 s = c.getHsvSaturation(),
364 v = c.getHsvValue();
365 switch (wheelAction) {
366 case WheelAction::HsvHue: h = h+dz*10.0; break;
367 case WheelAction::HsvSaturation: s = s+dz/10.0; break;
368 case WheelAction::HsvValue: v = v+dz/10.0; break;
369 }
370 colorBar->setFgColor(Color::fromHsv(MID(0.0, h, 360.0),
371 MID(0.0, s, 1.0),
372 MID(0.0, v, 1.0)));
373 break;
374 }
375
376 }
377
378 return true;
379 }
380
onTouchMagnify(Editor * editor,ui::TouchMessage * msg)381 bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* msg)
382 {
383 render::Zoom zoom = editor->zoom();
384 zoom = render::Zoom::fromScale(
385 zoom.internalScale() + zoom.internalScale() * msg->magnification());
386
387 setZoom(editor, zoom, msg->position());
388 return true;
389 }
390
setZoom(Editor * editor,const render::Zoom & zoom,const gfx::Point & mousePos)391 void StateWithWheelBehavior::setZoom(Editor* editor,
392 const render::Zoom& zoom,
393 const gfx::Point& mousePos)
394 {
395 bool center = Preferences::instance().editor.zoomFromCenterWithWheel();
396
397 editor->setZoomAndCenterInMouse(
398 zoom, mousePos,
399 (center ? Editor::ZoomBehavior::CENTER:
400 Editor::ZoomBehavior::MOUSE));
401 }
402
getActiveTool()403 tools::Tool* StateWithWheelBehavior::getActiveTool()
404 {
405 disableQuickTool();
406 return App::instance()->activeToolManager()->activeTool();
407 }
408
disableQuickTool()409 void StateWithWheelBehavior::disableQuickTool()
410 {
411 auto atm = App::instance()->activeToolManager();
412 if (atm->quickTool()) {
413 // As Ctrl key could active the Move tool, and Ctrl+mouse wheel can
414 // change the size of the tool, we want to remove the quick tool so
415 // the effect is for the selected tool.
416 atm->newQuickToolSelectedFromEditor(nullptr);
417 }
418 }
419
420 } // namespace app
421