Magic tool Object mode (Compositor 1.1.8)
This commit is contained in:
1 parent
78bfa2445d
commit
3d56688ce4
39 files changed
+1193
-134
No files matched your search
@@ -140,7 +140,7 @@ const std::vector<ShortcutDefinition> &ShortcutDefinition::all()
|
||||
const std::vector<std::pair<const char *, QString>> tools{
|
||||
{"Select tool", "a"}, {"Move / Transform tool", "v"}, {"Hand tool", "h"}, {"Zoom tool", "z"}, {"Brush tool", "b"},
|
||||
{"Eraser", "e"}, {"Spot Healing", "j"}, {"Clone Stamp", "s"}, {"Type tool", "t"}, {"Gradient tool", "g"},
|
||||
{"Shape tool", "u"}, {"Eyedropper tool", "i"}, {"Marquee / cycle shape", "m"}, {"Magic Wand", "w"},
|
||||
{"Shape tool", "u"}, {"Eyedropper tool", "i"}, {"Marquee / cycle shape", "m"}, {"Magic", "w"},
|
||||
{"Lasso / cycle mode", "l"}, {"Blur / Smudge / Liquify", "r"}, {"Crop tool", "c"}, {"Swap foreground/background", "x"},
|
||||
{"Reset colors", "d"}, {"Cycle tool mode", "\t"}, {"Temporary Hand tool (hold)", " "},
|
||||
{"Delete selection / layer / effect / lasso point", backspace}, {"Apply current canvas operation", "\r"},
|
||||
|
||||
+60
-18
@@ -17,15 +17,15 @@ SelectionAmountField::SelectionAmountField(EditorSession &session, int low, int
|
||||
setFont(ToolHeaderStyle::controlFont());
|
||||
setFixedWidth(40);
|
||||
setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
// Digits only; the bounds clamp, as Swift's binding does.
|
||||
setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]*")), this));
|
||||
// Typing applies at once; an overflow reads as the top.
|
||||
// Digits, signed below zero; the bounds clamp, as Swift's binding.
|
||||
setValidator(new QRegularExpressionValidator(QRegularExpression(low < 0 ? QStringLiteral("-?[0-9]*") : QStringLiteral("[0-9]*")), this));
|
||||
// Typing applies at once; an overflow reads as its end.
|
||||
connect(this, &QLineEdit::textEdited, this, [this](const QString &text) {
|
||||
if (text.isEmpty())
|
||||
if (text.isEmpty() || text == QLatin1String("-"))
|
||||
return;
|
||||
bool fits = false;
|
||||
const qlonglong typed = text.toLongLong(&fits);
|
||||
m_change(double(fits ? std::clamp<qlonglong>(typed, m_low, m_high) : m_high));
|
||||
m_change(double(fits ? std::clamp<qlonglong>(typed, m_low, m_high) : text.startsWith(QLatin1Char('-')) ? m_low : m_high));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,7 +95,23 @@ LassoControls::LassoControls(EditorSession &session, QWidget *parent)
|
||||
m_allLayers(choice(m_sources, QStringLiteral("wandAllLayers"), QStringLiteral("All Layers"),
|
||||
QStringLiteral("Read colors from the active layer only, or from every visible layer as shown"),
|
||||
[this] { changeWand([](WandSettings &wand) { wand.sampleAllLayers = true; }); })),
|
||||
m_contiguous(new QCheckBox(QStringLiteral("Contiguous"), this)),
|
||||
m_contiguous(new QCheckBox(QStringLiteral("Contiguous"), this)), m_wandModes(new QButtonGroup(this)),
|
||||
m_wandMode(choice(m_wandModes, QStringLiteral("magicWandMode"), rawValue(WandMode::wand), QStringLiteral("Press Tab to switch between Wand and Object"),
|
||||
[this] { m_session.setWandMode(WandMode::wand); })),
|
||||
m_objectMode(choice(m_wandModes, QStringLiteral("magicObjectMode"), rawValue(WandMode::object), QStringLiteral("Press Tab to switch between Wand and Object"),
|
||||
[this] { m_session.setWandMode(WandMode::object); })),
|
||||
m_objectSources(new QButtonGroup(this)),
|
||||
m_objectThisLayer(choice(m_objectSources, QStringLiteral("objectThisLayer"), QStringLiteral("This Layer"),
|
||||
QStringLiteral("Analyze the active layer only, or every visible layer as shown"),
|
||||
[this] { changeObject([](ObjectSelectionSettings &object) { object.sampleAllLayers = false; }); })),
|
||||
m_objectAllLayers(choice(m_objectSources, QStringLiteral("objectAllLayers"), QStringLiteral("All Layers"),
|
||||
QStringLiteral("Analyze the active layer only, or every visible layer as shown"),
|
||||
[this] { changeObject([](ObjectSelectionSettings &object) { object.sampleAllLayers = true; }); })),
|
||||
m_edgeLabel(new QLabel(QStringLiteral("Edge"), this)),
|
||||
m_edge(new SelectionAmountField(session, -10, 10, [this] { return double(m_session.objectSelectionSettings().edgeOffset); },
|
||||
[this](double edge) { changeObject([edge](ObjectSelectionSettings &object) { object.edgeOffset = int(std::lround(edge)); }); },
|
||||
this)),
|
||||
m_edgeUnit(new QLabel(QStringLiteral("px"), this)),
|
||||
m_antialias(new QCheckBox(QStringLiteral("Anti-alias"), this)), m_expand(new QPushButton(QStringLiteral("Expand"), this)),
|
||||
m_expandAmount(new SelectionAmountField(session, 1, 500, [this] { return double(m_session.selectionExpandAmount()); },
|
||||
[this](double amount) { m_session.setSelectionExpandAmount(int(std::lround(amount))); }, this)),
|
||||
@@ -119,12 +135,16 @@ LassoControls::LassoControls(EditorSession &session, QWidget *parent)
|
||||
connect(m_sampleSize, &QComboBox::activated, this, [this](int index) {
|
||||
changeWand([index](WandSettings &wand) { wand.sampleSize = WandSampleSize(index); });
|
||||
});
|
||||
m_edge->setObjectName(QStringLiteral("objectEdge"));
|
||||
for (QWidget *edge : std::initializer_list<QWidget *>{m_edgeLabel, m_edge, m_edgeUnit})
|
||||
edge->setToolTip(QStringLiteral("Positive values tighten the detected mask inward; negative values expand it outward"));
|
||||
m_edgeLabel->setObjectName(QStringLiteral("objectEdgeLabel"));
|
||||
m_edgeLabel->setFont(ToolHeaderStyle::controlFont());
|
||||
m_contiguous->setObjectName(QStringLiteral("wandContiguous"));
|
||||
m_contiguous->setToolTip(QStringLiteral("Select only similar pixels connected to the one you click; off selects them everywhere"));
|
||||
connect(m_contiguous, &QCheckBox::toggled, this, [this](bool on) { changeWand([on](WandSettings &wand) { wand.contiguous = on; }); });
|
||||
// Rectangles snap to whole pixels, so smoothing applies elsewhere.
|
||||
m_antialias->setObjectName(QStringLiteral("selectionAntialiased"));
|
||||
m_antialias->setToolTip(QStringLiteral("Smooth selection edges; turn off for hard pixel edges"));
|
||||
connect(m_antialias, &QCheckBox::toggled, this, [this](bool on) { m_session.setSelectionAntialiased(on); });
|
||||
auto *divider = new QFrame(this);
|
||||
divider->setFrameShape(QFrame::VLine);
|
||||
@@ -150,8 +170,9 @@ LassoControls::LassoControls(EditorSession &session, QWidget *parent)
|
||||
m_deselect->setObjectName(QStringLiteral("deselect"));
|
||||
connect(m_deselect, &QPushButton::clicked, this, [this] { m_session.deselect(); });
|
||||
// Swift's `unitSuffix`: a unit sits by each field.
|
||||
for (QWidget *widget : std::initializer_list<QWidget *>{m_rectangle, m_ellipse, m_freehand, m_polygonal, m_replace, m_add, m_subtract, m_toleranceLabel,
|
||||
m_tolerance, m_sampleSize, m_thisLayer, m_allLayers, m_contiguous, m_antialias,
|
||||
for (QWidget *widget : std::initializer_list<QWidget *>{m_rectangle, m_ellipse, m_wandMode, m_objectMode, m_freehand, m_polygonal, m_replace, m_add,
|
||||
m_subtract, m_toleranceLabel, m_tolerance, m_sampleSize, m_thisLayer, m_allLayers, m_contiguous,
|
||||
m_objectThisLayer, m_objectAllLayers, m_edgeLabel, m_edge, m_edgeUnit, m_antialias,
|
||||
divider, m_expand, m_expandAmount, new QLabel(QStringLiteral("px"), this), m_contract, m_contractAmount,
|
||||
new QLabel(QStringLiteral("px"), this), m_feather, m_featherAmount,
|
||||
new QLabel(QStringLiteral("px"), this)})
|
||||
@@ -170,6 +191,14 @@ void LassoControls::changeWand(const std::function<void(WandSettings &)> &change
|
||||
m_session.setWandSettings(wand);
|
||||
}
|
||||
|
||||
// One field of Object mode's settings changes; the rest stay.
|
||||
void LassoControls::changeObject(const std::function<void(ObjectSelectionSettings &)> &change)
|
||||
{
|
||||
ObjectSelectionSettings object = m_session.objectSelectionSettings();
|
||||
change(object);
|
||||
m_session.setObjectSelectionSettings(object);
|
||||
}
|
||||
|
||||
// A segment of Swift's segmented picker: one checkable button.
|
||||
QToolButton *LassoControls::choice(QButtonGroup *group, const QString &name, const QString &text, const QString &tip, const std::function<void()> &pick)
|
||||
{
|
||||
@@ -188,7 +217,18 @@ QToolButton *LassoControls::choice(QButtonGroup *group, const QString &name, con
|
||||
void LassoControls::synchronize()
|
||||
{
|
||||
const NavigationTool tool = m_session.tool();
|
||||
title->setText(tool == NavigationTool::marquee ? QStringLiteral("Marquee") : tool == NavigationTool::wand ? QStringLiteral("Magic Wand") : QStringLiteral("Lasso"));
|
||||
title->setText(tool == NavigationTool::marquee ? QStringLiteral("Marquee") : tool == NavigationTool::wand ? QStringLiteral("Magic") : QStringLiteral("Lasso"));
|
||||
const bool wand = tool == NavigationTool::wand;
|
||||
const bool object = wand && m_session.wandMode() == WandMode::object;
|
||||
for (QToolButton *button : {m_wandMode, m_objectMode})
|
||||
button->setVisible(wand);
|
||||
m_wandMode->setChecked(!object);
|
||||
m_objectMode->setChecked(object);
|
||||
for (QWidget *widget : std::initializer_list<QWidget *>{m_objectThisLayer, m_objectAllLayers, m_edgeLabel, m_edge, m_edgeUnit})
|
||||
widget->setVisible(object);
|
||||
m_objectThisLayer->setChecked(!m_session.objectSelectionSettings().sampleAllLayers);
|
||||
m_objectAllLayers->setChecked(m_session.objectSelectionSettings().sampleAllLayers);
|
||||
m_edge->sync(m_session.objectSelectionSettings().edgeOffset);
|
||||
for (QToolButton *button : {m_rectangle, m_ellipse})
|
||||
button->setVisible(tool == NavigationTool::marquee);
|
||||
for (QToolButton *button : {m_freehand, m_polygonal})
|
||||
@@ -202,16 +242,18 @@ void LassoControls::synchronize()
|
||||
m_replace->setChecked(mode == SelectionMode::replace);
|
||||
m_add->setChecked(mode == SelectionMode::add);
|
||||
m_subtract->setChecked(mode == SelectionMode::subtract);
|
||||
const WandSettings &wand = m_session.wandSettings();
|
||||
const WandSettings &colours = m_session.wandSettings();
|
||||
for (QWidget *widget : std::initializer_list<QWidget *>{m_toleranceLabel, m_tolerance, m_sampleSize, m_thisLayer, m_allLayers, m_contiguous})
|
||||
widget->setVisible(tool == NavigationTool::wand);
|
||||
m_tolerance->sync(wand.tolerance);
|
||||
m_sampleSize->setCurrentIndex(int(wand.sampleSize));
|
||||
m_thisLayer->setChecked(!wand.sampleAllLayers);
|
||||
m_allLayers->setChecked(wand.sampleAllLayers);
|
||||
if (m_contiguous->isChecked() != wand.contiguous)
|
||||
m_contiguous->setChecked(wand.contiguous);
|
||||
widget->setVisible(wand && !object);
|
||||
m_tolerance->sync(colours.tolerance);
|
||||
m_sampleSize->setCurrentIndex(int(colours.sampleSize));
|
||||
m_thisLayer->setChecked(!colours.sampleAllLayers);
|
||||
m_allLayers->setChecked(colours.sampleAllLayers);
|
||||
if (m_contiguous->isChecked() != colours.contiguous)
|
||||
m_contiguous->setChecked(colours.contiguous);
|
||||
m_antialias->setVisible(tool == NavigationTool::lasso || tool == NavigationTool::wand || (tool == NavigationTool::marquee && m_session.marqueeKind() == LassoKind::ellipse));
|
||||
m_antialias->setToolTip(object ? QStringLiteral("Smooth the detected object outline; turn off for the raw pixel mask")
|
||||
: QStringLiteral("Smooth selection edges; turn off for hard pixel edges"));
|
||||
if (m_antialias->isChecked() != m_session.selectionAntialiased())
|
||||
m_antialias->setChecked(m_session.selectionAntialiased());
|
||||
const bool modifies = m_session.canModifySelection();
|
||||
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
private:
|
||||
QToolButton *choice(QButtonGroup *group, const QString &name, const QString &text, const QString &tip, const std::function<void()> &pick);
|
||||
void changeWand(const std::function<void(WandSettings &)> &change);
|
||||
void changeObject(const std::function<void(ObjectSelectionSettings &)> &change);
|
||||
void synchronize();
|
||||
|
||||
EditorSession &m_session;
|
||||
@@ -66,6 +67,16 @@ private:
|
||||
QToolButton *const m_thisLayer;
|
||||
QToolButton *const m_allLayers;
|
||||
QCheckBox *const m_contiguous;
|
||||
// The Magic tool's modes, and Object mode's own settings.
|
||||
QButtonGroup *const m_wandModes;
|
||||
QToolButton *const m_wandMode;
|
||||
QToolButton *const m_objectMode;
|
||||
QButtonGroup *const m_objectSources;
|
||||
QToolButton *const m_objectThisLayer;
|
||||
QToolButton *const m_objectAllLayers;
|
||||
QLabel *const m_edgeLabel;
|
||||
SelectionAmountField *const m_edge;
|
||||
QLabel *const m_edgeUnit;
|
||||
QCheckBox *const m_antialias;
|
||||
QPushButton *const m_expand;
|
||||
SelectionAmountField *const m_expandAmount;
|
||||
|
||||
@@ -187,7 +187,7 @@ void NativeLayerList::pressKey(QKeyEvent *event)
|
||||
static const std::map<int, NavigationTool> tools{
|
||||
{Qt::Key_A, NavigationTool::idle}, {Qt::Key_V, NavigationTool::move}, {Qt::Key_H, NavigationTool::hand},
|
||||
{Qt::Key_Z, NavigationTool::zoom}, {Qt::Key_G, NavigationTool::gradient}, {Qt::Key_L, NavigationTool::lasso}, {Qt::Key_M, NavigationTool::marquee},
|
||||
{Qt::Key_W, NavigationTool::wand}, {Qt::Key_J, NavigationTool::spotHealing}, {Qt::Key_S, NavigationTool::cloneStamp},
|
||||
{Qt::Key_J, NavigationTool::spotHealing}, {Qt::Key_S, NavigationTool::cloneStamp},
|
||||
{Qt::Key_U, NavigationTool::shape}, {Qt::Key_R, NavigationTool::blur}, {Qt::Key_I, NavigationTool::eyedropper},
|
||||
{Qt::Key_C, NavigationTool::crop}, {Qt::Key_T, NavigationTool::type}};
|
||||
if (plain && (key == Qt::Key_Backspace || key == Qt::Key_Delete)) {
|
||||
@@ -209,6 +209,10 @@ void NativeLayerList::pressKey(QKeyEvent *event)
|
||||
m_session.swapPaletteColors();
|
||||
else
|
||||
m_session.resetPaletteColors();
|
||||
} else if (plain && key == Qt::Key_W) {
|
||||
// A held W picks the Magic tool once, as Swift's.
|
||||
if (!event->isAutoRepeat())
|
||||
m_session.pressWandKey();
|
||||
} else if (plain && tools.contains(key)) {
|
||||
m_session.selectTool(tools.at(key));
|
||||
} else if (plain && event->text().size() == 1 && event->text().front().isDigit() && m_session.usesOpacityKeys()) {
|
||||
|
||||
@@ -211,7 +211,7 @@ void ToolIcons::paint(QPainter &painter, NavigationTool tool, QPointF origin, do
|
||||
case NavigationTool::move: move(painter); break;
|
||||
case NavigationTool::marquee: marquee(painter, kind); break;
|
||||
case NavigationTool::lasso: lasso(painter, kind); break;
|
||||
case NavigationTool::wand: wand(painter); break;
|
||||
case NavigationTool::wand: kind == ToolIconKind::object ? SelectionIcons::paint(painter, SelectionIcon::objectSelection) : wand(painter); break;
|
||||
case NavigationTool::crop: crop(painter); break;
|
||||
case NavigationTool::brush: kind == ToolIconKind::eraser ? eraser(painter) : brush(painter); break;
|
||||
case NavigationTool::spotHealing: spotHealing(painter); break;
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
#include <QRectF>
|
||||
|
||||
// The Marquee's and the Lasso's icons follow their kinds.
|
||||
enum class ToolIconKind { plain, ellipse, polygonal, eraser };
|
||||
enum class ToolIconKind { plain, ellipse, polygonal, eraser, object };
|
||||
|
||||
// The tool rail's icons, drawn: SF Symbols have no twin.
|
||||
namespace ToolIcons {
|
||||
|
||||
Reference in new issue
Block a user