1// Aseprite
2// Copyright (C) 2022 Igara Studio S.A.
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/dragging_value_state.h"
12
13#include "app/tools/tool.h"
14#include "app/ui/editor/editor.h"
15#include "app/ui/toolbar.h"
16#include "app/ui_context.h"
17#include "ui/display.h"
18#include "ui/message.h"
19#include "ui/scale.h"
20#include "ui/system.h"
21
22#include <cmath>
23
24namespace app {
25
26using namespace ui;
27
28DraggingValueState::DraggingValueState(Editor* editor, const Keys& keys)
29 : m_editor(editor)
30 , m_keys(keys)
31 , m_initialPos(editor->display()->nativeWindow()->pointFromScreen(ui::get_mouse_position()))
32 , m_initialPosSameGroup(m_initialPos)
33 , m_initialFgColor(StateWithWheelBehavior::initialFgColor())
34 , m_initialBgColor(StateWithWheelBehavior::initialBgColor())
35 , m_initialFgTileIndex(StateWithWheelBehavior::initialFgTileIndex())
36 , m_initialBgTileIndex(StateWithWheelBehavior::initialBgTileIndex())
37 , m_initialBrushSize(StateWithWheelBehavior::initialBrushSize())
38 , m_initialBrushAngle(StateWithWheelBehavior::initialBrushAngle())
39 , m_initialScroll(StateWithWheelBehavior::initialScroll(editor))
40 , m_initialZoom(StateWithWheelBehavior::initialZoom(editor))
41 , m_initialFrame(StateWithWheelBehavior::initialFrame(editor))
42 , m_initialInkType(StateWithWheelBehavior::initialInkType(editor))
43 , m_initialInkOpacity(StateWithWheelBehavior::initialInkOpacity(editor))
44 , m_initialCelOpacity(StateWithWheelBehavior::initialCelOpacity(editor))
45 , m_initialLayerOpacity(StateWithWheelBehavior::initialLayerOpacity(editor))
46 , m_initialTool(StateWithWheelBehavior::initialTool())
47{
48 if (!editor->hasCapture())
49 editor->captureMouse();
50
51 // As StateWithWheelBehavior::initialLayer() fills browsableLayers()
52 // we will only fill it if it's necessary (there is a key that
53 // triggers WheelAction::Layer)
54 for (const KeyPtr& key : m_keys) {
55 if (key->wheelAction() == WheelAction::Layer) {
56 m_initialLayer = StateWithWheelBehavior::initialLayer(editor);
57 break;
58 }
59 }
60 m_beforeCmdConn =
61 UIContext::instance()->BeforeCommandExecution.connect(
62 &DraggingValueState::onBeforeCommandExecution, this);
63}
64
65void DraggingValueState::onBeforePopState(Editor* editor)
66{
67 m_beforeCmdConn.disconnect();
68 StateWithWheelBehavior::onBeforePopState(editor);
69}
70
71bool DraggingValueState::onMouseDown(Editor* editor, MouseMessage* msg)
72{
73 return true;
74}
75
76bool DraggingValueState::onMouseUp(Editor* editor, MouseMessage* msg)
77{
78 editor->backToPreviousState();
79 editor->releaseMouse();
80 return true;
81}
82
83bool DraggingValueState::onMouseMove(Editor* editor, MouseMessage* msg)
84{
85 m_fgColor = m_initialFgColor;
86
87 for (const KeyPtr& key : m_keys) {
88 gfx::Point initialPos;
89 if (key->wheelAction() == WheelAction::ToolSameGroup)
90 initialPos = m_initialPosSameGroup;
91 else
92 initialPos = m_initialPos;
93
94 const gfx::Point delta = (msg->position() - initialPos);
95 const DragVector deltaV(delta.x, delta.y);
96 const DragVector invDragVector(key->dragVector().x,
97 -key->dragVector().y);
98 const double threshold = invDragVector.magnitude();
99
100 DragVector v = deltaV.projectOn(invDragVector);
101 double dz = v.magnitude();
102 {
103 if (threshold > 0)
104 dz /= threshold;
105 auto dot = invDragVector * v;
106 dz *= SGN(dot);
107
108 PreciseWheel preciseWheel = PreciseWheel::On;
109 if (key->wheelAction() == WheelAction::Zoom ||
110 key->wheelAction() == WheelAction::Frame ||
111 key->wheelAction() == WheelAction::Layer) {
112 preciseWheel = PreciseWheel::Off;
113 dz = -dz; // Invert value for zoom only so the vector is
114 // pointing to the direction to increase zoom
115
116 // TODO we should change the direction of the wheel
117 // information from the laf layer
118 }
119 else if (key->wheelAction() == WheelAction::InkType) {
120 preciseWheel = PreciseWheel::Off;
121 }
122
123 processWheelAction(editor,
124 key->wheelAction(),
125 msg->position(),
126 delta,
127 dz,
128 ScrollBigSteps::Off,
129 preciseWheel,
130 FromMouseWheel::Off);
131 }
132 }
133
134 if (m_fgColor != m_initialFgColor)
135 StateWithWheelBehavior::changeFgColor(m_fgColor);
136
137 return true;
138}
139
140bool DraggingValueState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
141{
142 return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
143}
144
145bool DraggingValueState::onKeyDown(Editor* editor, KeyMessage* msg)
146{
147 return false;
148}
149
150bool DraggingValueState::onKeyUp(Editor* editor, KeyMessage* msg)
151{
152 if (editor->hasCapture())
153 editor->releaseMouse();
154 editor->backToPreviousState();
155 return true;
156}
157
158bool DraggingValueState::onUpdateStatusBar(Editor* editor)
159{
160 return false;
161}
162
163void DraggingValueState::onBeforeCommandExecution(CommandExecutionEvent& ev)
164{
165 m_editor->backToPreviousState();
166}
167
168void DraggingValueState::changeFgColor(Color c)
169{
170 m_fgColor = c;
171}
172
173tools::Tool* DraggingValueState::getInitialToolInActiveGroup()
174{
175 return StateWithWheelBehavior::getInitialToolInActiveGroup();
176}
177
178void DraggingValueState::onToolChange(tools::Tool* tool)
179{
180 ToolBar::instance()->selectTool(tool);
181}
182
183void DraggingValueState::onToolGroupChange(Editor* editor,
184 tools::ToolGroup* group)
185{
186 if (getActiveTool()->getGroup() != group) {
187 StateWithWheelBehavior::onToolGroupChange(editor, group);
188
189 // Update reference initial position to change tools in the same
190 // group. Useful when the same key modifiers are associated to
191 // WheelAction::ToolSameGroup and WheelAction::ToolOtherGroup at
192 // the same time. This special position is needed to avoid jumping
193 // "randomly" to other tools when we change to another group (as
194 // the delta from the m_initialPos is accumulated).
195 m_initialPosSameGroup = editor->display()->nativeWindow()
196 ->pointFromScreen(ui::get_mouse_position());
197 }
198}
199
200} // namespace app
201