mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-06 12:39:57 +00:00
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:
parent
5affdbbae1
commit
79f9e28ce8
10
.travis.yml
10
.travis.yml
@ -11,7 +11,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
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:
|
env:
|
||||||
- ENABLE_UI=OFF
|
- ENABLE_UI=OFF
|
||||||
- XVFB=xvfb-run
|
- XVFB=xvfb-run
|
||||||
@ -19,7 +19,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
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:
|
env:
|
||||||
- ENABLE_SCRIPTING=OFF
|
- ENABLE_SCRIPTING=OFF
|
||||||
- XVFB=xvfb-run
|
- XVFB=xvfb-run
|
||||||
@ -27,7 +27,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
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:
|
env:
|
||||||
- ENABLE_SCRIPTING=OFF
|
- ENABLE_SCRIPTING=OFF
|
||||||
- ENABLE_UI=OFF
|
- ENABLE_UI=OFF
|
||||||
@ -37,7 +37,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
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:
|
env:
|
||||||
- ENABLE_UI=ON
|
- ENABLE_UI=ON
|
||||||
- XVFB=xvfb-run
|
- XVFB=xvfb-run
|
||||||
@ -47,7 +47,7 @@ matrix:
|
|||||||
sources:
|
sources:
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
packages:
|
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:
|
env:
|
||||||
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
|
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
|
||||||
- ENABLE_UI=ON
|
- ENABLE_UI=ON
|
||||||
|
@ -72,11 +72,11 @@ versions might work).
|
|||||||
|
|
||||||
You will need the following dependencies on Ubuntu/Debian:
|
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:
|
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
|
# Compiling
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@ -418,6 +418,7 @@
|
|||||||
<part id="outline_vertical" x="192" y="224" w="13" h="15" />
|
<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_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="outline_full_pixel" x="214" y="224" w="5" h="5" />
|
||||||
|
<part id="dynamics" x="176" y="144" w="16" h="16" />
|
||||||
</parts>
|
</parts>
|
||||||
<styles>
|
<styles>
|
||||||
<style id="box" />
|
<style id="box" />
|
||||||
|
@ -422,6 +422,7 @@ SetSameInk = Same Ink in All Tools
|
|||||||
ShowAutoGuides = Show Auto Guides
|
ShowAutoGuides = Show Auto Guides
|
||||||
ShowBrushPreview = Show Brush Preview
|
ShowBrushPreview = Show Brush Preview
|
||||||
ShowBrushes = Show Brushes
|
ShowBrushes = Show Brushes
|
||||||
|
ShowDynamics = Show Dynamics
|
||||||
ShowExtras = Show Extras
|
ShowExtras = Show Extras
|
||||||
ShowGrid = Show Grid
|
ShowGrid = Show Grid
|
||||||
ShowLayerEdges = Show Layer Edges
|
ShowLayerEdges = Show Layer Edges
|
||||||
@ -520,6 +521,28 @@ duplicate = Duplicate:
|
|||||||
as = As:
|
as = As:
|
||||||
merged_layers = Duplicate merged layers only
|
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]
|
[export_file]
|
||||||
title = Export File
|
title = Export File
|
||||||
output_file = Output File:
|
output_file = Output File:
|
||||||
|
39
data/widgets/dynamics.xml
Normal file
39
data/widgets/dynamics.xml
Normal 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
2
laf
@ -1 +1 @@
|
|||||||
Subproject commit 55a3acb0bf6f30a50023dbdf04c2813423064702
|
Subproject commit 40c86d83db8eb4a96b0552535a9c7e4ddef2ab70
|
@ -319,6 +319,7 @@ if(ENABLE_UI)
|
|||||||
ui/dithering_selector.cpp
|
ui/dithering_selector.cpp
|
||||||
ui/doc_view.cpp
|
ui/doc_view.cpp
|
||||||
ui/drop_down_button.cpp
|
ui/drop_down_button.cpp
|
||||||
|
ui/dynamics_popup.cpp
|
||||||
ui/editor/brush_preview.cpp
|
ui/editor/brush_preview.cpp
|
||||||
ui/editor/drawing_state.cpp
|
ui/editor/drawing_state.cpp
|
||||||
ui/editor/editor.cpp
|
ui/editor/editor.cpp
|
||||||
|
@ -328,7 +328,12 @@ int App_useTool(lua_State* L)
|
|||||||
while (lua_next(L, -2) != 0) {
|
while (lua_next(L, -2) != 0) {
|
||||||
gfx::Point pt = convert_args_into_point(L, -1);
|
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) {
|
if (first) {
|
||||||
first = false;
|
first = false;
|
||||||
manager.prepareLoop(pointer);
|
manager.prepareLoop(pointer);
|
||||||
|
42
src/app/tools/dynamics.h
Normal file
42
src/app/tools/dynamics.h
Normal 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
|
@ -37,21 +37,26 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class BrushPointShape : public PointShape {
|
class BrushPointShape : public PointShape {
|
||||||
Brush* m_brush;
|
Brush* m_lastBrush;
|
||||||
std::shared_ptr<CompressedImage> m_compressedImage;
|
std::shared_ptr<CompressedImage> m_compressedImage;
|
||||||
bool m_firstPoint;
|
bool m_firstPoint;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void preparePointShape(ToolLoop* loop) override {
|
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_firstPoint = true;
|
||||||
|
m_lastBrush = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void transformPoint(ToolLoop* loop, int x, int y) override {
|
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;
|
x += m_brush->bounds().x;
|
||||||
y += m_brush->bounds().y;
|
y += m_brush->bounds().y;
|
||||||
|
|
||||||
@ -91,7 +96,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
|
void getModifiedArea(ToolLoop* loop, int x, int y, Rect& area) override {
|
||||||
area = m_brush->bounds();
|
area = loop->getBrush()->bounds();
|
||||||
area.x += x;
|
area.x += x;
|
||||||
area.y += y;
|
area.y += y;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2016 David Capello
|
// Copyright (C) 2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -9,6 +10,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gfx/point.h"
|
#include "gfx/point.h"
|
||||||
|
#include "ui/pointer_type.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace tools {
|
namespace tools {
|
||||||
@ -17,19 +19,33 @@ namespace tools {
|
|||||||
class Pointer {
|
class Pointer {
|
||||||
public:
|
public:
|
||||||
enum Button { None, Left, Middle, Right };
|
enum Button { None, Left, Middle, Right };
|
||||||
|
typedef ui::PointerType Type;
|
||||||
|
|
||||||
Pointer()
|
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)
|
Pointer(const gfx::Point& point,
|
||||||
: m_point(point), m_button(button) { }
|
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; }
|
const gfx::Point& point() const { return m_point; }
|
||||||
Button button() const { return m_button; }
|
Button button() const { return m_button; }
|
||||||
|
Type type() const { return m_type; }
|
||||||
|
float pressure() const { return m_pressure; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
gfx::Point m_point;
|
gfx::Point m_point;
|
||||||
Button m_button;
|
Button m_button;
|
||||||
|
Type m_type;
|
||||||
|
float m_pressure;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
|
@ -9,8 +9,10 @@
|
|||||||
#define APP_TOOLS_TOOL_LOOP_H_INCLUDED
|
#define APP_TOOLS_TOOL_LOOP_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "app/tools/dynamics.h"
|
||||||
#include "app/tools/tool_loop_modifiers.h"
|
#include "app/tools/tool_loop_modifiers.h"
|
||||||
#include "app/tools/trace_policy.h"
|
#include "app/tools/trace_policy.h"
|
||||||
|
#include "doc/brush.h"
|
||||||
#include "doc/color.h"
|
#include "doc/color.h"
|
||||||
#include "doc/frame.h"
|
#include "doc/frame.h"
|
||||||
#include "filters/tiled_mode.h"
|
#include "filters/tiled_mode.h"
|
||||||
@ -23,7 +25,6 @@ namespace gfx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace doc {
|
namespace doc {
|
||||||
class Brush;
|
|
||||||
class Image;
|
class Image;
|
||||||
class Layer;
|
class Layer;
|
||||||
class Mask;
|
class Mask;
|
||||||
@ -72,6 +73,7 @@ namespace app {
|
|||||||
|
|
||||||
// Returns the brush which will be used with the tool
|
// Returns the brush which will be used with the tool
|
||||||
virtual Brush* getBrush() = 0;
|
virtual Brush* getBrush() = 0;
|
||||||
|
virtual void setBrush(const BrushRef& newBrush) = 0;
|
||||||
|
|
||||||
// Returns the document to which belongs the sprite.
|
// Returns the document to which belongs the sprite.
|
||||||
virtual Doc* getDocument() = 0;
|
virtual Doc* getDocument() = 0;
|
||||||
@ -235,6 +237,9 @@ namespace app {
|
|||||||
virtual render::DitheringAlgorithmBase* getDitheringAlgorithm() = 0;
|
virtual render::DitheringAlgorithmBase* getDitheringAlgorithm() = 0;
|
||||||
virtual render::GradientType getGradientType() = 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
|
// Called when the user release the mouse on SliceInk
|
||||||
virtual void onSliceRect(const gfx::Rect& bounds) = 0;
|
virtual void onSliceRect(const gfx::Rect& bounds) = 0;
|
||||||
};
|
};
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "app/tools/point_shape.h"
|
#include "app/tools/point_shape.h"
|
||||||
#include "app/tools/symmetry.h"
|
#include "app/tools/symmetry.h"
|
||||||
#include "app/tools/tool_loop.h"
|
#include "app/tools/tool_loop.h"
|
||||||
|
#include "base/clamp.h"
|
||||||
#include "doc/brush.h"
|
#include "doc/brush.h"
|
||||||
#include "doc/image.h"
|
#include "doc/image.h"
|
||||||
#include "doc/primitives.h"
|
#include "doc/primitives.h"
|
||||||
@ -40,6 +41,8 @@ using namespace filters;
|
|||||||
|
|
||||||
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
|
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
|
||||||
: m_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)
|
void ToolLoopManager::movement(const Pointer& pointer)
|
||||||
{
|
{
|
||||||
TOOL_TRACE("ToolLoopManager::movement", pointer.point());
|
|
||||||
|
|
||||||
m_lastPointer = pointer;
|
m_lastPointer = pointer;
|
||||||
|
|
||||||
if (isCanceled())
|
if (isCanceled())
|
||||||
@ -163,11 +164,16 @@ void ToolLoopManager::movement(const Pointer& pointer)
|
|||||||
|
|
||||||
// Convert the screen point to a sprite point
|
// Convert the screen point to a sprite point
|
||||||
Point spritePoint = pointer.point();
|
Point spritePoint = pointer.point();
|
||||||
// Calculate the speed (new sprite point - old sprite point)
|
// Calculate the velocity (new sprite point - old sprite point)
|
||||||
m_toolLoop->setSpeed(spritePoint - m_oldPoint);
|
Point velocity = (spritePoint - m_oldPoint);
|
||||||
|
m_toolLoop->setSpeed(velocity);
|
||||||
m_oldPoint = spritePoint;
|
m_oldPoint = spritePoint;
|
||||||
snapToGrid(spritePoint);
|
snapToGrid(spritePoint);
|
||||||
|
|
||||||
|
// Control dynamic parameters through sensors
|
||||||
|
if (m_dynamics.isDynamic())
|
||||||
|
adjustBrushWithDynamics(pointer, velocity);
|
||||||
|
|
||||||
m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
|
m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
|
||||||
|
|
||||||
std::string statusText;
|
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 tools
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019 Igara Studio S.A.
|
// Copyright (C) 2019-2020 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
|
||||||
@ -9,8 +9,10 @@
|
|||||||
#define APP_TOOLS_TOOL_LOOP_MANAGER_H_INCLUDED
|
#define APP_TOOLS_TOOL_LOOP_MANAGER_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "app/tools/dynamics.h"
|
||||||
#include "app/tools/pointer.h"
|
#include "app/tools/pointer.h"
|
||||||
#include "app/tools/stroke.h"
|
#include "app/tools/stroke.h"
|
||||||
|
#include "doc/brush.h"
|
||||||
#include "gfx/point.h"
|
#include "gfx/point.h"
|
||||||
#include "gfx/region.h"
|
#include "gfx/region.h"
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
void doLoopStep(bool lastStep);
|
void doLoopStep(bool lastStep);
|
||||||
void snapToGrid(gfx::Point& point);
|
void snapToGrid(gfx::Point& point);
|
||||||
|
void adjustBrushWithDynamics(const Pointer& pointer,
|
||||||
|
const gfx::Point& velocity);
|
||||||
|
|
||||||
void calculateDirtyArea(const Strokes& strokes);
|
void calculateDirtyArea(const Strokes& strokes);
|
||||||
|
|
||||||
@ -81,6 +85,8 @@ private:
|
|||||||
gfx::Point m_oldPoint;
|
gfx::Point m_oldPoint;
|
||||||
gfx::Region m_dirtyArea;
|
gfx::Region m_dirtyArea;
|
||||||
gfx::Region m_nextDirtyArea;
|
gfx::Region m_nextDirtyArea;
|
||||||
|
doc::Brush m_brush0;
|
||||||
|
DynamicsOptions m_dynamics;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tools
|
} // namespace tools
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
#include "app/ui/color_button.h"
|
#include "app/ui/color_button.h"
|
||||||
#include "app/ui/color_shades.h"
|
#include "app/ui/color_shades.h"
|
||||||
#include "app/ui/dithering_selector.h"
|
#include "app/ui/dithering_selector.h"
|
||||||
|
#include "app/ui/dynamics_popup.h"
|
||||||
#include "app/ui/editor/editor.h"
|
#include "app/ui/editor/editor.h"
|
||||||
#include "app/ui/icon_button.h"
|
#include "app/ui/icon_button.h"
|
||||||
#include "app/ui/keyboard_shortcuts.h"
|
#include "app/ui/keyboard_shortcuts.h"
|
||||||
@ -972,6 +973,55 @@ private:
|
|||||||
bool m_lockChange;
|
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 {
|
class ContextBar::FreehandAlgorithmField : public CheckBox {
|
||||||
public:
|
public:
|
||||||
FreehandAlgorithmField() : CheckBox("Pixel-perfect") {
|
FreehandAlgorithmField() : CheckBox("Pixel-perfect") {
|
||||||
@ -1500,6 +1550,7 @@ ContextBar::ContextBar(TooltipManager* tooltipManager,
|
|||||||
addChild(m_selectBoxHelp = new Label(""));
|
addChild(m_selectBoxHelp = new Label(""));
|
||||||
addChild(m_freehandBox = new HBox());
|
addChild(m_freehandBox = new HBox());
|
||||||
|
|
||||||
|
m_freehandBox->addChild(m_dynamics = new DynamicsField(this));
|
||||||
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
|
||||||
|
|
||||||
addChild(m_symmetry = new SymmetryField());
|
addChild(m_symmetry = new SymmetryField());
|
||||||
@ -2148,6 +2199,11 @@ render::GradientType ContextBar::gradientType()
|
|||||||
return m_gradientType->gradientType();
|
return m_gradientType->gradientType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tools::DynamicsOptions ContextBar::getDynamics()
|
||||||
|
{
|
||||||
|
return m_dynamics->getDynamics();
|
||||||
|
}
|
||||||
|
|
||||||
void ContextBar::setupTooltips(TooltipManager* tooltipManager)
|
void ContextBar::setupTooltips(TooltipManager* tooltipManager)
|
||||||
{
|
{
|
||||||
tooltipManager->addTooltipFor(m_brushBack->at(0), "Discard Brush (Esc)", BOTTOM);
|
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_spraySpeed, "Spray Speed", BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_pivot->at(0), "Rotation Pivot", BOTTOM);
|
tooltipManager->addTooltipFor(m_pivot->at(0), "Rotation Pivot", BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_rotAlgo, "Rotation Algorithm", BOTTOM);
|
tooltipManager->addTooltipFor(m_rotAlgo, "Rotation Algorithm", BOTTOM);
|
||||||
|
tooltipManager->addTooltipFor(m_dynamics->at(0), "Dynamics", BOTTOM);
|
||||||
tooltipManager->addTooltipFor(m_freehandAlgo,
|
tooltipManager->addTooltipFor(m_freehandAlgo,
|
||||||
key_tooltip("Freehand trace algorithm",
|
key_tooltip("Freehand trace algorithm",
|
||||||
CommandId::PixelPerfectMode()), BOTTOM);
|
CommandId::PixelPerfectMode()), BOTTOM);
|
||||||
@ -2184,11 +2241,24 @@ void ContextBar::registerCommands()
|
|||||||
new QuickCommand(
|
new QuickCommand(
|
||||||
CommandId::ShowBrushes(),
|
CommandId::ShowBrushes(),
|
||||||
[this]{ this->showBrushes(); }));
|
[this]{ this->showBrushes(); }));
|
||||||
|
|
||||||
|
Commands::instance()
|
||||||
|
->add(
|
||||||
|
new QuickCommand(
|
||||||
|
CommandId::ShowDynamics(),
|
||||||
|
[this]{ this->showDynamics(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextBar::showBrushes()
|
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
|
} // namespace app
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
// Copyright (C) 2018-2020 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
|
||||||
@ -12,6 +12,7 @@
|
|||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "app/shade.h"
|
#include "app/shade.h"
|
||||||
#include "app/tools/active_tool_observer.h"
|
#include "app/tools/active_tool_observer.h"
|
||||||
|
#include "app/tools/dynamics.h"
|
||||||
#include "app/tools/ink_type.h"
|
#include "app/tools/ink_type.h"
|
||||||
#include "app/tools/tool_loop_modifiers.h"
|
#include "app/tools/tool_loop_modifiers.h"
|
||||||
#include "app/ui/context_bar_observer.h"
|
#include "app/ui/context_bar_observer.h"
|
||||||
@ -90,6 +91,9 @@ namespace app {
|
|||||||
render::DitheringAlgorithmBase* ditheringAlgorithm();
|
render::DitheringAlgorithmBase* ditheringAlgorithm();
|
||||||
render::GradientType gradientType();
|
render::GradientType gradientType();
|
||||||
|
|
||||||
|
// For freehand with dynamics
|
||||||
|
tools::DynamicsOptions getDynamics();
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
obs::signal<void()> BrushChange;
|
obs::signal<void()> BrushChange;
|
||||||
|
|
||||||
@ -124,6 +128,7 @@ namespace app {
|
|||||||
void setupTooltips(ui::TooltipManager* tooltipManager);
|
void setupTooltips(ui::TooltipManager* tooltipManager);
|
||||||
void registerCommands();
|
void registerCommands();
|
||||||
void showBrushes();
|
void showBrushes();
|
||||||
|
void showDynamics();
|
||||||
|
|
||||||
class ZoomButtons;
|
class ZoomButtons;
|
||||||
class BrushBackField;
|
class BrushBackField;
|
||||||
@ -143,6 +148,7 @@ namespace app {
|
|||||||
class TransparentColorField;
|
class TransparentColorField;
|
||||||
class PivotField;
|
class PivotField;
|
||||||
class RotAlgorithmField;
|
class RotAlgorithmField;
|
||||||
|
class DynamicsField;
|
||||||
class FreehandAlgorithmField;
|
class FreehandAlgorithmField;
|
||||||
class BrushPatternField;
|
class BrushPatternField;
|
||||||
class EyedropperField;
|
class EyedropperField;
|
||||||
@ -167,6 +173,7 @@ namespace app {
|
|||||||
EyedropperField* m_eyedropperField;
|
EyedropperField* m_eyedropperField;
|
||||||
AutoSelectLayerField* m_autoSelectLayer;
|
AutoSelectLayerField* m_autoSelectLayer;
|
||||||
ui::Box* m_freehandBox;
|
ui::Box* m_freehandBox;
|
||||||
|
DynamicsField* m_dynamics;
|
||||||
FreehandAlgorithmField* m_freehandAlgo;
|
FreehandAlgorithmField* m_freehandAlgo;
|
||||||
BrushPatternField* m_brushPatternField;
|
BrushPatternField* m_brushPatternField;
|
||||||
ui::Box* m_sprayBox;
|
ui::Box* m_sprayBox;
|
||||||
|
185
src/app/ui/dynamics_popup.cpp
Normal file
185
src/app/ui/dynamics_popup.cpp
Normal 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
|
49
src/app/ui/dynamics_popup.h
Normal file
49
src/app/ui/dynamics_popup.h
Normal 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
|
@ -211,7 +211,9 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg)
|
|||||||
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
|
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
|
||||||
handleMouseMovement(
|
handleMouseMovement(
|
||||||
tools::Pointer(editor->screenToEditor(mousePos),
|
tools::Pointer(editor->screenToEditor(mousePos),
|
||||||
button_from_msg(msg)));
|
button_from_msg(msg),
|
||||||
|
msg->pointerType(),
|
||||||
|
msg->pressure()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -269,7 +271,9 @@ bool DrawingState::onScrollChange(Editor* editor)
|
|||||||
gfx::Point mousePos = ui::get_mouse_position();
|
gfx::Point mousePos = ui::get_mouse_position();
|
||||||
handleMouseMovement(
|
handleMouseMovement(
|
||||||
tools::Pointer(editor->screenToEditor(mousePos),
|
tools::Pointer(editor->screenToEditor(mousePos),
|
||||||
m_lastPointer.button()));
|
m_lastPointer.button(),
|
||||||
|
tools::Pointer::Type::Unknown,
|
||||||
|
0.0f));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2020 Igara Studio S.A.
|
||||||
// Copyright (C) 2016-2017 David Capello
|
// Copyright (C) 2016-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -27,7 +28,9 @@ inline tools::Pointer pointer_from_msg(Editor* editor,
|
|||||||
const ui::MouseMessage* msg) {
|
const ui::MouseMessage* msg) {
|
||||||
return
|
return
|
||||||
tools::Pointer(editor->screenToEditor(msg->position()),
|
tools::Pointer(editor->screenToEditor(msg->position()),
|
||||||
button_from_msg(msg));
|
button_from_msg(msg),
|
||||||
|
msg->pointerType(),
|
||||||
|
msg->pressure());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -674,13 +674,17 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor,
|
|||||||
DrawingType::LineFreehand,
|
DrawingType::LineFreehand,
|
||||||
tools::Pointer(
|
tools::Pointer(
|
||||||
editor->document()->lastDrawingPoint(),
|
editor->document()->lastDrawingPoint(),
|
||||||
pointerButton));
|
pointerButton,
|
||||||
|
msg ? msg->pointerType(): PointerType::Unknown,
|
||||||
|
msg ? msg->pressure(): 0.0f));
|
||||||
if (drawingState) {
|
if (drawingState) {
|
||||||
drawingState->sendMovementToToolLoop(
|
drawingState->sendMovementToToolLoop(
|
||||||
tools::Pointer(
|
tools::Pointer(
|
||||||
editor->screenToEditor(msg ? msg->position():
|
editor->screenToEditor(msg ? msg->position():
|
||||||
ui::get_mouse_position()),
|
ui::get_mouse_position()),
|
||||||
pointerButton));
|
pointerButton,
|
||||||
|
msg ? msg->pointerType(): tools::Pointer::Type::Unknown,
|
||||||
|
msg ? msg->pressure(): 0.0f));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@ protected:
|
|||||||
Editor* m_editor;
|
Editor* m_editor;
|
||||||
tools::Tool* m_tool;
|
tools::Tool* m_tool;
|
||||||
BrushRef m_brush;
|
BrushRef m_brush;
|
||||||
|
BrushRef m_origBrush;
|
||||||
gfx::Point m_oldPatternOrigin;
|
gfx::Point m_oldPatternOrigin;
|
||||||
Doc* m_document;
|
Doc* m_document;
|
||||||
Sprite* m_sprite;
|
Sprite* m_sprite;
|
||||||
@ -112,6 +113,7 @@ public:
|
|||||||
: m_editor(editor)
|
: m_editor(editor)
|
||||||
, m_tool(tool)
|
, m_tool(tool)
|
||||||
, m_brush(brush)
|
, m_brush(brush)
|
||||||
|
, m_origBrush(brush)
|
||||||
, m_oldPatternOrigin(m_brush->patternOrigin())
|
, m_oldPatternOrigin(m_brush->patternOrigin())
|
||||||
, m_document(site.document())
|
, m_document(site.document())
|
||||||
, m_sprite(site.sprite())
|
, m_sprite(site.sprite())
|
||||||
@ -204,7 +206,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
~ToolLoopBase() {
|
~ToolLoopBase() {
|
||||||
m_brush->setPatternOrigin(m_oldPatternOrigin);
|
m_origBrush->setPatternOrigin(m_oldPatternOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void forceSnapToTiles() {
|
void forceSnapToTiles() {
|
||||||
@ -215,6 +217,7 @@ public:
|
|||||||
// IToolLoop interface
|
// IToolLoop interface
|
||||||
tools::Tool* getTool() override { return m_tool; }
|
tools::Tool* getTool() override { return m_tool; }
|
||||||
Brush* getBrush() override { return m_brush.get(); }
|
Brush* getBrush() override { return m_brush.get(); }
|
||||||
|
void setBrush(const BrushRef& newBrush) override { m_brush = newBrush; }
|
||||||
Doc* getDocument() override { return m_document; }
|
Doc* getDocument() override { return m_document; }
|
||||||
Sprite* sprite() override { return m_sprite; }
|
Sprite* sprite() override { return m_sprite; }
|
||||||
Layer* getLayer() override { return m_layer; }
|
Layer* getLayer() override { return m_layer; }
|
||||||
@ -360,6 +363,14 @@ public:
|
|||||||
#endif
|
#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 { }
|
void onSliceRect(const gfx::Rect& bounds) override { }
|
||||||
|
|
||||||
|
@ -443,7 +443,8 @@ void Manager::generateMessagesFromOSEvents()
|
|||||||
handleMouseMove(
|
handleMouseMove(
|
||||||
osEvent.position(),
|
osEvent.position(),
|
||||||
osEvent.modifiers(),
|
osEvent.modifiers(),
|
||||||
osEvent.pointerType());
|
osEvent.pointerType(),
|
||||||
|
osEvent.pressure());
|
||||||
lastMouseMoveEvent = osEvent;
|
lastMouseMoveEvent = osEvent;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -513,8 +514,9 @@ void Manager::generateMessagesFromOSEvents()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Manager::handleMouseMove(const gfx::Point& mousePos,
|
void Manager::handleMouseMove(const gfx::Point& mousePos,
|
||||||
KeyModifiers modifiers,
|
const KeyModifiers modifiers,
|
||||||
PointerType pointerType)
|
const PointerType pointerType,
|
||||||
|
const float pressure)
|
||||||
{
|
{
|
||||||
// Get the list of widgets to send mouse messages.
|
// Get the list of widgets to send mouse messages.
|
||||||
mouse_widgets_list.clear();
|
mouse_widgets_list.clear();
|
||||||
@ -549,7 +551,10 @@ void Manager::handleMouseMove(const gfx::Point& mousePos,
|
|||||||
mousePos,
|
mousePos,
|
||||||
pointerType,
|
pointerType,
|
||||||
m_mouseButton,
|
m_mouseButton,
|
||||||
modifiers));
|
modifiers,
|
||||||
|
gfx::Point(0, 0),
|
||||||
|
false,
|
||||||
|
pressure));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::handleMouseDown(const gfx::Point& mousePos,
|
void Manager::handleMouseDown(const gfx::Point& mousePos,
|
||||||
@ -1682,7 +1687,8 @@ Message* Manager::newMouseMessage(
|
|||||||
MouseButton button,
|
MouseButton button,
|
||||||
KeyModifiers modifiers,
|
KeyModifiers modifiers,
|
||||||
const gfx::Point& wheelDelta,
|
const gfx::Point& wheelDelta,
|
||||||
bool preciseWheel)
|
bool preciseWheel,
|
||||||
|
float pressure)
|
||||||
{
|
{
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
// Convert Ctrl+left click -> right-click
|
// Convert Ctrl+left click -> right-click
|
||||||
@ -1699,7 +1705,7 @@ Message* Manager::newMouseMessage(
|
|||||||
|
|
||||||
Message* msg = new MouseMessage(
|
Message* msg = new MouseMessage(
|
||||||
type, pointerType, button, modifiers, mousePos,
|
type, pointerType, button, modifiers, mousePos,
|
||||||
wheelDelta, preciseWheel);
|
wheelDelta, preciseWheel, pressure);
|
||||||
|
|
||||||
if (widget)
|
if (widget)
|
||||||
msg->setRecipient(widget);
|
msg->setRecipient(widget);
|
||||||
|
@ -121,8 +121,9 @@ namespace ui {
|
|||||||
PointerType pointerType);
|
PointerType pointerType);
|
||||||
void generateMessagesFromOSEvents();
|
void generateMessagesFromOSEvents();
|
||||||
void handleMouseMove(const gfx::Point& mousePos,
|
void handleMouseMove(const gfx::Point& mousePos,
|
||||||
KeyModifiers modifiers,
|
const KeyModifiers modifiers,
|
||||||
PointerType pointerType);
|
const PointerType pointerType,
|
||||||
|
const float pressure);
|
||||||
void handleMouseDown(const gfx::Point& mousePos,
|
void handleMouseDown(const gfx::Point& mousePos,
|
||||||
MouseButton mouseButton,
|
MouseButton mouseButton,
|
||||||
KeyModifiers modifiers,
|
KeyModifiers modifiers,
|
||||||
@ -158,7 +159,8 @@ namespace ui {
|
|||||||
MouseButton button,
|
MouseButton button,
|
||||||
KeyModifiers modifiers,
|
KeyModifiers modifiers,
|
||||||
const gfx::Point& wheelDelta = gfx::Point(0, 0),
|
const gfx::Point& wheelDelta = gfx::Point(0, 0),
|
||||||
bool preciseWheel = false);
|
bool preciseWheel = false,
|
||||||
|
float pressure = 0.0f);
|
||||||
void broadcastKeyMsg(Message* msg);
|
void broadcastKeyMsg(Message* msg);
|
||||||
|
|
||||||
static Manager* m_defaultManager;
|
static Manager* m_defaultManager;
|
||||||
|
@ -120,13 +120,15 @@ namespace ui {
|
|||||||
KeyModifiers modifiers,
|
KeyModifiers modifiers,
|
||||||
const gfx::Point& pos,
|
const gfx::Point& pos,
|
||||||
const gfx::Point& wheelDelta = gfx::Point(0, 0),
|
const gfx::Point& wheelDelta = gfx::Point(0, 0),
|
||||||
bool preciseWheel = false)
|
bool preciseWheel = false,
|
||||||
|
float pressure = 0.0f)
|
||||||
: Message(type, modifiers),
|
: Message(type, modifiers),
|
||||||
m_pointerType(pointerType),
|
m_pointerType(pointerType),
|
||||||
m_button(button),
|
m_button(button),
|
||||||
m_pos(pos),
|
m_pos(pos),
|
||||||
m_wheelDelta(wheelDelta),
|
m_wheelDelta(wheelDelta),
|
||||||
m_preciseWheel(preciseWheel) {
|
m_preciseWheel(preciseWheel),
|
||||||
|
m_pressure(pressure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PointerType pointerType() const { return m_pointerType; }
|
PointerType pointerType() const { return m_pointerType; }
|
||||||
@ -136,6 +138,7 @@ namespace ui {
|
|||||||
bool middle() const { return (m_button == kButtonMiddle); }
|
bool middle() const { return (m_button == kButtonMiddle); }
|
||||||
gfx::Point wheelDelta() const { return m_wheelDelta; }
|
gfx::Point wheelDelta() const { return m_wheelDelta; }
|
||||||
bool preciseWheel() const { return m_preciseWheel; }
|
bool preciseWheel() const { return m_preciseWheel; }
|
||||||
|
float pressure() const { return m_pressure; }
|
||||||
|
|
||||||
const gfx::Point& position() const { return m_pos; }
|
const gfx::Point& position() const { return m_pos; }
|
||||||
|
|
||||||
@ -145,6 +148,7 @@ namespace ui {
|
|||||||
gfx::Point m_pos; // Mouse position
|
gfx::Point m_pos; // Mouse position
|
||||||
gfx::Point m_wheelDelta; // Wheel axis variation
|
gfx::Point m_wheelDelta; // Wheel axis variation
|
||||||
bool m_preciseWheel;
|
bool m_preciseWheel;
|
||||||
|
float m_pressure;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TouchMessage : public Message {
|
class TouchMessage : public Message {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user