Added pen pressure sensitivity (fix #710)

- Added support to detect eraser tip on Linux (#610)
- Related to #139
- Still needs works for gradients and better brush interpolations
  between stroke points
- Requested several times, e.g. https://community.aseprite.org/t/1077
  https://community.aseprite.org/t/1881, steam forum, etc.
This commit is contained in:
David Capello 2020-04-21 22:27:49 -03:00
parent 5affdbbae1
commit 79f9e28ce8
26 changed files with 585 additions and 43 deletions

View File

@ -11,7 +11,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_UI=OFF
- XVFB=xvfb-run
@ -19,7 +19,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_SCRIPTING=OFF
- XVFB=xvfb-run
@ -27,7 +27,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_SCRIPTING=OFF
- ENABLE_UI=OFF
@ -37,7 +37,7 @@ matrix:
addons:
apt:
packages:
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- ENABLE_UI=ON
- XVFB=xvfb-run
@ -47,7 +47,7 @@ matrix:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev ninja-build
- g++-7 libpixman-1-dev libfreetype6-dev libharfbuzz-dev libx11-dev libxcursor-dev libxi-dev ninja-build
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
- ENABLE_UI=ON

View File

@ -72,11 +72,11 @@ versions might work).
You will need the following dependencies on Ubuntu/Debian:
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libgl1-mesa-dev libfontconfig1-dev
sudo apt-get install -y g++ cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
On Fedora:
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel mesa-libGL-devel fontconfig-devel
sudo dnf install -y gcc-c++ cmake ninja-build libX11-devel libXcursor-devel libXi-devel mesa-libGL-devel fontconfig-devel
# Compiling

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -418,6 +418,7 @@
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
<part id="outline_empty_pixel" x="208" y="224" w="5" h="5" />
<part id="outline_full_pixel" x="214" y="224" w="5" h="5" />
<part id="dynamics" x="176" y="144" w="16" h="16" />
</parts>
<styles>
<style id="box" />

View File

@ -422,6 +422,7 @@ SetSameInk = Same Ink in All Tools
ShowAutoGuides = Show Auto Guides
ShowBrushPreview = Show Brush Preview
ShowBrushes = Show Brushes
ShowDynamics = Show Dynamics
ShowExtras = Show Extras
ShowGrid = Show Grid
ShowLayerEdges = Show Layer Edges
@ -520,6 +521,28 @@ duplicate = Duplicate:
as = As:
merged_layers = Duplicate merged layers only
[dynamics]
pressure = Pressure
pressure_tooltip = Control parameters through the pen pressure sensor
velocity = Velocity
velocity_tooltip = Control parameters through the mouse velocity
size = Size:
size_tooltip = <<<END
Change the brush size
depending on the sensor value
END
angle = Angle:
angle_tooltip = <<<END
Change the brush angle
depending on the sensor value
END
gradient = Gradient:
gradient_tooltip = <<<END
Gradient between foreground
and background colors
END
max_point_value = Max Point Value:
[export_file]
title = Export File
output_file = Output File:

39
data/widgets/dynamics.xml Normal file
View File

@ -0,0 +1,39 @@
<!-- Aseprite -->
<!-- Copyright (C) 2020 Igara Studio S.A. -->
<gui>
<vbox id="dynamics">
<hbox>
<buttonset id="values" columns="3">
<item text="" />
<item text="@.pressure" tooltip="@.pressure_tooltip" tooltip_dir="bottom" />
<item text="@.velocity" tooltip="@.velocity_tooltip" tooltip_dir="bottom" />
<item text="@.size" tooltip="@.size_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
<item text="@.angle" tooltip="@.angle_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
<item text="@.gradient" tooltip="@.gradient_tooltip" tooltip_dir="right" />
<item text="" maxheight="1" />
<item text="" maxheight="1" />
</buttonset>
</hbox>
<separator id="separator" text="@.max_point_value" horizontal="true" />
<grid id="options" columns="2" childspacing="0" expansive="true">
<label id="max_size_label" text="@.size" style="mini_label" />
<slider id="max_size" value="64" min="1" max="64" cell_align="horizontal" />
<label id="max_angle_label" text="@.angle" style="mini_label" />
<slider id="max_angle" value="0" min="-180" max="+180" cell_align="horizontal" />
<label id="gradient_label" text="@.gradient" style="mini_label" />
<hbox id="gradient_placeholder" />
</grid>
</vbox>
</gui>

2
laf

@ -1 +1 @@
Subproject commit 55a3acb0bf6f30a50023dbdf04c2813423064702
Subproject commit 40c86d83db8eb4a96b0552535a9c7e4ddef2ab70

View File

@ -319,6 +319,7 @@ if(ENABLE_UI)
ui/dithering_selector.cpp
ui/doc_view.cpp
ui/drop_down_button.cpp
ui/dynamics_popup.cpp
ui/editor/brush_preview.cpp
ui/editor/drawing_state.cpp
ui/editor/editor.cpp

View File

@ -328,7 +328,12 @@ int App_useTool(lua_State* L)
while (lua_next(L, -2) != 0) {
gfx::Point pt = convert_args_into_point(L, -1);
tools::Pointer pointer(pt, tools::Pointer::Button::Left);
tools::Pointer pointer(
pt,
// TODO configurable params
tools::Pointer::Button::Left,
tools::Pointer::Type::Unknown,
0.0f);
if (first) {
first = false;
manager.prepareLoop(pointer);

42
src/app/tools/dynamics.h Normal file
View File

@ -0,0 +1,42 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_TOOLS_DYNAMICS_H_INCLUDED
#define APP_TOOLS_DYNAMICS_H_INCLUDED
#pragma once
#include "render/dithering_algorithm.h"
#include "render/dithering_matrix.h"
namespace app {
namespace tools {
enum class DynamicSensor {
Static,
Pressure,
Velocity,
};
struct DynamicsOptions {
DynamicSensor size = DynamicSensor::Static;
DynamicSensor angle = DynamicSensor::Static;
DynamicSensor gradient = DynamicSensor::Static;
int maxSize = 0;
int maxAngle = 0;
render::DitheringAlgorithm ditheringAlgorithm = render::DitheringAlgorithm::None;
render::DitheringMatrix ditheringMatrix;
bool isDynamic() const {
return (size != DynamicSensor::Static ||
angle != DynamicSensor::Static ||
gradient != DynamicSensor::Static);
}
};
} // namespace tools
} // namespace app
#endif

View File

@ -37,21 +37,26 @@ public:
};
class BrushPointShape : public PointShape {
Brush* m_brush;
Brush* m_lastBrush;
std::shared_ptr<CompressedImage> m_compressedImage;
bool m_firstPoint;
public:
void preparePointShape(ToolLoop* loop) override {
m_brush = loop->getBrush();
m_compressedImage.reset(new CompressedImage(m_brush->image(),
m_brush->maskBitmap(),
false));
m_firstPoint = true;
m_lastBrush = nullptr;
}
void transformPoint(ToolLoop* loop, int x, int y) override {
Brush* m_brush = loop->getBrush();
if (m_lastBrush != m_brush) {
m_lastBrush = m_brush;
m_compressedImage.reset(new CompressedImage(m_brush->image(),
m_brush->maskBitmap(),
false));
}
x += m_brush->bounds().x;
y += m_brush->bounds().y;
@ -91,7 +96,7 @@ public:
}
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
area = m_brush->bounds();
area = loop->getBrush()->bounds();
area.x += x;
area.y += y;
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016 David Capello
//
// This program is distributed under the terms of
@ -9,6 +10,7 @@
#pragma once
#include "gfx/point.h"
#include "ui/pointer_type.h"
namespace app {
namespace tools {
@ -17,19 +19,33 @@ namespace tools {
class Pointer {
public:
enum Button { None, Left, Middle, Right };
typedef ui::PointerType Type;
Pointer()
: m_point(0, 0), m_button(None) { }
: m_point(0, 0)
, m_button(None)
, m_type(Type::Unknown)
, m_pressure(0.0f) { }
Pointer(const gfx::Point& point, Button button)
: m_point(point), m_button(button) { }
Pointer(const gfx::Point& point,
const Button button,
const Type type,
const float pressure)
: m_point(point)
, m_button(button)
, m_type(type)
, m_pressure(pressure) { }
const gfx::Point& point() const { return m_point; }
Button button() const { return m_button; }
Type type() const { return m_type; }
float pressure() const { return m_pressure; }
private:
gfx::Point m_point;
Button m_button;
Type m_type;
float m_pressure;
};
} // namespace tools

View File

@ -9,8 +9,10 @@
#define APP_TOOLS_TOOL_LOOP_H_INCLUDED
#pragma once
#include "app/tools/dynamics.h"
#include "app/tools/tool_loop_modifiers.h"
#include "app/tools/trace_policy.h"
#include "doc/brush.h"
#include "doc/color.h"
#include "doc/frame.h"
#include "filters/tiled_mode.h"
@ -23,7 +25,6 @@ namespace gfx {
}
namespace doc {
class Brush;
class Image;
class Layer;
class Mask;
@ -72,6 +73,7 @@ namespace app {
// Returns the brush which will be used with the tool
virtual Brush* getBrush() = 0;
virtual void setBrush(const BrushRef& newBrush) = 0;
// Returns the document to which belongs the sprite.
virtual Doc* getDocument() = 0;
@ -235,6 +237,9 @@ namespace app {
virtual render::DitheringAlgorithmBase* getDitheringAlgorithm() = 0;
virtual render::GradientType getGradientType() = 0;
// For freehand algorithms with dynamics
virtual tools::DynamicsOptions getDynamics() = 0;
// Called when the user release the mouse on SliceInk
virtual void onSliceRect(const gfx::Rect& bounds) = 0;
};

View File

@ -19,6 +19,7 @@
#include "app/tools/point_shape.h"
#include "app/tools/symmetry.h"
#include "app/tools/tool_loop.h"
#include "base/clamp.h"
#include "doc/brush.h"
#include "doc/image.h"
#include "doc/primitives.h"
@ -40,6 +41,8 @@ using namespace filters;
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
: m_toolLoop(toolLoop)
, m_brush0(*toolLoop->getBrush())
, m_dynamics(toolLoop->getDynamics())
{
}
@ -154,8 +157,6 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
void ToolLoopManager::movement(const Pointer& pointer)
{
TOOL_TRACE("ToolLoopManager::movement", pointer.point());
m_lastPointer = pointer;
if (isCanceled())
@ -163,11 +164,16 @@ void ToolLoopManager::movement(const Pointer& pointer)
// Convert the screen point to a sprite point
Point spritePoint = pointer.point();
// Calculate the speed (new sprite point - old sprite point)
m_toolLoop->setSpeed(spritePoint - m_oldPoint);
// Calculate the velocity (new sprite point - old sprite point)
Point velocity = (spritePoint - m_oldPoint);
m_toolLoop->setSpeed(velocity);
m_oldPoint = spritePoint;
snapToGrid(spritePoint);
// Control dynamic parameters through sensors
if (m_dynamics.isDynamic())
adjustBrushWithDynamics(pointer, velocity);
m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
std::string statusText;
@ -373,5 +379,53 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
}
}
void ToolLoopManager::adjustBrushWithDynamics(const Pointer& pointer,
const Point& velocity)
{
int size = m_brush0.size();
int angle = m_brush0.angle();
// Pressure
bool hasP = (pointer.type() == Pointer::Type::Pen ||
pointer.type() == Pointer::Type::Eraser);
float p = (hasP ? pointer.pressure(): 1.0f);
ASSERT(p >= 0.0f && p <= 1.0f);
// Velocity
float v = float(std::sqrt(velocity.x*velocity.x +
velocity.y*velocity.y)) / 32.0f; // TODO 16 should be configurable
v = base::clamp(v, 0.0f, 1.0f);
switch (m_dynamics.size) {
case DynamicSensor::Pressure:
if (hasP) size = (1.0f-p)*size + p*m_dynamics.maxSize;
break;
case DynamicSensor::Velocity:
size = (1.0f-v)*size + v*m_dynamics.maxSize;
break;
}
switch (m_dynamics.angle) {
case DynamicSensor::Pressure:
if (hasP) angle = (1.0f-p)*angle + p*m_dynamics.maxAngle;
break;
case DynamicSensor::Velocity:
angle = (1.0f-v)*angle + v*m_dynamics.maxAngle;
break;
}
size = base::clamp(size, int(Brush::kMinBrushSize), int(Brush::kMaxBrushSize));
angle = base::clamp(angle, -180, 180);
Brush* currrentBrush = m_toolLoop->getBrush();
if (currrentBrush->size() != size ||
(currrentBrush->type() != kCircleBrushType &&
currrentBrush->angle() != angle)) {
m_toolLoop->setBrush(
std::make_shared<Brush>(m_brush0.type(), size, angle));
}
}
} // namespace tools
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -9,8 +9,10 @@
#define APP_TOOLS_TOOL_LOOP_MANAGER_H_INCLUDED
#pragma once
#include "app/tools/dynamics.h"
#include "app/tools/pointer.h"
#include "app/tools/stroke.h"
#include "doc/brush.h"
#include "gfx/point.h"
#include "gfx/region.h"
@ -72,6 +74,8 @@ public:
private:
void doLoopStep(bool lastStep);
void snapToGrid(gfx::Point& point);
void adjustBrushWithDynamics(const Pointer& pointer,
const gfx::Point& velocity);
void calculateDirtyArea(const Strokes& strokes);
@ -81,6 +85,8 @@ private:
gfx::Point m_oldPoint;
gfx::Region m_dirtyArea;
gfx::Region m_nextDirtyArea;
doc::Brush m_brush0;
DynamicsOptions m_dynamics;
};
} // namespace tools

View File

@ -39,6 +39,7 @@
#include "app/ui/color_button.h"
#include "app/ui/color_shades.h"
#include "app/ui/dithering_selector.h"
#include "app/ui/dynamics_popup.h"
#include "app/ui/editor/editor.h"
#include "app/ui/icon_button.h"
#include "app/ui/keyboard_shortcuts.h"
@ -972,6 +973,55 @@ private:
bool m_lockChange;
};
class ContextBar::DynamicsField : public ButtonSet
, public DynamicsPopup::Delegate {
public:
DynamicsField(ContextBar* ctxBar)
: ButtonSet(1)
, m_ctxBar(ctxBar) {
addItem(SkinTheme::instance()->parts.dynamics());
}
void showPopup() {
if (!m_popup) {
m_popup.reset(new DynamicsPopup(this));
m_popup->remapWindow();
}
const gfx::Rect bounds = this->bounds();
m_popup->positionWindow(bounds.x, bounds.y+bounds.h);
m_popup->setHotRegion(gfx::Region(m_popup->bounds()));
m_popup->openWindow();
}
tools::DynamicsOptions getDynamics() {
if (m_popup)
return m_popup->getDynamics();
else
return tools::DynamicsOptions();
}
private:
// DynamicsPopup::Delegate impl
doc::BrushRef getActiveBrush() override {
return m_ctxBar->activeBrush();
}
void onItemChange(Item* item) override {
ButtonSet::onItemChange(item);
showPopup();
}
void onInitTheme(InitThemeEvent& ev) override {
ButtonSet::onInitTheme(ev);
if (m_popup)
m_popup->initTheme();
}
std::unique_ptr<DynamicsPopup> m_popup;
ContextBar* m_ctxBar;
};
class ContextBar::FreehandAlgorithmField : public CheckBox {
public:
FreehandAlgorithmField() : CheckBox("Pixel-perfect") {
@ -1500,6 +1550,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
addChild(m_selectBoxHelp = new Label(""));
addChild(m_freehandBox = new HBox());
m_freehandBox->addChild(m_dynamics = new DynamicsField(this));
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
addChild(m_symmetry = new SymmetryField());
@ -2148,6 +2199,11 @@ render::GradientType ContextBar::gradientType()
return m_gradientType->gradientType();
}
tools::DynamicsOptions ContextBar::getDynamics()
{
return m_dynamics->getDynamics();
}
void ContextBar::setupTooltips(TooltipManager* tooltipManager)
{
tooltipManager->addTooltipFor(m_brushBack->at(0), "Discard Brush (Esc)", BOTTOM);
@ -2161,6 +2217,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager)
tooltipManager->addTooltipFor(m_spraySpeed, "Spray Speed", BOTTOM);
tooltipManager->addTooltipFor(m_pivot->at(0), "Rotation Pivot", BOTTOM);
tooltipManager->addTooltipFor(m_rotAlgo, "Rotation Algorithm", BOTTOM);
tooltipManager->addTooltipFor(m_dynamics->at(0), "Dynamics", BOTTOM);
tooltipManager->addTooltipFor(m_freehandAlgo,
key_tooltip("Freehand trace algorithm",
CommandId::PixelPerfectMode()), BOTTOM);
@ -2184,11 +2241,24 @@ void ContextBar::registerCommands()
new QuickCommand(
CommandId::ShowBrushes(),
[this]{ this->showBrushes(); }));
Commands::instance()
->add(
new QuickCommand(
CommandId::ShowDynamics(),
[this]{ this->showDynamics(); }));
}
void ContextBar::showBrushes()
{
m_brushType->showPopup();
if (m_brushType->isVisible())
m_brushType->showPopup();
}
void ContextBar::showDynamics()
{
if (m_dynamics->isVisible())
m_dynamics->showPopup();
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -12,6 +12,7 @@
#include "app/pref/preferences.h"
#include "app/shade.h"
#include "app/tools/active_tool_observer.h"
#include "app/tools/dynamics.h"
#include "app/tools/ink_type.h"
#include "app/tools/tool_loop_modifiers.h"
#include "app/ui/context_bar_observer.h"
@ -90,6 +91,9 @@ namespace app {
render::DitheringAlgorithmBase* ditheringAlgorithm();
render::GradientType gradientType();
// For freehand with dynamics
tools::DynamicsOptions getDynamics();
// Signals
obs::signal<void()> BrushChange;
@ -124,6 +128,7 @@ namespace app {
void setupTooltips(ui::TooltipManager* tooltipManager);
void registerCommands();
void showBrushes();
void showDynamics();
class ZoomButtons;
class BrushBackField;
@ -143,6 +148,7 @@ namespace app {
class TransparentColorField;
class PivotField;
class RotAlgorithmField;
class DynamicsField;
class FreehandAlgorithmField;
class BrushPatternField;
class EyedropperField;
@ -167,6 +173,7 @@ namespace app {
EyedropperField* m_eyedropperField;
AutoSelectLayerField* m_autoSelectLayer;
ui::Box* m_freehandBox;
DynamicsField* m_dynamics;
FreehandAlgorithmField* m_freehandAlgo;
BrushPatternField* m_brushPatternField;
ui::Box* m_sprayBox;

View File

@ -0,0 +1,185 @@
// Aseprite
// Copyright (C) 2020 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/dynamics_popup.h"
#include "app/ui/dithering_selector.h"
#include "app/ui/skin/skin_theme.h"
#include "base/clamp.h"
#include "ui/message.h"
#include "dynamics.xml.h"
#include <algorithm>
namespace app {
using namespace ui;
using namespace skin;
namespace {
enum {
NONE,
PRESSURE_HEADER,
VELOCITY_HEADER,
SIZE_HEADER,
SIZE_WITH_PRESSURE,
SIZE_WITH_VELOCITY,
ANGLE_HEADER,
ANGLE_WITH_PRESSURE,
ANGLE_WITH_VELOCITY,
GRADIENT_HEADER,
GRADIENT_WITH_PRESSURE,
GRADIENT_WITH_VELOCITY,
};
} // anonymous namespace
DynamicsPopup::DynamicsPopup(Delegate* delegate)
: PopupWindow("",
PopupWindow::ClickBehavior::CloseOnClickOutsideHotRegion,
PopupWindow::EnterBehavior::DoNothingOnEnter)
, m_delegate(delegate)
, m_dynamics(new gen::Dynamics)
, m_ditheringSel(new DitheringSelector(DitheringSelector::SelectMatrix))
{
m_dynamics->values()->ItemChange.connect(
[this](ButtonSet::Item* item){
onValuesChange(item);
});
m_dynamics->gradientPlaceholder()->addChild(m_ditheringSel);
addChild(m_dynamics);
onValuesChange(nullptr);
}
tools::DynamicsOptions DynamicsPopup::getDynamics() const
{
tools::DynamicsOptions opts;
opts.size =
(isCheck(SIZE_WITH_PRESSURE) ? tools::DynamicSensor::Pressure:
isCheck(SIZE_WITH_VELOCITY) ? tools::DynamicSensor::Velocity:
tools::DynamicSensor::Static);
opts.angle =
(isCheck(ANGLE_WITH_PRESSURE) ? tools::DynamicSensor::Pressure:
isCheck(ANGLE_WITH_VELOCITY) ? tools::DynamicSensor::Velocity:
tools::DynamicSensor::Static);
opts.gradient =
(isCheck(GRADIENT_WITH_PRESSURE) ? tools::DynamicSensor::Pressure:
isCheck(GRADIENT_WITH_VELOCITY) ? tools::DynamicSensor::Velocity:
tools::DynamicSensor::Static);
opts.maxSize = m_dynamics->maxSize()->getValue();
opts.maxAngle = m_dynamics->maxAngle()->getValue();
opts.ditheringAlgorithm = m_ditheringSel->ditheringAlgorithm();
opts.ditheringMatrix = m_ditheringSel->ditheringMatrix();
return opts;
}
void DynamicsPopup::setCheck(int i, bool state)
{
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
m_dynamics->values()
->getItem(i)
->setIcon(state ? theme->parts.dropPixelsOk(): nullptr);
}
bool DynamicsPopup::isCheck(int i) const
{
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
return (m_dynamics->values()
->getItem(i)
->icon() == theme->parts.dropPixelsOk());
}
void DynamicsPopup::onValuesChange(ButtonSet::Item* item)
{
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
const skin::SkinPartPtr& ok = theme->parts.dropPixelsOk();
const int i = (item ? m_dynamics->values()->getItemIndex(item): -1);
// Switch item off
if (item && item->icon().get() == ok.get()) {
item->setIcon(nullptr);
}
else {
switch (i) {
case SIZE_WITH_PRESSURE:
case SIZE_WITH_VELOCITY:
setCheck(SIZE_WITH_PRESSURE, i == SIZE_WITH_PRESSURE);
setCheck(SIZE_WITH_VELOCITY, i == SIZE_WITH_VELOCITY);
break;
case ANGLE_WITH_PRESSURE:
case ANGLE_WITH_VELOCITY:
setCheck(ANGLE_WITH_PRESSURE, i == ANGLE_WITH_PRESSURE);
setCheck(ANGLE_WITH_VELOCITY, i == ANGLE_WITH_VELOCITY);
break;
case GRADIENT_WITH_PRESSURE:
case GRADIENT_WITH_VELOCITY:
setCheck(GRADIENT_WITH_PRESSURE, i == GRADIENT_WITH_PRESSURE);
setCheck(GRADIENT_WITH_VELOCITY, i == GRADIENT_WITH_VELOCITY);
break;
}
}
const bool needsSize = (isCheck(SIZE_WITH_PRESSURE) ||
isCheck(SIZE_WITH_VELOCITY));
const bool needsAngle = (isCheck(ANGLE_WITH_PRESSURE) ||
isCheck(ANGLE_WITH_VELOCITY));
const bool needsGradient = (isCheck(GRADIENT_WITH_PRESSURE) ||
isCheck(GRADIENT_WITH_VELOCITY));
const bool any = (needsSize || needsAngle || needsGradient);
doc::BrushRef brush = m_delegate->getActiveBrush();
if (needsSize && !m_dynamics->maxSize()->isVisible()) {
m_dynamics->maxSize()->setValue(
base::clamp(std::max(2*brush->size(), 4), 1, 64));
}
m_dynamics->maxSizeLabel()->setVisible(needsSize);
m_dynamics->maxSize()->setVisible(needsSize);
if (needsAngle && !m_dynamics->maxAngle()->isVisible()) {
m_dynamics->maxAngle()->setValue(brush->angle());
}
m_dynamics->maxAngleLabel()->setVisible(needsAngle);
m_dynamics->maxAngle()->setVisible(needsAngle);
m_dynamics->gradientLabel()->setVisible(needsGradient);
m_dynamics->gradientPlaceholder()->setVisible(needsGradient);
m_dynamics->separator()->setVisible(any);
m_dynamics->options()->setVisible(any);
auto oldBounds = bounds();
layout();
setBounds(gfx::Rect(origin(), sizeHint()));
m_hotRegion |= gfx::Region(bounds());
setHotRegion(m_hotRegion);
if (isVisible())
manager()->invalidateRect(oldBounds);
}
bool DynamicsPopup::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kOpenMessage:
m_hotRegion = gfx::Region(bounds());
setHotRegion(m_hotRegion);
break;
case kCloseMessage:
m_hotRegion.clear();
break;
}
return PopupWindow::onProcessMessage(msg);
}
} // namespace app

View File

@ -0,0 +1,49 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_DYNAMICS_POPUP_H_INCLUDED
#define APP_UI_DYNAMICS_POPUP_H_INCLUDED
#pragma once
#include "app/tools/dynamics.h"
#include "app/ui/button_set.h"
#include "doc/brush.h"
#include "gfx/region.h"
#include "ui/popup_window.h"
namespace app {
namespace gen {
class Dynamics;
}
class DitheringSelector;
class DynamicsPopup : public ui::PopupWindow {
public:
class Delegate {
public:
virtual ~Delegate() { }
virtual doc::BrushRef getActiveBrush() = 0;
};
DynamicsPopup(Delegate* delegate);
tools::DynamicsOptions getDynamics() const;
private:
void setCheck(int i, bool state);
bool isCheck(int i) const;
void onValuesChange(ButtonSet::Item* item);
bool onProcessMessage(ui::Message* msg) override;
Delegate* m_delegate;
gen::Dynamics* m_dynamics;
DitheringSelector* m_ditheringSel;
gfx::Region m_hotRegion;
};
} // namespace app
#endif

View File

@ -211,7 +211,9 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg)
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
handleMouseMovement(
tools::Pointer(editor->screenToEditor(mousePos),
button_from_msg(msg)));
button_from_msg(msg),
msg->pointerType(),
msg->pressure()));
return true;
}
@ -269,7 +271,9 @@ bool DrawingState::onScrollChange(Editor* editor)
gfx::Point mousePos = ui::get_mouse_position();
handleMouseMovement(
tools::Pointer(editor->screenToEditor(mousePos),
m_lastPointer.button()));
m_lastPointer.button(),
tools::Pointer::Type::Unknown,
0.0f));
}
return true;
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2016-2017 David Capello
//
// This program is distributed under the terms of
@ -27,7 +28,9 @@ inline tools::Pointer pointer_from_msg(Editor* editor,
const ui::MouseMessage* msg) {
return
tools::Pointer(editor->screenToEditor(msg->position()),
button_from_msg(msg));
button_from_msg(msg),
msg->pointerType(),
msg->pressure());
}
} // namespace app

View File

@ -674,13 +674,17 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor,
DrawingType::LineFreehand,
tools::Pointer(
editor->document()->lastDrawingPoint(),
pointerButton));
pointerButton,
msg ? msg->pointerType(): PointerType::Unknown,
msg ? msg->pressure(): 0.0f));
if (drawingState) {
drawingState->sendMovementToToolLoop(
tools::Pointer(
editor->screenToEditor(msg ? msg->position():
ui::get_mouse_position()),
pointerButton));
pointerButton,
msg ? msg->pointerType(): tools::Pointer::Type::Unknown,
msg ? msg->pressure(): 0.0f));
return true;
}
}

View File

@ -71,6 +71,7 @@ protected:
Editor* m_editor;
tools::Tool* m_tool;
BrushRef m_brush;
BrushRef m_origBrush;
gfx::Point m_oldPatternOrigin;
Doc* m_document;
Sprite* m_sprite;
@ -112,6 +113,7 @@ public:
: m_editor(editor)
, m_tool(tool)
, m_brush(brush)
, m_origBrush(brush)
, m_oldPatternOrigin(m_brush->patternOrigin())
, m_document(site.document())
, m_sprite(site.sprite())
@ -204,7 +206,7 @@ public:
}
~ToolLoopBase() {
m_brush->setPatternOrigin(m_oldPatternOrigin);
m_origBrush->setPatternOrigin(m_oldPatternOrigin);
}
void forceSnapToTiles() {
@ -215,6 +217,7 @@ public:
// IToolLoop interface
tools::Tool* getTool() override { return m_tool; }
Brush* getBrush() override { return m_brush.get(); }
void setBrush(const BrushRef& newBrush) override { m_brush = newBrush; }
Doc* getDocument() override { return m_document; }
Sprite* sprite() override { return m_sprite; }
Layer* getLayer() override { return m_layer; }
@ -360,6 +363,14 @@ public:
#endif
}
tools::DynamicsOptions getDynamics() override {
#ifdef ENABLE_UI // TODO add support when UI is not enabled
return App::instance()->contextBar()->getDynamics();
#else
return tools::DynamicsOptions();
#endif
}
void onSliceRect(const gfx::Rect& bounds) override { }

View File

@ -443,7 +443,8 @@ void Manager::generateMessagesFromOSEvents()
handleMouseMove(
osEvent.position(),
osEvent.modifiers(),
osEvent.pointerType());
osEvent.pointerType(),
osEvent.pressure());
lastMouseMoveEvent = osEvent;
break;
}
@ -513,8 +514,9 @@ void Manager::generateMessagesFromOSEvents()
}
void Manager::handleMouseMove(const gfx::Point& mousePos,
KeyModifiers modifiers,
PointerType pointerType)
const KeyModifiers modifiers,
const PointerType pointerType,
const float pressure)
{
// Get the list of widgets to send mouse messages.
mouse_widgets_list.clear();
@ -549,7 +551,10 @@ void Manager::handleMouseMove(const gfx::Point& mousePos,
mousePos,
pointerType,
m_mouseButton,
modifiers));
modifiers,
gfx::Point(0, 0),
false,
pressure));
}
void Manager::handleMouseDown(const gfx::Point& mousePos,
@ -1682,7 +1687,8 @@ Message* Manager::newMouseMessage(
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta,
bool preciseWheel)
bool preciseWheel,
float pressure)
{
#ifdef __APPLE__
// Convert Ctrl+left click -> right-click
@ -1699,7 +1705,7 @@ Message* Manager::newMouseMessage(
Message* msg = new MouseMessage(
type, pointerType, button, modifiers, mousePos,
wheelDelta, preciseWheel);
wheelDelta, preciseWheel, pressure);
if (widget)
msg->setRecipient(widget);

View File

@ -121,8 +121,9 @@ namespace ui {
PointerType pointerType);
void generateMessagesFromOSEvents();
void handleMouseMove(const gfx::Point& mousePos,
KeyModifiers modifiers,
PointerType pointerType);
const KeyModifiers modifiers,
const PointerType pointerType,
const float pressure);
void handleMouseDown(const gfx::Point& mousePos,
MouseButton mouseButton,
KeyModifiers modifiers,
@ -158,7 +159,8 @@ namespace ui {
MouseButton button,
KeyModifiers modifiers,
const gfx::Point& wheelDelta = gfx::Point(0, 0),
bool preciseWheel = false);
bool preciseWheel = false,
float pressure = 0.0f);
void broadcastKeyMsg(Message* msg);
static Manager* m_defaultManager;

View File

@ -120,13 +120,15 @@ namespace ui {
KeyModifiers modifiers,
const gfx::Point& pos,
const gfx::Point& wheelDelta = gfx::Point(0, 0),
bool preciseWheel = false)
bool preciseWheel = false,
float pressure = 0.0f)
: Message(type, modifiers),
m_pointerType(pointerType),
m_button(button),
m_pos(pos),
m_wheelDelta(wheelDelta),
m_preciseWheel(preciseWheel) {
m_preciseWheel(preciseWheel),
m_pressure(pressure) {
}
PointerType pointerType() const { return m_pointerType; }
@ -136,6 +138,7 @@ namespace ui {
bool middle() const { return (m_button == kButtonMiddle); }
gfx::Point wheelDelta() const { return m_wheelDelta; }
bool preciseWheel() const { return m_preciseWheel; }
float pressure() const { return m_pressure; }
const gfx::Point& position() const { return m_pos; }
@ -145,6 +148,7 @@ namespace ui {
gfx::Point m_pos; // Mouse position
gfx::Point m_wheelDelta; // Wheel axis variation
bool m_preciseWheel;
float m_pressure;
};
class TouchMessage : public Message {