Add option to simulate mouse wheel using key shortcut+drag mouse (fix #3195)

Now we can change several values (zoom, brush size, etc.) pressing a
keyboard shortcuts and dragging the mouse in a specific vector
direction (DragVector). It allows the modification of one, two, or
even more parameters at the same time (e.g. X axis to change the brush
size, Y axis the alpha value of the ink).
This commit is contained in:
David Capello 2022-03-08 20:40:11 -03:00
parent 7e1d3832f0
commit 4ce2d1a340
13 changed files with 931 additions and 157 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2018-2021 Igara Studio S.A. --> <!-- Copyright (C) 2018-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello --> <!-- Copyright (C) 2001-2018 David Capello -->
<gui> <gui>
<!-- Keyboard shortcuts --> <!-- Keyboard shortcuts -->
@ -639,6 +639,10 @@
<key action="RightMouseButton" /> <key action="RightMouseButton" />
</actions> </actions>
<drag>
<key action="BrushSize" vector="4.0,0.0" shortcut="Ctrl+Alt" />
</drag>
</keyboard> </keyboard>
<menus> <menus>

View File

@ -1,5 +1,5 @@
# Aseprite # Aseprite
# Copyright (C) 2018-2021 Igara Studio S.A. # Copyright (C) 2018-2022 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello # Copyright (C) 2016-2018 David Capello
[advanced_mode] [advanced_mode]
@ -846,9 +846,14 @@ section_commands = Commands
section_tools = Tools section_tools = Tools
section_action_modifiers = Action Modifiers section_action_modifiers = Action Modifiers
section_mouse_wheel = Mouse Wheel section_mouse_wheel = Mouse Wheel
section_drag_value = Drag Value
default_wheel_behavior = Default default_wheel_behavior = Default
custom_wheel_behavior = Custom custom_wheel_behavior = Custom
slide_as_wheel = Interpret two fingers slide on Trackpad as mouse wheel slide_as_wheel = Interpret two fingers slide on Trackpad as mouse wheel
drag_angle = Angle:
drag_angle_tooltip = Direction of the mouse to indicate an increment of the value
drag_distance = Distance:
drag_distance_tooltip = Number of pixels for mouse movement to increment/decrement one unit
ok = &OK ok = &OK
cancel = &Cancel cancel = &Cancel

View File

@ -1,5 +1,5 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2018 Igara Studio S.A. --> <!-- Copyright (C) 2018-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 David Capello --> <!-- Copyright (C) 2001-2016 David Capello -->
<gui> <gui>
<window id="keyboard_shortcuts" text="@keyboard_shortcuts.title"> <window id="keyboard_shortcuts" text="@keyboard_shortcuts.title">
@ -15,6 +15,7 @@
<listitem text="@.section_tools" /> <listitem text="@.section_tools" />
<listitem text="@.section_action_modifiers" /> <listitem text="@.section_action_modifiers" />
<listitem text="@.section_mouse_wheel" /> <listitem text="@.section_mouse_wheel" />
<listitem text="@.section_drag_value" />
</listbox> </listbox>
</view> </view>
<separator horizontal="true" /> <separator horizontal="true" />
@ -53,6 +54,52 @@
<listbox id="wheel_actions" /> <listbox id="wheel_actions" />
</view> </view>
</vbox> </vbox>
<vbox id="drag_section" expansive="true">
<view expansive="true">
<listbox id="drag_actions" />
</view>
<separator horizontal="true" />
<hbox>
<vbox>
<label text="@.drag_angle" />
</vbox>
<buttonset columns="3" id="drag_angle">
<item icon="canvas_nw"
tooltip="@.drag_angle_tooltip"
tooltip_dir="bottom" />
<item icon="canvas_n"
tooltip="@.drag_angle_tooltip"
tooltip_dir="bottom" />
<item icon="canvas_ne"
tooltip="@.drag_angle_tooltip"
tooltip_dir="bottom" />
<item icon="canvas_w"
tooltip="@.drag_angle_tooltip"
tooltip_dir="right" />
<item />
<item icon="canvas_e"
tooltip="@.drag_angle_tooltip"
tooltip_dir="left" />
<item icon="canvas_sw"
tooltip="@.drag_angle_tooltip"
tooltip_dir="top" />
<item icon="canvas_s"
tooltip="@.drag_angle_tooltip"
tooltip_dir="top" />
<item icon="canvas_se"
tooltip="@.drag_angle_tooltip"
tooltip_dir="top" />
</buttonset>
<vbox>
<label text="@.drag_distance" />
</vbox>
<vbox>
<slider min="1" max="100" id="drag_distance" cell_align="horizontal" width="128"
tooltip="@.drag_distance_tooltip"
tooltip_dir="bottom" />
</vbox>
</hbox>
</vbox>
</vbox> </vbox>
</splitter> </splitter>
<hbox> <hbox>

View File

@ -343,6 +343,7 @@ if(ENABLE_UI)
ui/dynamics_popup.cpp ui/dynamics_popup.cpp
ui/editor/brush_preview.cpp ui/editor/brush_preview.cpp
ui/editor/delayed_mouse_move.cpp ui/editor/delayed_mouse_move.cpp
ui/editor/dragging_value_state.cpp
ui/editor/drawing_state.cpp ui/editor/drawing_state.cpp
ui/editor/editor.cpp ui/editor/editor.cpp
ui/editor/editor_observers.cpp ui/editor/editor_observers.cpp

View File

@ -27,6 +27,7 @@
#include "app/ui/separator_in_view.h" #include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "base/fs.h" #include "base/fs.h"
#include "base/pi.h"
#include "base/scoped_value.h" #include "base/scoped_value.h"
#include "base/split_string.h" #include "base/split_string.h"
#include "base/string.h" #include "base/string.h"
@ -506,14 +507,19 @@ private:
}; };
class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts { class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {
// TODO Merge with CanvasSizeWindow::Dir
enum class Dir { NW, N, NE, W, C, E, SW, S, SE };
public: public:
KeyboardShortcutsWindow(app::KeyboardShortcuts& keys, KeyboardShortcutsWindow(app::KeyboardShortcuts& keys,
MenuKeys& menuKeys, MenuKeys& menuKeys,
const std::string& searchText) const std::string& searchText,
int& curSection)
: m_keys(keys) : m_keys(keys)
, m_menuKeys(menuKeys) , m_menuKeys(menuKeys)
, m_searchChange(false) , m_searchChange(false)
, m_wasDefault(false) { , m_wasDefault(false)
, m_curSection(curSection) {
setAutoRemap(false); setAutoRemap(false);
m_listBoxes.push_back(menus()); m_listBoxes.push_back(menus());
@ -521,6 +527,7 @@ public:
m_listBoxes.push_back(tools()); m_listBoxes.push_back(tools());
m_listBoxes.push_back(actions()); m_listBoxes.push_back(actions());
m_listBoxes.push_back(wheelActions()); m_listBoxes.push_back(wheelActions());
m_listBoxes.push_back(dragActions());
#ifdef __APPLE__ // Zoom sliding two fingers option only on macOS #ifdef __APPLE__ // Zoom sliding two fingers option only on macOS
slideZoom()->setVisible(true); slideZoom()->setVisible(true);
@ -544,6 +551,9 @@ public:
search()->Change.connect([this]{ onSearchChange(); }); search()->Change.connect([this]{ onSearchChange(); });
section()->Change.connect([this]{ onSectionChange(); }); section()->Change.connect([this]{ onSectionChange(); });
dragActions()->Change.connect([this]{ onDragActionsChange(); });
dragAngle()->ItemChange.connect([this]{ onDragVectorChange(); });
dragDistance()->Change.connect([this]{ onDragVectorChange(); });
importButton()->Click.connect([this]{ onImport(); }); importButton()->Click.connect([this]{ onImport(); });
exportButton()->Click.connect([this]{ onExport(); }); exportButton()->Click.connect([this]{ onExport(); });
resetButton()->Click.connect([this]{ onReset(); }); resetButton()->Click.connect([this]{ onReset(); });
@ -573,6 +583,7 @@ private:
deleteList(tools()); deleteList(tools());
deleteList(actions()); deleteList(actions());
deleteList(wheelActions()); deleteList(wheelActions());
deleteList(dragActions());
} }
void fillAllLists() { void fillAllLists() {
@ -592,11 +603,13 @@ private:
fillToolsList(tools(), App::instance()->toolBox()); fillToolsList(tools(), App::instance()->toolBox());
fillWheelActionsList(); fillWheelActionsList();
fillDragActionsList();
for (const KeyPtr& key : m_keys) { for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::Tool || if (key->type() == KeyType::Tool ||
key->type() == KeyType::Quicktool || key->type() == KeyType::Quicktool ||
key->type() == KeyType::WheelAction) { key->type() == KeyType::WheelAction ||
key->type() == KeyType::DragAction) {
continue; continue;
} }
@ -636,7 +649,7 @@ private:
tools()->sortItems(); tools()->sortItems();
actions()->sortItems(); actions()->sortItems();
section()->selectIndex(0); section()->selectIndex(m_curSection);
updateViews(); updateViews();
} }
@ -730,6 +743,19 @@ private:
wheelActions()->sortItems(); wheelActions()->sortItems();
} }
void fillDragActionsList() {
deleteList(dragActions());
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::DragAction) {
KeyItem* keyItem = new KeyItem(
m_keys, m_menuKeys, key->triggerString(), key,
nullptr, 0, &m_headerItem);
dragActions()->addChild(keyItem);
}
}
dragActions()->sortItems();
}
void onWheelZoomChange() { void onWheelZoomChange() {
const bool isDefault = isDefaultWheelBehavior(); const bool isDefault = isDefaultWheelBehavior();
if (isDefault) if (isDefault)
@ -741,7 +767,7 @@ private:
std::string searchText = search()->text(); std::string searchText = search()->text();
if (searchText.empty()) if (searchText.empty())
section()->selectIndex(0); section()->selectIndex(m_curSection);
else { else {
fillSearchList(searchText); fillSearchList(searchText);
section()->selectChild(nullptr); section()->selectChild(nullptr);
@ -758,14 +784,48 @@ private:
updateViews(); updateViews();
} }
void onDragActionsChange() {
auto key = selectedDragActionKey();
if (!key)
return;
int angle = 180 * key->dragVector().angle() / PI;
ui::Widget* oldFocus = manager()->getFocus();
dragAngle()->setSelectedItem((int)angleToDir(angle));
if (oldFocus)
oldFocus->requestFocus();
dragDistance()->setValue(key->dragVector().magnitude());
}
void onDragVectorChange() {
auto key = selectedDragActionKey();
if (!key)
return;
auto v = key->dragVector();
double a = dirToAngle((Dir)dragAngle()->selectedItem()).angle();
double m = dragDistance()->getValue();
v.x = m * std::cos(a);
v.y = m * std::sin(a);
if (std::fabs(v.x) < 0.00001) v.x = 0.0;
if (std::fabs(v.y) < 0.00001) v.y = 0.0;
key->setDragVector(v);
}
void updateViews() { void updateViews() {
int s = section()->getSelectedIndex(); int s = section()->getSelectedIndex();
if (s >= 0)
m_curSection = s;
searchView()->setVisible(s < 0); searchView()->setVisible(s < 0);
menusView()->setVisible(s == 0); menusView()->setVisible(s == 0);
commandsView()->setVisible(s == 1); commandsView()->setVisible(s == 1);
toolsView()->setVisible(s == 2); toolsView()->setVisible(s == 2);
actionsView()->setVisible(s == 3); actionsView()->setVisible(s == 3);
wheelSection()->setVisible(s == 4); wheelSection()->setVisible(s == 4);
dragSection()->setVisible(s == 5);
if (m_headerItem.parent()) if (m_headerItem.parent())
m_headerItem.parent()->removeChild(&m_headerItem); m_headerItem.parent()->removeChild(&m_headerItem);
@ -868,12 +928,51 @@ private:
return app::gen::KeyboardShortcuts::onProcessMessage(msg); return app::gen::KeyboardShortcuts::onProcessMessage(msg);
} }
KeyPtr selectedDragActionKey() {
auto item = dragActions()->getSelectedChild();
if (KeyItem* keyItem = dynamic_cast<KeyItem*>(item)) {
KeyPtr key = keyItem->key();
if (key && key->type() == KeyType::DragAction)
return key;
}
return nullptr;
}
Dir angleToDir(int angle) {
if (angle >= -1*45/2 && angle < 1*45/2) return Dir::E;
if (angle >= 1*45/2 && angle < 3*45/2) return Dir::NE;
if (angle >= 3*45/2 && angle < 5*45/2) return Dir::N;
if (angle >= 5*45/2 && angle < 7*45/2) return Dir::NW;
if ((angle >= 7*45/2 && angle <= 180) ||
(angle >= -180 && angle <= -7*45/2)) return Dir::W;
if (angle > -7*45/2 && angle <= -5*45/2) return Dir::SW;
if (angle > -5*45/2 && angle <= -3*45/2) return Dir::S;
if (angle > -3*45/2 && angle <= -1*45/2) return Dir::SE;
return Dir::C;
}
DragVector dirToAngle(Dir dir) {
switch (dir) {
case Dir::NW: return DragVector(-1, +1);
case Dir::N: return DragVector( 0, +1);
case Dir::NE: return DragVector(+1, +1);
case Dir::W: return DragVector(-1, 0);
case Dir::C: return DragVector( 0, 0);
case Dir::E: return DragVector(+1, 0);
case Dir::SW: return DragVector(-1, -1);
case Dir::S: return DragVector( 0, -1);
case Dir::SE: return DragVector(+1, -1);
}
return DragVector();
}
app::KeyboardShortcuts& m_keys; app::KeyboardShortcuts& m_keys;
MenuKeys& m_menuKeys; MenuKeys& m_menuKeys;
std::vector<ListBox*> m_listBoxes; std::vector<ListBox*> m_listBoxes;
bool m_searchChange; bool m_searchChange;
bool m_wasDefault; bool m_wasDefault;
HeaderItem m_headerItem; HeaderItem m_headerItem;
int& m_curSection;
}; };
} // anonymous namespace } // anonymous namespace
@ -905,6 +1004,8 @@ void KeyboardShortcutsCommand::onLoadParams(const Params& params)
void KeyboardShortcutsCommand::onExecute(Context* context) void KeyboardShortcutsCommand::onExecute(Context* context)
{ {
static int curSection = 0;
app::KeyboardShortcuts* globalKeys = app::KeyboardShortcuts::instance(); app::KeyboardShortcuts* globalKeys = app::KeyboardShortcuts::instance();
app::KeyboardShortcuts keys; app::KeyboardShortcuts keys;
keys.setKeys(*globalKeys, true); keys.setKeys(*globalKeys, true);
@ -919,7 +1020,7 @@ void KeyboardShortcutsCommand::onExecute(Context* context)
// KeyboardShortcutsCommand instance (so m_search will be "") // KeyboardShortcutsCommand instance (so m_search will be "")
// TODO Seeing this, we need a complete new way to handle UI commands execution // TODO Seeing this, we need a complete new way to handle UI commands execution
std::string neededSearchCopy = m_search; std::string neededSearchCopy = m_search;
KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy); KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy, curSection);
ui::Display* mainDisplay = Manager::getDefault()->display(); ui::Display* mainDisplay = Manager::getDefault()->display();
ui::fit_bounds(mainDisplay, &window, ui::fit_bounds(mainDisplay, &window,

View File

@ -0,0 +1,144 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/editor/dragging_value_state.h"
#include "app/ui/editor/editor.h"
#include "base/clamp.h"
#include "ui/display.h"
#include "ui/message.h"
#include "ui/scale.h"
#include "ui/system.h"
#include <cmath>
namespace app {
using namespace ui;
DraggingValueState::DraggingValueState(Editor* editor, const Keys& keys)
: m_keys(keys)
, m_initialPos(editor->display()->nativeWindow()->pointFromScreen(ui::get_mouse_position()))
, m_initialFgColor(StateWithWheelBehavior::initialFgColor())
, m_initialBgColor(StateWithWheelBehavior::initialBgColor())
, m_initialFgTileIndex(StateWithWheelBehavior::initialFgTileIndex())
, m_initialBgTileIndex(StateWithWheelBehavior::initialBgTileIndex())
, m_initialBrushSize(StateWithWheelBehavior::initialBrushSize())
, m_initialBrushAngle(StateWithWheelBehavior::initialBrushAngle())
, m_initialScroll(StateWithWheelBehavior::initialScroll(editor))
, m_initialZoom(StateWithWheelBehavior::initialZoom(editor))
, m_initialFrame(StateWithWheelBehavior::initialFrame(editor))
, m_initialInkOpacity(StateWithWheelBehavior::initialInkOpacity(editor))
, m_initialCelOpacity(StateWithWheelBehavior::initialCelOpacity(editor))
, m_initialLayerOpacity(StateWithWheelBehavior::initialLayerOpacity(editor))
, m_initialTool(StateWithWheelBehavior::initialTool())
{
if (!editor->hasCapture())
editor->captureMouse();
// As StateWithWheelBehavior::initialLayer() fills browsableLayers()
// we will only fill it if it's necessary (there is a key that
// triggers WheelAction::Layer)
for (const KeyPtr& key : m_keys) {
if (key->wheelAction() == WheelAction::Layer) {
m_initialLayer = StateWithWheelBehavior::initialLayer(editor);
break;
}
}
}
bool DraggingValueState::onMouseDown(Editor* editor, MouseMessage* msg)
{
return true;
}
bool DraggingValueState::onMouseUp(Editor* editor, MouseMessage* msg)
{
editor->backToPreviousState();
editor->releaseMouse();
return true;
}
bool DraggingValueState::onMouseMove(Editor* editor, MouseMessage* msg)
{
m_fgColor = m_initialFgColor;
for (const KeyPtr& key : m_keys) {
const gfx::Point delta = (msg->position() - m_initialPos);
const DragVector deltaV(delta.x, delta.y);
const DragVector invDragVector(key->dragVector().x,
-key->dragVector().y);
const double threshold = invDragVector.magnitude();
DragVector v = deltaV.projectOn(invDragVector);
double dz = v.magnitude();
{
if (threshold > 0)
dz /= threshold;
auto dot = invDragVector * v;
dz *= SGN(dot);
bool preciseWheel = true;
if (key->wheelAction() == WheelAction::Zoom ||
key->wheelAction() == WheelAction::Frame ||
key->wheelAction() == WheelAction::Layer) {
preciseWheel = false;
dz = -dz; // Invert value for zoom only so the vector is
// pointing to the direction to increase zoom
// TODO we should change the direction of the wheel
// information from the laf layer
}
processWheelAction(editor,
key->wheelAction(),
msg->position(),
delta,
dz,
false, // scrollBigSteps=false
preciseWheel);
}
}
if (m_fgColor != m_initialFgColor)
StateWithWheelBehavior::changeFgColor(m_fgColor);
return true;
}
bool DraggingValueState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
}
bool DraggingValueState::onKeyDown(Editor* editor, KeyMessage* msg)
{
return false;
}
bool DraggingValueState::onKeyUp(Editor* editor, KeyMessage* msg)
{
if (editor->hasCapture())
editor->releaseMouse();
editor->backToPreviousState();
return true;
}
bool DraggingValueState::onUpdateStatusBar(Editor* editor)
{
return false;
}
void DraggingValueState::changeFgColor(Color c)
{
m_fgColor = c;
}
} // namespace app

View File

@ -0,0 +1,77 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_DRAGGING_VALUE_STATE_H_INCLUDED
#define APP_UI_EDITOR_DRAGGING_VALUE_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/state_with_wheel_behavior.h"
#include "app/ui/key.h"
#include "gfx/point.h"
#include "render/zoom.h"
namespace app {
class DraggingValueState : public StateWithWheelBehavior {
public:
DraggingValueState(Editor* editor, const Keys& keys);
bool isTemporalState() const override { return true; }
bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override;
bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override;
bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override;
bool onUpdateStatusBar(Editor* editor) override;
bool requireBrushPreview() override { return true; }
private:
Color initialFgColor() const override { return m_fgColor; }
Color initialBgColor() const override { return m_initialBgColor; }
int initialFgTileIndex() const override { return m_initialFgTileIndex; }
int initialBgTileIndex() const override { return m_initialBgTileIndex; }
int initialBrushSize() override { return m_initialBrushSize; }
int initialBrushAngle() override { return m_initialBrushAngle; }
gfx::Point initialScroll(Editor* editor) const override { return m_initialScroll; }
render::Zoom initialZoom(Editor* editor) const override { return m_initialZoom; }
doc::frame_t initialFrame(Editor* editor) const override { return m_initialFrame; }
doc::layer_t initialLayer(Editor* editor) const override { return m_initialLayer; }
int initialInkOpacity(Editor* editor) const override { return m_initialInkOpacity; }
int initialCelOpacity(Editor* editor) const override { return m_initialCelOpacity; }
int initialLayerOpacity(Editor* editor) const override { return m_initialLayerOpacity; }
tools::Tool* initialTool() const override { return m_initialTool; }
void changeFgColor(Color c) override;
Keys m_keys;
gfx::Point m_initialPos;
Color m_initialFgColor;
Color m_initialBgColor;
int m_initialFgTileIndex;
int m_initialBgTileIndex;
int m_initialBrushSize;
int m_initialBrushAngle;
gfx::Point m_initialScroll;
render::Zoom m_initialZoom;
doc::frame_t m_initialFrame;
doc::layer_t m_initialLayer;
int m_initialInkOpacity;
int m_initialCelOpacity;
int m_initialLayerOpacity;
tools::Tool* m_initialTool;
// Used to allow multiple modifications to the same initial FG
// color (m_initialFgColor), e.g. when multiples Key will change
// different elements of the color (e.g. Value and Saturation) at
// the same time with different DragVectors/axes.
Color m_fgColor;
};
} // namespace app
#endif // APP_UI_EDITOR_ZOOMING_STATE_H_INCLUDED

View File

@ -27,6 +27,7 @@
#include "app/tools/tool.h" #include "app/tools/tool.h"
#include "app/ui/app_menuitem.h" #include "app/ui/app_menuitem.h"
#include "app/ui/doc_view.h" #include "app/ui/doc_view.h"
#include "app/ui/editor/dragging_value_state.h"
#include "app/ui/editor/drawing_state.h" #include "app/ui/editor/drawing_state.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_customization_delegate.h" #include "app/ui/editor/editor_customization_delegate.h"
@ -435,20 +436,6 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
editor->showBrushPreview(mouseScreenPos); editor->showBrushPreview(mouseScreenPos);
return true; return true;
} }
else if (ink->isEyedropper()) {
editor->showMouseCursor(
kCustomCursor, theme->cursors.eyedropper());
return true;
}
else if (ink->isZoom()) {
editor->showMouseCursor(
kCustomCursor, theme->cursors.magnifier());
return true;
}
else if (ink->isScrollMovement()) {
editor->showMouseCursor(kScrollCursor);
return true;
}
else if (ink->isCelMovement()) { else if (ink->isCelMovement()) {
if (resizeCelBounds(editor).contains(mouseScreenPos)) if (resizeCelBounds(editor).contains(mouseScreenPos))
editor->showMouseCursor(kSizeSECursor); editor->showMouseCursor(kSizeSECursor);
@ -498,16 +485,7 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
} }
} }
// Draw return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
if (editor->canDraw()) {
editor->showBrushPreview(mouseScreenPos);
}
// Forbidden
else {
editor->showMouseCursor(kForbiddenCursor);
}
return true;
} }
bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg) bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
@ -515,6 +493,15 @@ bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
if (Preferences::instance().editor.straightLinePreview() && if (Preferences::instance().editor.straightLinePreview() &&
checkStartDrawingStraightLine(editor, nullptr, nullptr)) checkStartDrawingStraightLine(editor, nullptr, nullptr))
return false; return false;
Keys keys = KeyboardShortcuts::instance()
->getDragActionsFromKeyMessage(KeyContext::MouseWheel, msg);
if (!keys.empty()) {
EditorStatePtr newState(new DraggingValueState(editor, keys));
editor->setState(newState);
return true;
}
return false; return false;
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020-2021 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -33,10 +33,41 @@
#include "ui/system.h" #include "ui/system.h"
#include "ui/theme.h" #include "ui/theme.h"
#include "app/tools/ink.h"
#include "app/ui/skin/skin_theme.h"
namespace app { namespace app {
using namespace ui; using namespace ui;
template<typename T>
static inline void adjust_value(bool preciseWheel, double dz, T& v, T min, T max)
{
if (preciseWheel)
v = base::clamp<T>(T(v+dz), min, max);
else
v = base::clamp<T>(T(v+dz*max/T(10)), min, max);
}
template<typename T>
static inline void adjust_hue(bool preciseWheel, double dz, T& v, T min, T max)
{
if (preciseWheel)
v = base::clamp<T>(T(v+dz), min, max);
else
v = base::clamp<T>(T(v+dz*T(10)), min, max);
}
static inline void adjust_unit(bool preciseWheel, double dz, double& v)
{
v = base::clamp<double>(v+(preciseWheel ? dz/100.0: dz/25.0), 0.0, 1.0);
}
StateWithWheelBehavior::StateWithWheelBehavior()
: m_groupTool(initialTool())
{
}
bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg) bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
{ {
gfx::Point delta = msg->wheelDelta(); gfx::Point delta = msg->wheelDelta();
@ -109,6 +140,25 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
} }
} }
processWheelAction(editor,
wheelAction,
msg->position(),
delta,
dz,
scrollBigSteps,
msg->preciseWheel());
return true;
}
void StateWithWheelBehavior::processWheelAction(
Editor* editor,
WheelAction wheelAction,
const gfx::Point& position,
gfx::Point delta,
double dz,
bool scrollBigSteps,
bool preciseWheel)
{
switch (wheelAction) { switch (wheelAction) {
case WheelAction::None: case WheelAction::None:
@ -117,15 +167,15 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::FgColor: { case WheelAction::FgColor: {
int lastIndex = get_current_palette()->size()-1; int lastIndex = get_current_palette()->size()-1;
int newIndex = ColorBar::instance()->getFgColor().getIndex() + int(dz); int newIndex = initialFgColor().getIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex); newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setFgColor(app::Color::fromIndex(newIndex)); changeFgColor(app::Color::fromIndex(newIndex));
break; break;
} }
case WheelAction::BgColor: { case WheelAction::BgColor: {
int lastIndex = get_current_palette()->size()-1; int lastIndex = get_current_palette()->size()-1;
int newIndex = ColorBar::instance()->getBgColor().getIndex() + int(dz); int newIndex = initialBgColor().getIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex); newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex)); ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex));
break; break;
@ -134,7 +184,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::FgTile: { case WheelAction::FgTile: {
auto tilesView = ColorBar::instance()->getTilesView(); auto tilesView = ColorBar::instance()->getTilesView();
int lastIndex = tilesView->tileset()->size()-1; int lastIndex = tilesView->tileset()->size()-1;
int newIndex = ColorBar::instance()->getFgTile() + int(dz); int newIndex = initialFgTileIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex); newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setFgTile(newIndex); ColorBar::instance()->setFgTile(newIndex);
break; break;
@ -143,29 +193,39 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::BgTile: { case WheelAction::BgTile: {
auto tilesView = ColorBar::instance()->getTilesView(); auto tilesView = ColorBar::instance()->getTilesView();
int lastIndex = tilesView->tileset()->size()-1; int lastIndex = tilesView->tileset()->size()-1;
int newIndex = ColorBar::instance()->getBgTile() + int(dz); int newIndex = initialBgTileIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex); newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setBgTile(newIndex); ColorBar::instance()->setBgTile(newIndex);
break; break;
} }
case WheelAction::Frame: { case WheelAction::Frame: {
Command* command = nullptr; frame_t deltaFrame = 0;
if (preciseWheel) {
if (dz < 0.0)
deltaFrame = +1;
else if (dz > 0.0)
deltaFrame = -1;
}
else {
deltaFrame = -dz;
}
if (dz < 0.0) frame_t frame = initialFrame(editor) + deltaFrame;
command = Commands::instance()->byId(CommandId::GotoNextFrame()); frame_t nframes = editor->sprite()->totalFrames();
else if (dz > 0.0) while (frame < 0)
command = Commands::instance()->byId(CommandId::GotoPreviousFrame()); frame += nframes;
while (frame >= nframes)
frame -= nframes;
if (command) editor->setFrame(frame);
UIContext::instance()->executeCommand(command);
break; break;
} }
case WheelAction::Zoom: { case WheelAction::Zoom: {
render::Zoom zoom = editor->zoom(); render::Zoom zoom = initialZoom(editor);
if (msg->preciseWheel()) { if (preciseWheel) {
dz /= 1.5; dz /= 1.5;
if (dz < -1.0) dz = -1.0; if (dz < -1.0) dz = -1.0;
else if (dz > 1.0) dz = 1.0; else if (dz > 1.0) dz = 1.0;
@ -173,16 +233,16 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
zoom = render::Zoom::fromLinearScale(zoom.linearScale() - int(dz)); zoom = render::Zoom::fromLinearScale(zoom.linearScale() - int(dz));
setZoom(editor, zoom, msg->position()); setZoom(editor, zoom, position);
break; break;
} }
case WheelAction::HScroll: case WheelAction::HScroll:
case WheelAction::VScroll: { case WheelAction::VScroll: {
View* view = View::getView(editor); View* view = View::getView(editor);
gfx::Point scroll = view->viewScroll(); gfx::Point scroll = initialScroll(editor);
if (!msg->preciseWheel()) { if (!preciseWheel) {
gfx::Rect vp = view->viewportBounds(); gfx::Rect vp = view->viewportBounds();
if (wheelAction == WheelAction::HScroll) { if (wheelAction == WheelAction::HScroll) {
@ -211,7 +271,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
brush.size( brush.size(
base::clamp( base::clamp(
int(brush.size()+dz), int(initialBrushSize()+dz),
// If we use the "static const int" member directly here, // If we use the "static const int" member directly here,
// we'll get a linker error (when compiling without // we'll get a linker error (when compiling without
// optimizations) because we should need to define the // optimizations) because we should need to define the
@ -226,7 +286,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
ToolPreferences::Brush& brush = ToolPreferences::Brush& brush =
Preferences::instance().tool(tool).brush; Preferences::instance().tool(tool).brush;
int angle = brush.angle()+dz; int angle = initialBrushAngle()+dz;
while (angle < 0) while (angle < 0)
angle += 180; angle += 180;
angle %= 181; angle %= 181;
@ -236,7 +296,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
} }
case WheelAction::ToolSameGroup: { case WheelAction::ToolSameGroup: {
tools::Tool* tool = getActiveTool(); const tools::Tool* tool = m_groupTool;
auto toolBox = App::instance()->toolBox(); auto toolBox = App::instance()->toolBox();
std::vector<tools::Tool*> tools; std::vector<tools::Tool*> tools;
@ -249,12 +309,13 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
auto end = tools.end(); auto end = tools.end();
auto it = std::find(begin, end, tool); auto it = std::find(begin, end, tool);
if (it != end) { if (it != end) {
if (dz < 0) { int i = std::round(dz);
while (i++ < 0) {
if (it == begin) if (it == begin)
it = end; it = end;
--it; --it;
} }
else { while (i-- > 0) {
++it; ++it;
if (it == end) if (it == end)
it = begin; it = begin;
@ -266,43 +327,60 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
} }
case WheelAction::ToolOtherGroup: { case WheelAction::ToolOtherGroup: {
tools::Tool* tool = getActiveTool(); tools::Tool* tool = initialTool();
auto toolBox = App::instance()->toolBox(); auto toolBox = App::instance()->toolBox();
auto begin = toolBox->begin_group(); auto begin = toolBox->begin_group();
auto end = toolBox->end_group(); auto end = toolBox->end_group();
auto it = std::find(begin, end, tool->getGroup()); auto it = std::find(begin, end, tool->getGroup());
if (it != end) { if (it != end) {
if (dz < 0) { int i = std::round(dz);
while (i++ < 0) {
if (it == begin) if (it == begin)
it = end; it = end;
--it; --it;
} }
else { while (i-- > 0) {
++it; ++it;
if (it == end) if (it == end)
it = begin; it = begin;
} }
ToolBar::instance()->selectToolGroup(*it); ToolBar::instance()->selectToolGroup(*it);
m_groupTool = getActiveTool();
} }
break; break;
} }
case WheelAction::Layer: { case WheelAction::Layer: {
Command* command = nullptr; int deltaLayer = 0;
if (dz < 0.0) if (preciseWheel) {
command = Commands::instance()->byId(CommandId::GotoNextLayer()); if (dz < 0.0)
else if (dz > 0.0) deltaLayer = +1;
command = Commands::instance()->byId(CommandId::GotoPreviousLayer()); else if (dz > 0.0)
if (command) deltaLayer = -1;
UIContext::instance()->executeCommand(command); }
else {
deltaLayer = -dz;
}
const LayerList& layers = browsableLayers(editor);
layer_t layer = initialLayer(editor) + deltaLayer;
layer_t nlayers = layers.size();
while (layer < 0)
layer += nlayers;
while (layer >= nlayers)
layer -= nlayers;
editor->setLayer(layers[layer]);
break; break;
} }
case WheelAction::InkOpacity: { case WheelAction::InkOpacity: {
int opacity = initialInkOpacity(editor);
adjust_value(preciseWheel, dz, opacity, 0, 255);
tools::Tool* tool = getActiveTool(); tools::Tool* tool = getActiveTool();
auto& toolPref = Preferences::instance().tool(tool); auto& toolPref = Preferences::instance().tool(tool);
int opacity = toolPref.opacity();
opacity = base::clamp(int(opacity+dz*255/10), 0, 255);
toolPref.opacity(opacity); toolPref.opacity(opacity);
break; break;
} }
@ -314,8 +392,8 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
site.layer()->isEditable()) { site.layer()->isEditable()) {
Command* command = Commands::instance()->byId(CommandId::LayerOpacity()); Command* command = Commands::instance()->byId(CommandId::LayerOpacity());
if (command) { if (command) {
int opacity = static_cast<doc::LayerImage*>(site.layer())->opacity(); int opacity = initialLayerOpacity(editor);
opacity = base::clamp(int(opacity+dz*255/10), 0, 255); adjust_value(preciseWheel, dz, opacity, 0, 255);
Params params; Params params;
params.set("opacity", params.set("opacity",
@ -334,8 +412,9 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
site.cel()) { site.cel()) {
Command* command = Commands::instance()->byId(CommandId::CelOpacity()); Command* command = Commands::instance()->byId(CommandId::CelOpacity());
if (command) { if (command) {
int opacity = site.cel()->opacity(); int opacity = initialCelOpacity(editor);
opacity = base::clamp(int(opacity+dz*255/10), 0, 255); adjust_value(preciseWheel, dz, opacity, 0, 255);
Params params; Params params;
params.set("opacity", params.set("opacity",
base::convert_to<std::string>(opacity).c_str()); base::convert_to<std::string>(opacity).c_str());
@ -348,12 +427,12 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::Alpha: { case WheelAction::Alpha: {
disableQuickTool(); disableQuickTool();
ColorBar* colorBar = ColorBar::instance(); Color c = initialFgColor();
Color c = colorBar->getFgColor();
int a = c.getAlpha(); int a = c.getAlpha();
a = base::clamp(int(a+dz*255/10), 0, 255); adjust_value(preciseWheel, dz, a, 0, 255);
c.setAlpha(a); c.setAlpha(a);
colorBar->setFgColor(c);
changeFgColor(c);
break; break;
} }
@ -362,20 +441,26 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::HslLightness: { case WheelAction::HslLightness: {
disableQuickTool(); disableQuickTool();
ColorBar* colorBar = ColorBar::instance(); Color c = initialFgColor();
Color c = colorBar->getFgColor();
double double
h = c.getHslHue(), h = c.getHslHue(),
s = c.getHslSaturation(), s = c.getHslSaturation(),
l = c.getHslLightness(); l = c.getHslLightness();
switch (wheelAction) { switch (wheelAction) {
case WheelAction::HslHue: h = h+dz*10.0; break; case WheelAction::HslHue:
case WheelAction::HslSaturation: s = s+dz/10.0; break; adjust_hue(preciseWheel, dz, h, 0.0, 360.0);
case WheelAction::HslLightness: l = l+dz/10.0; break; break;
case WheelAction::HslSaturation:
adjust_unit(preciseWheel, dz, s);
break;
case WheelAction::HslLightness:
adjust_unit(preciseWheel, dz, l);
break;
} }
colorBar->setFgColor(Color::fromHsl(base::clamp(h, 0.0, 360.0),
base::clamp(s, 0.0, 1.0), changeFgColor(Color::fromHsl(base::clamp(h, 0.0, 360.0),
base::clamp(l, 0.0, 1.0))); base::clamp(s, 0.0, 1.0),
base::clamp(l, 0.0, 1.0)));
break; break;
} }
@ -384,26 +469,30 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::HsvValue: { case WheelAction::HsvValue: {
disableQuickTool(); disableQuickTool();
ColorBar* colorBar = ColorBar::instance(); Color c = initialFgColor();
Color c = colorBar->getFgColor();
double double
h = c.getHsvHue(), h = c.getHsvHue(),
s = c.getHsvSaturation(), s = c.getHsvSaturation(),
v = c.getHsvValue(); v = c.getHsvValue();
switch (wheelAction) { switch (wheelAction) {
case WheelAction::HsvHue: h = h+dz*10.0; break; case WheelAction::HsvHue:
case WheelAction::HsvSaturation: s = s+dz/10.0; break; adjust_hue(preciseWheel, dz, h, 0.0, 360.0);
case WheelAction::HsvValue: v = v+dz/10.0; break; break;
case WheelAction::HsvSaturation:
adjust_unit(preciseWheel, dz, s);
break;
case WheelAction::HsvValue:
adjust_unit(preciseWheel, dz, v);
break;
} }
colorBar->setFgColor(Color::fromHsv(base::clamp(h, 0.0, 360.0),
base::clamp(s, 0.0, 1.0), changeFgColor(Color::fromHsv(base::clamp(h, 0.0, 360.0),
base::clamp(v, 0.0, 1.0))); base::clamp(s, 0.0, 1.0),
base::clamp(v, 0.0, 1.0)));
break; break;
} }
} }
return true;
} }
bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* msg) bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* msg)
@ -416,6 +505,49 @@ bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* ms
return true; return true;
} }
bool StateWithWheelBehavior::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
tools::Ink* ink = editor->getCurrentEditorInk();
auto theme = skin::SkinTheme::get(editor);
if (ink) {
// If the current tool change selection (e.g. rectangular marquee, etc.)
if (ink->isSelection()) {
editor->showBrushPreview(mouseScreenPos);
return true;
}
else if (ink->isEyedropper()) {
editor->showMouseCursor(
kCustomCursor, theme->cursors.eyedropper());
return true;
}
else if (ink->isZoom()) {
editor->showMouseCursor(
kCustomCursor, theme->cursors.magnifier());
return true;
}
else if (ink->isScrollMovement()) {
editor->showMouseCursor(kScrollCursor);
return true;
}
else if (ink->isCelMovement()) {
editor->showMouseCursor(kMoveCursor);
return true;
}
}
// Draw
if (editor->canDraw()) {
editor->showBrushPreview(mouseScreenPos);
}
// Forbidden
else {
editor->showMouseCursor(kForbiddenCursor);
}
return true;
}
void StateWithWheelBehavior::setZoom(Editor* editor, void StateWithWheelBehavior::setZoom(Editor* editor,
const render::Zoom& zoom, const render::Zoom& zoom,
const gfx::Point& mousePos) const gfx::Point& mousePos)
@ -428,13 +560,120 @@ void StateWithWheelBehavior::setZoom(Editor* editor,
Editor::ZoomBehavior::MOUSE)); Editor::ZoomBehavior::MOUSE));
} }
tools::Tool* StateWithWheelBehavior::getActiveTool() Color StateWithWheelBehavior::initialFgColor() const
{
return ColorBar::instance()->getFgColor();
}
Color StateWithWheelBehavior::initialBgColor() const
{
return ColorBar::instance()->getBgColor();
}
int StateWithWheelBehavior::initialFgTileIndex() const
{
return ColorBar::instance()->getFgTile();
}
int StateWithWheelBehavior::initialBgTileIndex() const
{
return ColorBar::instance()->getBgTile();
}
int StateWithWheelBehavior::initialBrushSize()
{
tools::Tool* tool = getActiveTool();
ToolPreferences::Brush& brush =
Preferences::instance().tool(tool).brush;
return brush.size();
}
int StateWithWheelBehavior::initialBrushAngle()
{
tools::Tool* tool = getActiveTool();
ToolPreferences::Brush& brush =
Preferences::instance().tool(tool).brush;
return brush.angle();
}
gfx::Point StateWithWheelBehavior::initialScroll(Editor* editor) const
{
View* view = View::getView(editor);
return view->viewScroll();
}
render::Zoom StateWithWheelBehavior::initialZoom(Editor* editor) const
{
return editor->zoom();
}
doc::frame_t StateWithWheelBehavior::initialFrame(Editor* editor) const
{
return editor->frame();
}
doc::layer_t StateWithWheelBehavior::initialLayer(Editor* editor) const
{
return doc::find_layer_index(browsableLayers(editor), editor->layer());
}
int StateWithWheelBehavior::initialInkOpacity(Editor* editor) const
{
tools::Tool* tool = getActiveTool();
auto& toolPref = Preferences::instance().tool(tool);
return toolPref.opacity();
}
int StateWithWheelBehavior::initialCelOpacity(Editor* editor) const
{
doc::Layer* layer = editor->layer();
if (layer &&
layer->isImage() &&
layer->isEditable()) {
if (Cel* cel = layer->cel(editor->frame()))
return cel->opacity();
}
return 0;
}
int StateWithWheelBehavior::initialLayerOpacity(Editor* editor) const
{
doc::Layer* layer = editor->layer();
if (layer &&
layer->isImage() &&
layer->isEditable()) {
return static_cast<doc::LayerImage*>(layer)->opacity();
}
else
return 0;
}
tools::Tool* StateWithWheelBehavior::initialTool() const
{
return getActiveTool();
}
void StateWithWheelBehavior::changeFgColor(Color c)
{
ColorBar::instance()->setFgColor(c);
}
tools::Tool* StateWithWheelBehavior::getActiveTool() const
{ {
disableQuickTool(); disableQuickTool();
return App::instance()->activeToolManager()->activeTool(); return App::instance()->activeToolManager()->activeTool();
} }
void StateWithWheelBehavior::disableQuickTool() const doc::LayerList& StateWithWheelBehavior::browsableLayers(Editor* editor) const
{
if (m_browsableLayers.empty())
m_browsableLayers = editor->sprite()->allBrowsableLayers();
return m_browsableLayers;
}
void StateWithWheelBehavior::disableQuickTool() const
{ {
auto atm = App::instance()->activeToolManager(); auto atm = App::instance()->activeToolManager();
if (atm->quickTool()) { if (atm->quickTool()) {

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -8,7 +9,12 @@
#define APP_UI_STATE_WITH_WHEEL_BEHAVIOR_H_INCLUDED #define APP_UI_STATE_WITH_WHEEL_BEHAVIOR_H_INCLUDED
#pragma once #pragma once
#include "app/color.h"
#include "app/ui/editor/editor_state.h" #include "app/ui/editor/editor_state.h"
#include "app/ui/key.h"
#include "doc/frame.h"
#include "doc/layer.h"
#include "doc/layer_list.h"
namespace render { namespace render {
class Zoom; class Zoom;
@ -22,12 +28,45 @@ namespace app {
class StateWithWheelBehavior : public EditorState { class StateWithWheelBehavior : public EditorState {
public: public:
virtual bool onMouseWheel(Editor* editor, ui::MouseMessage* msg) override; StateWithWheelBehavior();
virtual bool onTouchMagnify(Editor* editor, ui::TouchMessage* msg) override;
bool onMouseWheel(Editor* editor, ui::MouseMessage* msg) override;
bool onTouchMagnify(Editor* editor, ui::TouchMessage* msg) override;
bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override;
protected:
void processWheelAction(Editor* editor,
WheelAction wheelAction,
const gfx::Point& position,
gfx::Point delta,
double dz,
bool scrollBigSteps,
bool preciseWheel);
const doc::LayerList& browsableLayers(Editor* editor) const;
virtual Color initialFgColor() const;
virtual Color initialBgColor() const;
virtual int initialFgTileIndex() const;
virtual int initialBgTileIndex() const;
virtual int initialBrushSize();
virtual int initialBrushAngle();
virtual gfx::Point initialScroll(Editor* editor) const;
virtual render::Zoom initialZoom(Editor* editor) const;
virtual doc::frame_t initialFrame(Editor* editor) const;
virtual doc::layer_t initialLayer(Editor* editor) const;
virtual int initialInkOpacity(Editor* editor) const;
virtual int initialCelOpacity(Editor* editor) const;
virtual int initialLayerOpacity(Editor* editor) const;
virtual tools::Tool* initialTool() const;
virtual void changeFgColor(Color c);
private: private:
void setZoom(Editor* editor, const render::Zoom& zoom, const gfx::Point& mousePos); void setZoom(Editor* editor, const render::Zoom& zoom, const gfx::Point& mousePos);
tools::Tool* getActiveTool(); tools::Tool* getActiveTool() const;
void disableQuickTool(); void disableQuickTool() const;
mutable doc::LayerList m_browsableLayers;
tools::Tool* m_groupTool;
}; };
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -12,6 +12,7 @@
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/ui/key_context.h" #include "app/ui/key_context.h"
#include "base/convert_to.h" #include "base/convert_to.h"
#include "base/vector2d.h"
#include "ui/accelerator.h" #include "ui/accelerator.h"
#include <memory> #include <memory>
@ -40,6 +41,7 @@ namespace app {
Quicktool, Quicktool,
Action, Action,
WheelAction, WheelAction,
DragAction,
}; };
// TODO This should be called "KeyActionModifier" or something similar // TODO This should be called "KeyActionModifier" or something similar
@ -101,6 +103,11 @@ namespace app {
return KeyAction(int(a) & int(b)); return KeyAction(int(a) & int(b));
} }
class Key;
using KeyPtr = std::shared_ptr<Key>;
using Keys = std::vector<KeyPtr>;
using DragVector = base::Vector2d<double>;
class Key { class Key {
public: public:
Key(Command* command, const Params& params, Key(Command* command, const Params& params,
@ -109,6 +116,7 @@ namespace app {
explicit Key(const KeyAction action, explicit Key(const KeyAction action,
const KeyContext keyContext); const KeyContext keyContext);
explicit Key(const WheelAction action); explicit Key(const WheelAction action);
static KeyPtr MakeDragAction(WheelAction dragAction);
KeyType type() const { return m_type; } KeyType type() const { return m_type; }
const ui::Accelerators& accels() const { const ui::Accelerators& accels() const {
@ -142,8 +150,11 @@ namespace app {
tools::Tool* tool() const { return m_tool; } tools::Tool* tool() const { return m_tool; }
// for KeyType::Action // for KeyType::Action
KeyAction action() const { return m_action; } KeyAction action() const { return m_action; }
// for KeyType::WheelAction // for KeyType::WheelAction / KeyType::DragAction
WheelAction wheelAction() const { return m_wheelAction; } WheelAction wheelAction() const { return m_wheelAction; }
// for KeyType::DragAction
DragVector dragVector() const { return m_dragVector; }
void setDragVector(const DragVector& v) { m_dragVector = v; }
std::string triggerString() const; std::string triggerString() const;
@ -158,16 +169,12 @@ namespace app {
// for KeyType::Command // for KeyType::Command
Command* m_command; Command* m_command;
Params m_params; Params m_params;
// for KeyType::Tool or Quicktool
tools::Tool* m_tool;
// for KeyType::Action
KeyAction m_action;
// for KeyType::WheelAction
WheelAction m_wheelAction;
};
typedef std::shared_ptr<Key> KeyPtr; tools::Tool* m_tool; // for KeyType::Tool or Quicktool
typedef std::vector<KeyPtr> Keys; KeyAction m_action; // for KeyType::Action
WheelAction m_wheelAction; // for KeyType::WheelAction / DragAction
DragVector m_dragVector; // for KeyType::DragAction
};
std::string convertKeyContextToUserFriendlyString(KeyContext keyContext); std::string convertKeyContextToUserFriendlyString(KeyContext keyContext);

View File

@ -22,9 +22,11 @@
#include "app/tools/ink.h" #include "app/tools/ink.h"
#include "app/tools/tool.h" #include "app/tools/tool.h"
#include "app/tools/tool_box.h" #include "app/tools/tool_box.h"
#include "app/ui/key.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/xml_document.h" #include "app/xml_document.h"
#include "app/xml_exception.h" #include "app/xml_exception.h"
#include "fmt/format.h"
#include "ui/accelerator.h" #include "ui/accelerator.h"
#include "ui/message.h" #include "ui/message.h"
@ -81,35 +83,37 @@ namespace {
{ NULL , app::KeyContext::Any } { NULL , app::KeyContext::Any }
}; };
using Vec = app::DragVector;
static struct { static struct {
const char* name; const char* name;
const char* userfriendly; const char* userfriendly;
app::WheelAction action; Vec vector;
} wheel_actions[] = { } wheel_actions[] = {
{ "Zoom" , "Zoom" , app::WheelAction::Zoom }, { "" , "" , Vec(0.0, 0.0) },
{ "VScroll" , "Scroll: Vertically" , app::WheelAction::VScroll }, { "Zoom" , "Zoom" , Vec(8.0, 0.0) },
{ "HScroll" , "Scroll: Horizontally" , app::WheelAction::HScroll }, { "VScroll" , "Scroll: Vertically" , Vec(4.0, 0.0) },
{ "FgColor" , "Color: Foreground Palette Entry" , app::WheelAction::FgColor }, { "HScroll" , "Scroll: Horizontally" , Vec(4.0, 0.0) },
{ "BgColor" , "Color: Background Palette Entry" , app::WheelAction::BgColor }, { "FgColor" , "Color: Foreground Palette Entry" , Vec(8.0, 0.0) },
{ "FgTile" , "Tile: Foreground Tile Entry" , app::WheelAction::FgTile }, { "BgColor" , "Color: Background Palette Entry" , Vec(8.0, 0.0) },
{ "BgTile" , "Tile: Background Tile Entry" , app::WheelAction::BgTile }, { "FgTile" , "Tile: Foreground Tile Entry" , Vec(8.0, 0.0) },
{ "Frame" , "Change Frame" , app::WheelAction::Frame }, { "BgTile" , "Tile: Background Tile Entry" , Vec(8.0, 0.0) },
{ "BrushSize" , "Change Brush Size" , app::WheelAction::BrushSize }, { "Frame" , "Change Frame" , Vec(16.0, 0.0) },
{ "BrushAngle" , "Change Brush Angle" , app::WheelAction::BrushAngle }, { "BrushSize" , "Change Brush Size" , Vec(4.0, 0.0) },
{ "ToolSameGroup" , "Change Tool (same group)" , app::WheelAction::ToolSameGroup }, { "BrushAngle" , "Change Brush Angle" , Vec(-4.0, 0.0) },
{ "ToolOtherGroup" , "Change Tool" , app::WheelAction::ToolOtherGroup }, { "ToolSameGroup" , "Change Tool (same group)" , Vec(8.0, 0.0) },
{ "Layer" , "Change Layer" , app::WheelAction::Layer }, { "ToolOtherGroup" , "Change Tool" , Vec(0.0, -8.0) },
{ "InkOpacity" , "Change Ink Opacity" , app::WheelAction::InkOpacity }, { "Layer" , "Change Layer" , Vec(0.0, 8.0) },
{ "LayerOpacity" , "Change Layer Opacity" , app::WheelAction::LayerOpacity }, { "InkOpacity" , "Change Ink Opacity" , Vec(0.0, 1.0) },
{ "CelOpacity" , "Change Cel Opacity" , app::WheelAction::CelOpacity }, { "LayerOpacity" , "Change Layer Opacity" , Vec(0.0, 1.0) },
{ "Alpha" , "Color: Alpha" , app::WheelAction::Alpha }, { "CelOpacity" , "Change Cel Opacity" , Vec(0.0, 1.0) },
{ "HslHue" , "Color: HSL Hue" , app::WheelAction::HslHue }, { "Alpha" , "Color: Alpha" , Vec(4.0, 0.0) },
{ "HslSaturation", "Color: HSL Saturation" , app::WheelAction::HslSaturation }, { "HslHue" , "Color: HSL Hue" , Vec(1.0, 0.0) },
{ "HslLightness" , "Color: HSL Lightness" , app::WheelAction::HslLightness }, { "HslSaturation", "Color: HSL Saturation" , Vec(4.0, 0.0) },
{ "HsvHue" , "Color: HSV Hue" , app::WheelAction::HsvHue }, { "HslLightness" , "Color: HSL Lightness" , Vec(0.0, 4.0) },
{ "HsvSaturation", "Color: HSV Saturation" , app::WheelAction::HsvSaturation }, { "HsvHue" , "Color: HSV Hue" , Vec(1.0, 0.0) },
{ "HsvValue" , "Color: HSV Value" , app::WheelAction::HsvValue }, { "HsvSaturation", "Color: HSV Saturation" , Vec(4.0, 0.0) },
{ nullptr , nullptr , app::WheelAction::None } { "HsvValue" , "Color: HSV Value" , Vec(0.0, 4.0) },
}; };
const char* get_shortcut(TiXmlElement* elem) { const char* get_shortcut(TiXmlElement* elem) {
@ -140,11 +144,13 @@ namespace {
} }
std::string get_user_friendly_string_for_wheelaction(app::WheelAction wheelAction) { std::string get_user_friendly_string_for_wheelaction(app::WheelAction wheelAction) {
for (int c=0; wheel_actions[c].name; ++c) { int c = int(wheelAction);
if (wheelAction == wheel_actions[c].action) if (c >= int(app::WheelAction::First) &&
return wheel_actions[c].userfriendly; c <= int(app::WheelAction::Last)) {
return wheel_actions[c].userfriendly;
} }
return std::string(); else
return std::string();
} }
} // anonymous namespace } // anonymous namespace
@ -169,20 +175,22 @@ namespace base {
} }
template<> app::WheelAction convert_to(const std::string& from) { template<> app::WheelAction convert_to(const std::string& from) {
app::WheelAction action = app::WheelAction::None; for (int c=int(app::WheelAction::First);
for (int c=0; wheel_actions[c].name; ++c) { c<=int(app::WheelAction::Last); ++c) {
if (from == wheel_actions[c].name) if (from == wheel_actions[c].name)
return wheel_actions[c].action; return (app::WheelAction)c;
} }
return action; return app::WheelAction::None;
} }
template<> std::string convert_to(const app::WheelAction& from) { template<> std::string convert_to(const app::WheelAction& from) {
for (int c=0; wheel_actions[c].name; ++c) { int c = int(from);
if (from == wheel_actions[c].action) if (c >= int(app::WheelAction::First) &&
return wheel_actions[c].name; c <= int(app::WheelAction::Last)) {
return wheel_actions[c].name;
} }
return ""; else
return std::string();
} }
template<> app::KeyContext convert_to(const std::string& from) { template<> app::KeyContext convert_to(const std::string& from) {
@ -291,6 +299,16 @@ Key::Key(const WheelAction wheelAction)
{ {
} }
// static
KeyPtr Key::MakeDragAction(WheelAction dragAction)
{
KeyPtr k(new Key(dragAction));
k->m_type = KeyType::DragAction;
k->m_keycontext = KeyContext::Any;
k->m_dragVector = wheel_actions[(int)dragAction].vector;
return k;
}
void Key::add(const ui::Accelerator& accel, void Key::add(const ui::Accelerator& accel,
const KeySource source, const KeySource source,
KeyboardShortcuts& globalKeys) KeyboardShortcuts& globalKeys)
@ -411,6 +429,7 @@ std::string Key::triggerString() const
return get_user_friendly_string_for_keyaction(m_action, return get_user_friendly_string_for_keyaction(m_action,
m_keycontext); m_keycontext);
case KeyType::WheelAction: case KeyType::WheelAction:
case KeyType::DragAction:
return get_user_friendly_string_for_wheelaction(m_wheelAction); return get_user_friendly_string_for_wheelaction(m_wheelAction);
} }
return "Unknown"; return "Unknown";
@ -633,6 +652,45 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
} }
xmlKey = xmlKey->NextSiblingElement(); xmlKey = xmlKey->NextSiblingElement();
} }
// Load special keyboard shortcuts to simulate mouse wheel actions
// <keyboard><drag><key>
xmlKey = handle
.FirstChild("drag")
.FirstChild("key").ToElement();
while (xmlKey) {
const char* action_id = xmlKey->Attribute("action");
const char* action_key = get_shortcut(xmlKey);
bool removed = bool_attr(xmlKey, "removed", false);
if (action_id) {
WheelAction action = base::convert_to<WheelAction, std::string>(action_id);
if (action != WheelAction::None) {
KeyPtr key = this->dragAction(action);
if (key && action_key) {
if (auto vector_str = xmlKey->Attribute("vector")) {
double x, y = 0.0;
// Parse a string like "double,double"
x = std::strtod(vector_str, (char**)&vector_str);
if (vector_str && *vector_str == ',') {
++vector_str;
y = std::strtod(vector_str, nullptr);
}
key->setDragVector(DragVector(x, y));
}
LOG(VERBOSE, "KEYS: Shortcut for drag action %s: %s\n", action_id, action_key);
Accelerator accel(action_key);
if (!removed)
key->add(accel, source, *this);
else
key->disableAccel(accel);
}
}
}
xmlKey = xmlKey->NextSiblingElement();
}
} }
void KeyboardShortcuts::importFile(const std::string& filename, KeySource source) void KeyboardShortcuts::importFile(const std::string& filename, KeySource source)
@ -654,6 +712,7 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
TiXmlElement quicktools("quicktools"); TiXmlElement quicktools("quicktools");
TiXmlElement actions("actions"); TiXmlElement actions("actions");
TiXmlElement wheel("wheel"); TiXmlElement wheel("wheel");
TiXmlElement drag("drag");
keyboard.SetAttribute("version", XML_KEYBOARD_FILE_VERSION); keyboard.SetAttribute("version", XML_KEYBOARD_FILE_VERSION);
@ -662,12 +721,14 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
exportKeys(quicktools, KeyType::Quicktool); exportKeys(quicktools, KeyType::Quicktool);
exportKeys(actions, KeyType::Action); exportKeys(actions, KeyType::Action);
exportKeys(wheel, KeyType::WheelAction); exportKeys(wheel, KeyType::WheelAction);
exportKeys(drag, KeyType::DragAction);
keyboard.InsertEndChild(commands); keyboard.InsertEndChild(commands);
keyboard.InsertEndChild(tools); keyboard.InsertEndChild(tools);
keyboard.InsertEndChild(quicktools); keyboard.InsertEndChild(quicktools);
keyboard.InsertEndChild(actions); keyboard.InsertEndChild(actions);
keyboard.InsertEndChild(wheel); keyboard.InsertEndChild(wheel);
keyboard.InsertEndChild(drag);
TiXmlDeclaration declaration("1.0", "utf-8", ""); TiXmlDeclaration declaration("1.0", "utf-8", "");
doc->InsertEndChild(declaration); doc->InsertEndChild(declaration);
@ -731,7 +792,16 @@ void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const
case KeyType::WheelAction: case KeyType::WheelAction:
elem.SetAttribute("action", elem.SetAttribute("action",
base::convert_to<std::string>(key->wheelAction()).c_str()); base::convert_to<std::string>(key->wheelAction()));
break;
case KeyType::DragAction:
elem.SetAttribute("action",
base::convert_to<std::string>(key->wheelAction()));
elem.SetAttribute("vector",
fmt::format("{},{}",
key->dragVector().x,
key->dragVector().y));
break; break;
} }
@ -827,6 +897,20 @@ KeyPtr KeyboardShortcuts::wheelAction(WheelAction wheelAction)
return key; return key;
} }
KeyPtr KeyboardShortcuts::dragAction(WheelAction dragAction)
{
for (KeyPtr& key : m_keys) {
if (key->type() == KeyType::DragAction &&
key->wheelAction() == dragAction) {
return key;
}
}
KeyPtr key = Key::MakeDragAction(dragAction);
m_keys.push_back(key);
return key;
}
void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel, void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
const KeyContext keyContext, const KeyContext keyContext,
const Key* newKey) const Key* newKey)
@ -837,7 +921,12 @@ void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
// Tools can contain the same keyboard shortcut // Tools can contain the same keyboard shortcut
(key->type() != KeyType::Tool || (key->type() != KeyType::Tool ||
newKey == nullptr || newKey == nullptr ||
newKey->type() != KeyType::Tool)) { newKey->type() != KeyType::Tool) &&
// DragActions can share the same keyboard shortcut (e.g. to
// change different values using different DragVectors)
(key->type() != KeyType::DragAction ||
newKey == nullptr ||
newKey->type() != KeyType::DragAction)) {
key->disableAccel(accel); key->disableAccel(accel);
} }
} }
@ -921,7 +1010,6 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
{ {
WheelAction wheelAction = WheelAction::None; WheelAction wheelAction = WheelAction::None;
const ui::Accelerator* bestAccel = nullptr; const ui::Accelerator* bestAccel = nullptr;
KeyPtr bestKey;
for (const KeyPtr& key : m_keys) { for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::WheelAction && if (key->type() == KeyType::WheelAction &&
key->keycontext() == context) { key->keycontext() == context) {
@ -936,6 +1024,22 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
return wheelAction; return wheelAction;
} }
Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const KeyContext context,
const ui::Message* msg)
{
KeyPtr bestKey = nullptr;
Keys keys;
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::DragAction) {
const ui::Accelerator* accel = key->isPressed(msg, *this);
if (accel) {
keys.push_back(key);
}
}
}
return keys;
}
bool KeyboardShortcuts::hasMouseWheelCustomization() const bool KeyboardShortcuts::hasMouseWheelCustomization() const
{ {
for (const KeyPtr& key : m_keys) { for (const KeyPtr& key : m_keys) {
@ -958,15 +1062,31 @@ void KeyboardShortcuts::clearMouseWheelKeys()
void KeyboardShortcuts::addMissingMouseWheelKeys() void KeyboardShortcuts::addMissingMouseWheelKeys()
{ {
for (int wheelAction=int(WheelAction::First); for (int action=int(WheelAction::First);
wheelAction<=int(WheelAction::Last); ++wheelAction) { action<=int(WheelAction::Last); ++action) {
// Wheel actions
auto it = std::find_if( auto it = std::find_if(
m_keys.begin(), m_keys.end(), m_keys.begin(), m_keys.end(),
[wheelAction](const KeyPtr& key) -> bool { [action](const KeyPtr& key) -> bool {
return key->wheelAction() == (WheelAction)wheelAction; return
key->type() == KeyType::WheelAction &&
key->wheelAction() == (WheelAction)action;
}); });
if (it == m_keys.end()) { if (it == m_keys.end()) {
KeyPtr key = std::make_shared<Key>((WheelAction)wheelAction); KeyPtr key = std::make_shared<Key>((WheelAction)action);
m_keys.push_back(key);
}
// Drag actions
it = std::find_if(
m_keys.begin(), m_keys.end(),
[action](const KeyPtr& key) -> bool {
return
key->type() == KeyType::DragAction &&
key->wheelAction() == (WheelAction)action;
});
if (it == m_keys.end()) {
KeyPtr key = Key::MakeDragAction((WheelAction)action);
m_keys.push_back(key); m_keys.push_back(key);
} }
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -51,6 +51,7 @@ namespace app {
KeyPtr action(const KeyAction action, KeyPtr action(const KeyAction action,
const KeyContext keyContext = KeyContext::Any); const KeyContext keyContext = KeyContext::Any);
KeyPtr wheelAction(const WheelAction action); KeyPtr wheelAction(const WheelAction action);
KeyPtr dragAction(const WheelAction action);
void disableAccel(const ui::Accelerator& accel, void disableAccel(const ui::Accelerator& accel,
const KeyContext keyContext, const KeyContext keyContext,
@ -62,6 +63,8 @@ namespace app {
KeyAction getCurrentActionModifiers(KeyContext context); KeyAction getCurrentActionModifiers(KeyContext context);
WheelAction getWheelActionFromMouseMessage(const KeyContext context, WheelAction getWheelActionFromMouseMessage(const KeyContext context,
const ui::Message* msg); const ui::Message* msg);
Keys getDragActionsFromKeyMessage(const KeyContext context,
const ui::Message* msg);
bool hasMouseWheelCustomization() const; bool hasMouseWheelCustomization() const;
void clearMouseWheelKeys(); void clearMouseWheelKeys();
void addMissingMouseWheelKeys(); void addMissingMouseWheelKeys();