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"?>
<!-- Aseprite -->
<!-- Copyright (C) 2018-2021 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<!-- Keyboard shortcuts -->
@ -639,6 +639,10 @@
<key action="RightMouseButton" />
</actions>
<drag>
<key action="BrushSize" vector="4.0,0.0" shortcut="Ctrl+Alt" />
</drag>
</keyboard>
<menus>

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2021 Igara Studio S.A.
# Copyright (C) 2018-2022 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello
[advanced_mode]
@ -846,9 +846,14 @@ section_commands = Commands
section_tools = Tools
section_action_modifiers = Action Modifiers
section_mouse_wheel = Mouse Wheel
section_drag_value = Drag Value
default_wheel_behavior = Default
custom_wheel_behavior = Custom
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
cancel = &Cancel

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2022 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 David Capello -->
<gui>
<window id="keyboard_shortcuts" text="@keyboard_shortcuts.title">
@ -15,6 +15,7 @@
<listitem text="@.section_tools" />
<listitem text="@.section_action_modifiers" />
<listitem text="@.section_mouse_wheel" />
<listitem text="@.section_drag_value" />
</listbox>
</view>
<separator horizontal="true" />
@ -53,6 +54,52 @@
<listbox id="wheel_actions" />
</view>
</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>
</splitter>
<hbox>

View File

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

View File

@ -27,6 +27,7 @@
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/fs.h"
#include "base/pi.h"
#include "base/scoped_value.h"
#include "base/split_string.h"
#include "base/string.h"
@ -506,14 +507,19 @@ private:
};
class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {
// TODO Merge with CanvasSizeWindow::Dir
enum class Dir { NW, N, NE, W, C, E, SW, S, SE };
public:
KeyboardShortcutsWindow(app::KeyboardShortcuts& keys,
MenuKeys& menuKeys,
const std::string& searchText)
const std::string& searchText,
int& curSection)
: m_keys(keys)
, m_menuKeys(menuKeys)
, m_searchChange(false)
, m_wasDefault(false) {
, m_wasDefault(false)
, m_curSection(curSection) {
setAutoRemap(false);
m_listBoxes.push_back(menus());
@ -521,6 +527,7 @@ public:
m_listBoxes.push_back(tools());
m_listBoxes.push_back(actions());
m_listBoxes.push_back(wheelActions());
m_listBoxes.push_back(dragActions());
#ifdef __APPLE__ // Zoom sliding two fingers option only on macOS
slideZoom()->setVisible(true);
@ -544,6 +551,9 @@ public:
search()->Change.connect([this]{ onSearchChange(); });
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(); });
exportButton()->Click.connect([this]{ onExport(); });
resetButton()->Click.connect([this]{ onReset(); });
@ -573,6 +583,7 @@ private:
deleteList(tools());
deleteList(actions());
deleteList(wheelActions());
deleteList(dragActions());
}
void fillAllLists() {
@ -592,11 +603,13 @@ private:
fillToolsList(tools(), App::instance()->toolBox());
fillWheelActionsList();
fillDragActionsList();
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::Tool ||
key->type() == KeyType::Quicktool ||
key->type() == KeyType::WheelAction) {
key->type() == KeyType::WheelAction ||
key->type() == KeyType::DragAction) {
continue;
}
@ -636,7 +649,7 @@ private:
tools()->sortItems();
actions()->sortItems();
section()->selectIndex(0);
section()->selectIndex(m_curSection);
updateViews();
}
@ -730,6 +743,19 @@ private:
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() {
const bool isDefault = isDefaultWheelBehavior();
if (isDefault)
@ -741,7 +767,7 @@ private:
std::string searchText = search()->text();
if (searchText.empty())
section()->selectIndex(0);
section()->selectIndex(m_curSection);
else {
fillSearchList(searchText);
section()->selectChild(nullptr);
@ -758,14 +784,48 @@ private:
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() {
int s = section()->getSelectedIndex();
if (s >= 0)
m_curSection = s;
searchView()->setVisible(s < 0);
menusView()->setVisible(s == 0);
commandsView()->setVisible(s == 1);
toolsView()->setVisible(s == 2);
actionsView()->setVisible(s == 3);
wheelSection()->setVisible(s == 4);
dragSection()->setVisible(s == 5);
if (m_headerItem.parent())
m_headerItem.parent()->removeChild(&m_headerItem);
@ -868,12 +928,51 @@ private:
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;
MenuKeys& m_menuKeys;
std::vector<ListBox*> m_listBoxes;
bool m_searchChange;
bool m_wasDefault;
HeaderItem m_headerItem;
int& m_curSection;
};
} // anonymous namespace
@ -905,6 +1004,8 @@ void KeyboardShortcutsCommand::onLoadParams(const Params& params)
void KeyboardShortcutsCommand::onExecute(Context* context)
{
static int curSection = 0;
app::KeyboardShortcuts* globalKeys = app::KeyboardShortcuts::instance();
app::KeyboardShortcuts keys;
keys.setKeys(*globalKeys, true);
@ -919,7 +1020,7 @@ void KeyboardShortcutsCommand::onExecute(Context* context)
// KeyboardShortcutsCommand instance (so m_search will be "")
// TODO Seeing this, we need a complete new way to handle UI commands execution
std::string neededSearchCopy = m_search;
KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy);
KeyboardShortcutsWindow window(keys, menuKeys, neededSearchCopy, curSection);
ui::Display* mainDisplay = Manager::getDefault()->display();
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/ui/app_menuitem.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/editor.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);
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()) {
if (resizeCelBounds(editor).contains(mouseScreenPos))
editor->showMouseCursor(kSizeSECursor);
@ -498,16 +485,7 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
}
}
// Draw
if (editor->canDraw()) {
editor->showBrushPreview(mouseScreenPos);
}
// Forbidden
else {
editor->showMouseCursor(kForbiddenCursor);
}
return true;
return StateWithWheelBehavior::onSetCursor(editor, mouseScreenPos);
}
bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
@ -515,6 +493,15 @@ bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
if (Preferences::instance().editor.straightLinePreview() &&
checkStartDrawingStraightLine(editor, nullptr, nullptr))
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;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -33,10 +33,41 @@
#include "ui/system.h"
#include "ui/theme.h"
#include "app/tools/ink.h"
#include "app/ui/skin/skin_theme.h"
namespace app {
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)
{
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) {
case WheelAction::None:
@ -117,15 +167,15 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::FgColor: {
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);
ColorBar::instance()->setFgColor(app::Color::fromIndex(newIndex));
changeFgColor(app::Color::fromIndex(newIndex));
break;
}
case WheelAction::BgColor: {
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);
ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex));
break;
@ -134,7 +184,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::FgTile: {
auto tilesView = ColorBar::instance()->getTilesView();
int lastIndex = tilesView->tileset()->size()-1;
int newIndex = ColorBar::instance()->getFgTile() + int(dz);
int newIndex = initialFgTileIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setFgTile(newIndex);
break;
@ -143,29 +193,39 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::BgTile: {
auto tilesView = ColorBar::instance()->getTilesView();
int lastIndex = tilesView->tileset()->size()-1;
int newIndex = ColorBar::instance()->getBgTile() + int(dz);
int newIndex = initialBgTileIndex() + int(dz);
newIndex = base::clamp(newIndex, 0, lastIndex);
ColorBar::instance()->setBgTile(newIndex);
break;
}
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)
command = Commands::instance()->byId(CommandId::GotoNextFrame());
else if (dz > 0.0)
command = Commands::instance()->byId(CommandId::GotoPreviousFrame());
frame_t frame = initialFrame(editor) + deltaFrame;
frame_t nframes = editor->sprite()->totalFrames();
while (frame < 0)
frame += nframes;
while (frame >= nframes)
frame -= nframes;
if (command)
UIContext::instance()->executeCommand(command);
editor->setFrame(frame);
break;
}
case WheelAction::Zoom: {
render::Zoom zoom = editor->zoom();
render::Zoom zoom = initialZoom(editor);
if (msg->preciseWheel()) {
if (preciseWheel) {
dz /= 1.5;
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));
setZoom(editor, zoom, msg->position());
setZoom(editor, zoom, position);
break;
}
case WheelAction::HScroll:
case WheelAction::VScroll: {
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();
if (wheelAction == WheelAction::HScroll) {
@ -211,7 +271,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
brush.size(
base::clamp(
int(brush.size()+dz),
int(initialBrushSize()+dz),
// If we use the "static const int" member directly here,
// we'll get a linker error (when compiling without
// optimizations) because we should need to define the
@ -226,7 +286,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
ToolPreferences::Brush& brush =
Preferences::instance().tool(tool).brush;
int angle = brush.angle()+dz;
int angle = initialBrushAngle()+dz;
while (angle < 0)
angle += 180;
angle %= 181;
@ -236,7 +296,7 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
}
case WheelAction::ToolSameGroup: {
tools::Tool* tool = getActiveTool();
const tools::Tool* tool = m_groupTool;
auto toolBox = App::instance()->toolBox();
std::vector<tools::Tool*> tools;
@ -249,12 +309,13 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
auto end = tools.end();
auto it = std::find(begin, end, tool);
if (it != end) {
if (dz < 0) {
int i = std::round(dz);
while (i++ < 0) {
if (it == begin)
it = end;
--it;
}
else {
while (i-- > 0) {
++it;
if (it == end)
it = begin;
@ -266,43 +327,60 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
}
case WheelAction::ToolOtherGroup: {
tools::Tool* tool = getActiveTool();
tools::Tool* tool = initialTool();
auto toolBox = App::instance()->toolBox();
auto begin = toolBox->begin_group();
auto end = toolBox->end_group();
auto it = std::find(begin, end, tool->getGroup());
if (it != end) {
if (dz < 0) {
int i = std::round(dz);
while (i++ < 0) {
if (it == begin)
it = end;
--it;
}
else {
while (i-- > 0) {
++it;
if (it == end)
it = begin;
}
ToolBar::instance()->selectToolGroup(*it);
m_groupTool = getActiveTool();
}
break;
}
case WheelAction::Layer: {
Command* command = nullptr;
if (dz < 0.0)
command = Commands::instance()->byId(CommandId::GotoNextLayer());
else if (dz > 0.0)
command = Commands::instance()->byId(CommandId::GotoPreviousLayer());
if (command)
UIContext::instance()->executeCommand(command);
int deltaLayer = 0;
if (preciseWheel) {
if (dz < 0.0)
deltaLayer = +1;
else if (dz > 0.0)
deltaLayer = -1;
}
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;
}
case WheelAction::InkOpacity: {
int opacity = initialInkOpacity(editor);
adjust_value(preciseWheel, dz, opacity, 0, 255);
tools::Tool* tool = getActiveTool();
auto& toolPref = Preferences::instance().tool(tool);
int opacity = toolPref.opacity();
opacity = base::clamp(int(opacity+dz*255/10), 0, 255);
toolPref.opacity(opacity);
break;
}
@ -314,8 +392,8 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
site.layer()->isEditable()) {
Command* command = Commands::instance()->byId(CommandId::LayerOpacity());
if (command) {
int opacity = static_cast<doc::LayerImage*>(site.layer())->opacity();
opacity = base::clamp(int(opacity+dz*255/10), 0, 255);
int opacity = initialLayerOpacity(editor);
adjust_value(preciseWheel, dz, opacity, 0, 255);
Params params;
params.set("opacity",
@ -334,8 +412,9 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
site.cel()) {
Command* command = Commands::instance()->byId(CommandId::CelOpacity());
if (command) {
int opacity = site.cel()->opacity();
opacity = base::clamp(int(opacity+dz*255/10), 0, 255);
int opacity = initialCelOpacity(editor);
adjust_value(preciseWheel, dz, opacity, 0, 255);
Params params;
params.set("opacity",
base::convert_to<std::string>(opacity).c_str());
@ -348,12 +427,12 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::Alpha: {
disableQuickTool();
ColorBar* colorBar = ColorBar::instance();
Color c = colorBar->getFgColor();
Color c = initialFgColor();
int a = c.getAlpha();
a = base::clamp(int(a+dz*255/10), 0, 255);
adjust_value(preciseWheel, dz, a, 0, 255);
c.setAlpha(a);
colorBar->setFgColor(c);
changeFgColor(c);
break;
}
@ -362,20 +441,26 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::HslLightness: {
disableQuickTool();
ColorBar* colorBar = ColorBar::instance();
Color c = colorBar->getFgColor();
Color c = initialFgColor();
double
h = c.getHslHue(),
s = c.getHslSaturation(),
l = c.getHslLightness();
switch (wheelAction) {
case WheelAction::HslHue: h = h+dz*10.0; break;
case WheelAction::HslSaturation: s = s+dz/10.0; break;
case WheelAction::HslLightness: l = l+dz/10.0; break;
case WheelAction::HslHue:
adjust_hue(preciseWheel, dz, h, 0.0, 360.0);
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),
base::clamp(l, 0.0, 1.0)));
changeFgColor(Color::fromHsl(base::clamp(h, 0.0, 360.0),
base::clamp(s, 0.0, 1.0),
base::clamp(l, 0.0, 1.0)));
break;
}
@ -384,26 +469,30 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WheelAction::HsvValue: {
disableQuickTool();
ColorBar* colorBar = ColorBar::instance();
Color c = colorBar->getFgColor();
Color c = initialFgColor();
double
h = c.getHsvHue(),
s = c.getHsvSaturation(),
v = c.getHsvValue();
switch (wheelAction) {
case WheelAction::HsvHue: h = h+dz*10.0; break;
case WheelAction::HsvSaturation: s = s+dz/10.0; break;
case WheelAction::HsvValue: v = v+dz/10.0; break;
case WheelAction::HsvHue:
adjust_hue(preciseWheel, dz, h, 0.0, 360.0);
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),
base::clamp(v, 0.0, 1.0)));
changeFgColor(Color::fromHsv(base::clamp(h, 0.0, 360.0),
base::clamp(s, 0.0, 1.0),
base::clamp(v, 0.0, 1.0)));
break;
}
}
return true;
}
bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* msg)
@ -416,6 +505,49 @@ bool StateWithWheelBehavior::onTouchMagnify(Editor* editor, ui::TouchMessage* ms
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,
const render::Zoom& zoom,
const gfx::Point& mousePos)
@ -428,13 +560,120 @@ void StateWithWheelBehavior::setZoom(Editor* editor,
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();
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();
if (atm->quickTool()) {

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -8,7 +9,12 @@
#define APP_UI_STATE_WITH_WHEEL_BEHAVIOR_H_INCLUDED
#pragma once
#include "app/color.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 {
class Zoom;
@ -22,12 +28,45 @@ namespace app {
class StateWithWheelBehavior : public EditorState {
public:
virtual bool onMouseWheel(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onTouchMagnify(Editor* editor, ui::TouchMessage* msg) override;
StateWithWheelBehavior();
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:
void setZoom(Editor* editor, const render::Zoom& zoom, const gfx::Point& mousePos);
tools::Tool* getActiveTool();
void disableQuickTool();
tools::Tool* getActiveTool() const;
void disableQuickTool() const;
mutable doc::LayerList m_browsableLayers;
tools::Tool* m_groupTool;
};
} // namespace app

View File

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

View File

@ -22,9 +22,11 @@
#include "app/tools/ink.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/ui/key.h"
#include "app/ui_context.h"
#include "app/xml_document.h"
#include "app/xml_exception.h"
#include "fmt/format.h"
#include "ui/accelerator.h"
#include "ui/message.h"
@ -81,35 +83,37 @@ namespace {
{ NULL , app::KeyContext::Any }
};
using Vec = app::DragVector;
static struct {
const char* name;
const char* userfriendly;
app::WheelAction action;
Vec vector;
} wheel_actions[] = {
{ "Zoom" , "Zoom" , app::WheelAction::Zoom },
{ "VScroll" , "Scroll: Vertically" , app::WheelAction::VScroll },
{ "HScroll" , "Scroll: Horizontally" , app::WheelAction::HScroll },
{ "FgColor" , "Color: Foreground Palette Entry" , app::WheelAction::FgColor },
{ "BgColor" , "Color: Background Palette Entry" , app::WheelAction::BgColor },
{ "FgTile" , "Tile: Foreground Tile Entry" , app::WheelAction::FgTile },
{ "BgTile" , "Tile: Background Tile Entry" , app::WheelAction::BgTile },
{ "Frame" , "Change Frame" , app::WheelAction::Frame },
{ "BrushSize" , "Change Brush Size" , app::WheelAction::BrushSize },
{ "BrushAngle" , "Change Brush Angle" , app::WheelAction::BrushAngle },
{ "ToolSameGroup" , "Change Tool (same group)" , app::WheelAction::ToolSameGroup },
{ "ToolOtherGroup" , "Change Tool" , app::WheelAction::ToolOtherGroup },
{ "Layer" , "Change Layer" , app::WheelAction::Layer },
{ "InkOpacity" , "Change Ink Opacity" , app::WheelAction::InkOpacity },
{ "LayerOpacity" , "Change Layer Opacity" , app::WheelAction::LayerOpacity },
{ "CelOpacity" , "Change Cel Opacity" , app::WheelAction::CelOpacity },
{ "Alpha" , "Color: Alpha" , app::WheelAction::Alpha },
{ "HslHue" , "Color: HSL Hue" , app::WheelAction::HslHue },
{ "HslSaturation", "Color: HSL Saturation" , app::WheelAction::HslSaturation },
{ "HslLightness" , "Color: HSL Lightness" , app::WheelAction::HslLightness },
{ "HsvHue" , "Color: HSV Hue" , app::WheelAction::HsvHue },
{ "HsvSaturation", "Color: HSV Saturation" , app::WheelAction::HsvSaturation },
{ "HsvValue" , "Color: HSV Value" , app::WheelAction::HsvValue },
{ nullptr , nullptr , app::WheelAction::None }
{ "" , "" , Vec(0.0, 0.0) },
{ "Zoom" , "Zoom" , Vec(8.0, 0.0) },
{ "VScroll" , "Scroll: Vertically" , Vec(4.0, 0.0) },
{ "HScroll" , "Scroll: Horizontally" , Vec(4.0, 0.0) },
{ "FgColor" , "Color: Foreground Palette Entry" , Vec(8.0, 0.0) },
{ "BgColor" , "Color: Background Palette Entry" , Vec(8.0, 0.0) },
{ "FgTile" , "Tile: Foreground Tile Entry" , Vec(8.0, 0.0) },
{ "BgTile" , "Tile: Background Tile Entry" , Vec(8.0, 0.0) },
{ "Frame" , "Change Frame" , Vec(16.0, 0.0) },
{ "BrushSize" , "Change Brush Size" , Vec(4.0, 0.0) },
{ "BrushAngle" , "Change Brush Angle" , Vec(-4.0, 0.0) },
{ "ToolSameGroup" , "Change Tool (same group)" , Vec(8.0, 0.0) },
{ "ToolOtherGroup" , "Change Tool" , Vec(0.0, -8.0) },
{ "Layer" , "Change Layer" , Vec(0.0, 8.0) },
{ "InkOpacity" , "Change Ink Opacity" , Vec(0.0, 1.0) },
{ "LayerOpacity" , "Change Layer Opacity" , Vec(0.0, 1.0) },
{ "CelOpacity" , "Change Cel Opacity" , Vec(0.0, 1.0) },
{ "Alpha" , "Color: Alpha" , Vec(4.0, 0.0) },
{ "HslHue" , "Color: HSL Hue" , Vec(1.0, 0.0) },
{ "HslSaturation", "Color: HSL Saturation" , Vec(4.0, 0.0) },
{ "HslLightness" , "Color: HSL Lightness" , Vec(0.0, 4.0) },
{ "HsvHue" , "Color: HSV Hue" , Vec(1.0, 0.0) },
{ "HsvSaturation", "Color: HSV Saturation" , Vec(4.0, 0.0) },
{ "HsvValue" , "Color: HSV Value" , Vec(0.0, 4.0) },
};
const char* get_shortcut(TiXmlElement* elem) {
@ -140,11 +144,13 @@ namespace {
}
std::string get_user_friendly_string_for_wheelaction(app::WheelAction wheelAction) {
for (int c=0; wheel_actions[c].name; ++c) {
if (wheelAction == wheel_actions[c].action)
return wheel_actions[c].userfriendly;
int c = int(wheelAction);
if (c >= int(app::WheelAction::First) &&
c <= int(app::WheelAction::Last)) {
return wheel_actions[c].userfriendly;
}
return std::string();
else
return std::string();
}
} // anonymous namespace
@ -169,20 +175,22 @@ namespace base {
}
template<> app::WheelAction convert_to(const std::string& from) {
app::WheelAction action = app::WheelAction::None;
for (int c=0; wheel_actions[c].name; ++c) {
for (int c=int(app::WheelAction::First);
c<=int(app::WheelAction::Last); ++c) {
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) {
for (int c=0; wheel_actions[c].name; ++c) {
if (from == wheel_actions[c].action)
return wheel_actions[c].name;
int c = int(from);
if (c >= int(app::WheelAction::First) &&
c <= int(app::WheelAction::Last)) {
return wheel_actions[c].name;
}
return "";
else
return std::string();
}
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,
const KeySource source,
KeyboardShortcuts& globalKeys)
@ -411,6 +429,7 @@ std::string Key::triggerString() const
return get_user_friendly_string_for_keyaction(m_action,
m_keycontext);
case KeyType::WheelAction:
case KeyType::DragAction:
return get_user_friendly_string_for_wheelaction(m_wheelAction);
}
return "Unknown";
@ -633,6 +652,45 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
}
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)
@ -654,6 +712,7 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
TiXmlElement quicktools("quicktools");
TiXmlElement actions("actions");
TiXmlElement wheel("wheel");
TiXmlElement drag("drag");
keyboard.SetAttribute("version", XML_KEYBOARD_FILE_VERSION);
@ -662,12 +721,14 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
exportKeys(quicktools, KeyType::Quicktool);
exportKeys(actions, KeyType::Action);
exportKeys(wheel, KeyType::WheelAction);
exportKeys(drag, KeyType::DragAction);
keyboard.InsertEndChild(commands);
keyboard.InsertEndChild(tools);
keyboard.InsertEndChild(quicktools);
keyboard.InsertEndChild(actions);
keyboard.InsertEndChild(wheel);
keyboard.InsertEndChild(drag);
TiXmlDeclaration declaration("1.0", "utf-8", "");
doc->InsertEndChild(declaration);
@ -731,7 +792,16 @@ void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const
case KeyType::WheelAction:
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;
}
@ -827,6 +897,20 @@ KeyPtr KeyboardShortcuts::wheelAction(WheelAction wheelAction)
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,
const KeyContext keyContext,
const Key* newKey)
@ -837,7 +921,12 @@ void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
// Tools can contain the same keyboard shortcut
(key->type() != KeyType::Tool ||
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);
}
}
@ -921,7 +1010,6 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
{
WheelAction wheelAction = WheelAction::None;
const ui::Accelerator* bestAccel = nullptr;
KeyPtr bestKey;
for (const KeyPtr& key : m_keys) {
if (key->type() == KeyType::WheelAction &&
key->keycontext() == context) {
@ -936,6 +1024,22 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
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
{
for (const KeyPtr& key : m_keys) {
@ -958,15 +1062,31 @@ void KeyboardShortcuts::clearMouseWheelKeys()
void KeyboardShortcuts::addMissingMouseWheelKeys()
{
for (int wheelAction=int(WheelAction::First);
wheelAction<=int(WheelAction::Last); ++wheelAction) {
for (int action=int(WheelAction::First);
action<=int(WheelAction::Last); ++action) {
// Wheel actions
auto it = std::find_if(
m_keys.begin(), m_keys.end(),
[wheelAction](const KeyPtr& key) -> bool {
return key->wheelAction() == (WheelAction)wheelAction;
[action](const KeyPtr& key) -> bool {
return
key->type() == KeyType::WheelAction &&
key->wheelAction() == (WheelAction)action;
});
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);
}
}

View File

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