Several fixed for Dynamics

* Now the max value in dynamics is equal to the brush size, so changing
  the brush size (e.g. switching between pencil/eraser tools) will
  change the max brush size (which is the most common parameter to
  change)
* Added mini_slider style for min/max brush/angle values
* Fixed some issues clicking outside the dynamics popup
This commit is contained in:
David Capello 2020-05-02 11:59:34 -03:00
parent cc7bdbcec4
commit 263236deb3
13 changed files with 114 additions and 52 deletions

View File

@ -964,5 +964,10 @@
<text color="slider_empty_text" align="center middle" />
<text color="slider_empty_text" align="center middle" state="focus" y="1" />
</style>
<style id="mini_slider" extends="slider" font="mini" padding-top="1" padding-bottom="3">
<background part="mini_slider_empty" />
<text color="slider_empty_text" align="center middle" />
<text color="slider_empty_text" align="center middle" state="focus" />
</style>
</styles>
</theme>

View File

@ -536,13 +536,17 @@ angle_tooltip = <<<END
Change the brush angle
depending on the sensor value
END
min_size_tooltip = Brush size when the sensor has its minimum value
max_size_tooltip = Brush size when the sensor has its maximum value
min_angle_tooltip = Brush angle when the sensor has its minimum value
max_angle_tooltip = Brush angle when the sensor has its maximum value
gradient = Gradient
gradient_tooltip = <<<END
Gradient between foreground
and background colors
END
max_point_value = Max Point Value:
sensors_tweaks = Sensor Tweaks
max_point_value = Min/Max Values
sensors_tweaks = Sensor Threshold
[export_file]
title = Export File

View File

@ -25,14 +25,23 @@
<grid id="options" columns="2" childspacing="0" expansive="true">
<separator id="separator" text="@.max_point_value" horizontal="true" cell_hspan="2" />
<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="size_label" text="@.size" style="mini_label" />
<slider id="min_size" value="1" min="1" max="64" cell_align="horizontal" style="mini_slider"
tooltip="@.min_size_tooltip" tooltip_dir="left" />
<boxfiller />
<slider id="max_size" value="64" min="1" max="64" cell_align="horizontal" style="mini_slider"
tooltip="@.max_size_tooltip" tooltip_dir="left" />
<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="angle_label" text="@.angle" style="mini_label" />
<slider id="min_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
tooltip="@.min_angle_tooltip" tooltip_dir="left" />
<boxfiller />
<slider id="max_angle" value="0" min="-180" max="+180" cell_align="horizontal" style="mini_slider"
tooltip="@.max_angle_tooltip" tooltip_dir="left" />
<label id="gradient_label" text="@.gradient" style="mini_label" />
<hbox id="gradient_placeholder" />
<hbox id="gradient_placeholder" cell_vspan="2" />
<boxfiller />
<separator id="separator2" text="@.sensors_tweaks" horizontal="true" cell_hspan="2" />

View File

@ -23,8 +23,8 @@ namespace tools {
DynamicSensor size = DynamicSensor::Static;
DynamicSensor angle = DynamicSensor::Static;
DynamicSensor gradient = DynamicSensor::Static;
int maxSize = 0;
int maxAngle = 0;
int minSize = 0;
int minAngle = 0;
render::DitheringMatrix ditheringMatrix;
float minPressureThreshold = 0.0f, maxPressureThreshold = 1.0f;
float minVelocityThreshold = 0.0f, maxVelocityThreshold = 1.0f;

View File

@ -111,7 +111,7 @@ public:
if (stroke.size() == 0)
return;
else if (stroke.size() == 1) {
doPointshapePoint(stroke[0].x, stroke[0].y, loop);
doPointshapeStrokePt(stroke[0], loop);
}
else {
Stroke pts;

View File

@ -82,8 +82,8 @@ public:
if (m_hasDynamicGradient &&
m_dynamics.ditheringMatrix.rows() == 1 &&
m_dynamics.ditheringMatrix.cols() == 1) {
color_t a = m_primaryColor;
color_t b = m_secondaryColor;
color_t a = m_secondaryColor;
color_t b = m_primaryColor;
const float t = pt.gradient;
const float ti = 1.0f - pt.gradient;
switch (loop->sprite()->pixelFormat()) {
@ -146,8 +146,8 @@ public:
loop->sprite()->pixelFormat(),
m_dynamics.ditheringMatrix,
pt.gradient,
m_primaryColor,
m_secondaryColor);
m_secondaryColor,
m_primaryColor);
prepareInk = true;
}
m_lastGradientValue = pt.gradient;

View File

@ -104,7 +104,7 @@ void ToolLoopManager::pressButton(const Pointer& pointer)
return;
}
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, true);
Stroke::Pt spritePoint = getSpriteStrokePt(pointer);
m_toolLoop->getController()->pressButton(m_toolLoop, m_stroke, spritePoint);
std::string statusText;
@ -134,7 +134,7 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
if (isCanceled())
return false;
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, false);
Stroke::Pt spritePoint = getSpriteStrokePt(pointer);
bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
if (!res && (m_toolLoop->getTracePolicy() == TracePolicy::Last ||
@ -156,7 +156,7 @@ void ToolLoopManager::movement(const Pointer& pointer)
if (isCanceled())
return;
Stroke::Pt spritePoint = getSpriteStrokePt(pointer, false);
Stroke::Pt spritePoint = getSpriteStrokePt(pointer);
m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
std::string statusText;
@ -365,8 +365,7 @@ void ToolLoopManager::calculateDirtyArea(const Strokes& strokes)
}
}
Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer,
const bool firstPoint)
Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer)
{
// Convert the screen point to a sprite point
Stroke::Pt spritePoint = pointer.point();
@ -445,26 +444,25 @@ void ToolLoopManager::adjustPointWithDynamics(const Pointer& pointer,
switch (m_dynamics.size) {
case DynamicSensor::Pressure:
if (hasP) size = (1.0f-p)*size + p*m_dynamics.maxSize;
if (hasP) size = (1.0f-p)*m_dynamics.minSize + p*size;
break;
case DynamicSensor::Velocity:
size = (1.0f-v)*size + v*m_dynamics.maxSize;
size = (1.0f-v)*m_dynamics.minSize + v*size;
break;
}
switch (m_dynamics.angle) {
case DynamicSensor::Pressure:
if (hasP) angle = (1.0f-p)*angle + p*m_dynamics.maxAngle;
if (hasP) angle = (1.0f-p)*m_dynamics.minAngle + p*angle;
break;
case DynamicSensor::Velocity:
angle = (1.0f-v)*angle + v*m_dynamics.maxAngle;
angle = (1.0f-v)*m_dynamics.minAngle + v*angle;
break;
}
switch (m_dynamics.gradient) {
case DynamicSensor::Pressure:
if (hasP)
pt.gradient = p;
pt.gradient = p;
break;
case DynamicSensor::Velocity:
pt.gradient = v;

View File

@ -74,8 +74,7 @@ public:
private:
void doLoopStep(bool lastStep);
void snapToGrid(Stroke::Pt& pt);
Stroke::Pt getSpriteStrokePt(const Pointer& pointer,
const bool firstPoint);
Stroke::Pt getSpriteStrokePt(const Pointer& pointer);
bool useDynamics() const;
void adjustPointWithDynamics(const Pointer& pointer, Stroke::Pt& pt);

View File

@ -259,7 +259,7 @@ private:
class ContextBar::BrushAngleField : public IntEntry {
public:
BrushAngleField(BrushTypeField* brushType)
: IntEntry(0, 180)
: IntEntry(-180, 180)
, m_brushType(brushType) {
setSuffix("\xc2\xb0");
}
@ -1013,6 +1013,16 @@ private:
return m_ctxBar->activeBrush();
}
void setMaxSize(int size) override {
Tool* tool = App::instance()->activeTool();
Preferences::instance().tool(tool).brush.size(size);
}
void setMaxAngle(int angle) override {
Tool* tool = App::instance()->activeTool();
Preferences::instance().tool(tool).brush.angle(angle);
}
void onItemChange(Item* item) override {
ButtonSet::onItemChange(item);
switchPopup();

View File

@ -49,10 +49,10 @@ enum {
} // anonymous namespace
// Special slider to set min/max values of a sensor
class DynamicsPopup::MinMaxSlider : public Widget {
// Special slider to set the min/max threshold values of a sensor
class DynamicsPopup::ThresholdSlider : public Widget {
public:
MinMaxSlider() {
ThresholdSlider() {
setExpansive(true);
initTheme();
}
@ -218,10 +218,18 @@ DynamicsPopup::DynamicsPopup(Delegate* delegate)
[this](ButtonSet::Item* item){
onValuesChange(item);
});
m_dynamics->maxSize()->Change.connect(
[this]{
m_delegate->setMaxSize(m_dynamics->maxSize()->getValue());
});
m_dynamics->maxAngle()->Change.connect(
[this]{
m_delegate->setMaxAngle(m_dynamics->maxAngle()->getValue());
});
m_dynamics->gradientPlaceholder()->addChild(m_ditheringSel);
m_dynamics->pressurePlaceholder()->addChild(m_pressureTweaks = new MinMaxSlider);
m_dynamics->velocityPlaceholder()->addChild(m_velocityTweaks = new MinMaxSlider);
m_dynamics->pressurePlaceholder()->addChild(m_pressureThreshold = new ThresholdSlider);
m_dynamics->velocityPlaceholder()->addChild(m_velocityThreshold = new ThresholdSlider);
addChild(m_dynamics);
onValuesChange(nullptr);
@ -242,14 +250,14 @@ tools::DynamicsOptions DynamicsPopup::getDynamics() const
(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.minSize = m_dynamics->minSize()->getValue();
opts.minAngle = m_dynamics->minAngle()->getValue();
opts.ditheringMatrix = m_ditheringSel->ditheringMatrix();
opts.minPressureThreshold = m_pressureTweaks->minThreshold();
opts.maxPressureThreshold = m_pressureTweaks->maxThreshold();
opts.minVelocityThreshold = m_velocityTweaks->minThreshold();
opts.maxVelocityThreshold = m_velocityTweaks->maxThreshold();
opts.minPressureThreshold = m_pressureThreshold->minThreshold();
opts.maxPressureThreshold = m_pressureThreshold->maxThreshold();
opts.minVelocityThreshold = m_velocityThreshold->minThreshold();
opts.maxVelocityThreshold = m_velocityThreshold->maxThreshold();
return opts;
}
@ -315,17 +323,28 @@ void DynamicsPopup::onValuesChange(ButtonSet::Item* item)
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));
if (needsSize && !m_dynamics->minSize()->isVisible()) {
m_dynamics->minSize()->setValue(1);
int maxSize = brush->size();
if (maxSize == 1) {
// If brush size == 1, we put it to 4 so the user has some size
// change by default.
maxSize = 4;
m_delegate->setMaxSize(maxSize);
}
m_dynamics->maxSize()->setValue(maxSize);
}
m_dynamics->maxSizeLabel()->setVisible(needsSize);
m_dynamics->sizeLabel()->setVisible(needsSize);
m_dynamics->minSize()->setVisible(needsSize);
m_dynamics->maxSize()->setVisible(needsSize);
if (needsAngle && !m_dynamics->maxAngle()->isVisible()) {
if (needsAngle && !m_dynamics->minAngle()->isVisible()) {
m_dynamics->minAngle()->setValue(brush->angle());
m_dynamics->maxAngle()->setValue(brush->angle());
}
m_dynamics->maxAngleLabel()->setVisible(needsAngle);
m_dynamics->angleLabel()->setVisible(needsAngle);
m_dynamics->minAngle()->setVisible(needsAngle);
m_dynamics->maxAngle()->setVisible(needsAngle);
m_dynamics->gradientLabel()->setVisible(needsGradient);
@ -358,12 +377,14 @@ bool DynamicsPopup::onProcessMessage(Message* msg)
m_hotRegion = gfx::Region(bounds());
setHotRegion(m_hotRegion);
manager()->addMessageFilter(kMouseMoveMessage, this);
manager()->addMessageFilter(kMouseDownMessage, this);
disableFlags(IGNORE_MOUSE);
break;
case kCloseMessage:
m_hotRegion.clear();
manager()->removeMessageFilter(kMouseMoveMessage, this);
manager()->removeMessageFilter(kMouseDownMessage, this);
break;
case kMouseEnterMessage:
@ -376,7 +397,7 @@ bool DynamicsPopup::onProcessMessage(Message* msg)
if (mouseMsg->pointerType() == PointerType::Pen ||
mouseMsg->pointerType() == PointerType::Eraser) {
if (m_dynamics->pressurePlaceholder()->isVisible()) {
m_pressureTweaks->setSensorValue(mouseMsg->pressure());
m_pressureThreshold->setSensorValue(mouseMsg->pressure());
}
}
@ -387,7 +408,18 @@ bool DynamicsPopup::onProcessMessage(Message* msg)
/ tools::VelocitySensor::kScreenPixelsForFullVelocity;
v = base::clamp(v, 0.0f, 1.0f);
m_velocityTweaks->setSensorValue(v);
m_velocityThreshold->setSensorValue(v);
}
break;
}
case kMouseDownMessage: {
auto mouseMsg = static_cast<MouseMessage*>(msg);
auto picked = manager()->pick(mouseMsg->position());
if ((picked == nullptr) ||
(picked->window() != this &&
picked->window() != m_ditheringSel->getWindowWidget())) {
closeWindow(nullptr);
}
break;
}

View File

@ -29,13 +29,15 @@ namespace app {
public:
virtual ~Delegate() { }
virtual doc::BrushRef getActiveBrush() = 0;
virtual void setMaxSize(int size) = 0;
virtual void setMaxAngle(int angle) = 0;
};
DynamicsPopup(Delegate* delegate);
tools::DynamicsOptions getDynamics() const;
private:
class MinMaxSlider;
class ThresholdSlider;
void setCheck(int i, bool state);
bool isCheck(int i) const;
@ -46,8 +48,8 @@ namespace app {
gen::Dynamics* m_dynamics;
DitheringSelector* m_ditheringSel;
gfx::Region m_hotRegion;
MinMaxSlider* m_pressureTweaks;
MinMaxSlider* m_velocityTweaks;
ThresholdSlider* m_pressureThreshold;
ThresholdSlider* m_velocityThreshold;
tools::VelocitySensor m_velocity;
};

View File

@ -69,7 +69,8 @@ struct app::skin::SkinTheme::BackwardCompatibility {
}
void createMissingStyles(SkinTheme* theme) {
if (!hasSliderStyle &&
theme->styles.slider()) {
theme->styles.slider() &&
theme->styles.miniSlider()) {
// Old slider style
ui::Style style(nullptr);
os::Font* font = theme->getDefaultFont();
@ -86,6 +87,7 @@ struct app::skin::SkinTheme::BackwardCompatibility {
part->bitmapS()->height()-1*guiscale()+h/2));
*theme->styles.slider() = style;
*theme->styles.miniSlider() = style;
}
}
};

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -81,6 +81,7 @@ namespace ui {
Entry* getEntryWidget();
Button* getButtonWidget();
Window* getWindowWidget() { return m_window; }
void openListBox();
void closeListBox();