14.9: Filter › Last Filter on Ctrl+Meta+F

This commit is contained in:
ZacharyZhang-NY committed 2026-10-11 06:28:14 -04:00
1 parent 0136d9d41a
commit 886b18dd7f
14 files changed
+306 -9

No files matched your search

+4 -2
View File
@@ -52,8 +52,10 @@ void ContentView::synchronizePanels()
m_colorRangePanel.close();
}
}
if (m_filterShown != m_session.filterEdit().has_value()) {
m_filterShown = m_session.filterEdit().has_value();
// Last Filter applies without the panel.
const bool filtering = m_session.filterEdit() && !m_session.filterEdit()->repeating;
if (m_filterShown != filtering) {
m_filterShown = filtering;
if (m_filterShown) {
m_filterPanel.onClose = [this] { m_session.cancelFilter(); };
const FilterKind kind = m_session.filterEdit()->kind;
+1
View File
@@ -441,6 +441,7 @@ private:
QFutureWatcher<RasterMade> m_rasterCommit;
std::optional<FilterEdit> m_filterEdit;
FilterSettings m_filterSettings;
std::optional<FilterKind> m_lastFilter;
QFutureWatcher<Filtered> m_filterPreview;
// Out until its finish runs: setFuture drops a pending signal.
bool m_filterRendering = false;
+49 -2
View File
@@ -16,7 +16,7 @@ bool EditorSession::canContentAwareFill() const
return canAdjustColors() && selection().has_value() && !m_hueSaturation;
}
void EditorSession::beginFilter(FilterKind kind)
void EditorSession::beginFilter(FilterKind kind, bool repeating)
{
if (kind == FilterKind::contentAwareFill && !canContentAwareFill())
return;
@@ -61,7 +61,9 @@ void EditorSession::beginFilter(FilterKind kind)
if (fillsCanvas)
m_filterEdit->canvas = canvas;
m_filterEdit->startedEmpty = startedEmpty;
updateFilter(m_filterEdit->settings, true);
m_filterEdit->repeating = repeating;
// An automatic filter commits its preview, so needs one.
updateFilter(m_filterEdit->settings, !repeating || isAutomatic(kind));
} catch (const ProjectError &error) {
setBrushError(QString::fromUtf8(error.what()));
} catch (const ExportError &error) {
@@ -69,6 +71,49 @@ void EditorSession::beginFilter(FilterKind kind)
}
}
void EditorSession::setLastFilter(std::optional<FilterKind> kind)
{
m_lastFilter = kind;
notify();
}
bool EditorSession::canRepeatLastFilter() const
{
if (!m_lastFilter || m_filterEdit || m_hueSaturation)
return false;
return m_lastFilter == FilterKind::vignette ? canVignette() : canAdjustColors();
}
void EditorSession::repeatLastFilter(std::function<void()> done)
{
if (!canRepeatLastFilter()) {
qCWarning(lcApp) << "no last filter to run here";
if (done)
QMetaObject::invokeMethod(this, done, Qt::QueuedConnection);
return;
}
const FilterKind kind = m_lastFilter.value();
const auto run = [this, kind, done] {
beginFilter(kind, true);
if (!m_filterEdit) {
if (done)
QMetaObject::invokeMethod(this, done, Qt::QueuedConnection);
return;
}
// Nothing applied: no panel is left to close it.
commitFilter([this, id = m_filterEdit->id, done] {
if (m_filterEdit && m_filterEdit->id == id)
cancelFilter();
if (done)
done();
});
};
if (m_gradientEdit)
commitGradient(run);
else
run();
}
void EditorSession::updateFilter(const FilterSettings &settings, bool preview)
{
if (!m_filterEdit || m_filterEdit->committing)
@@ -243,6 +288,8 @@ void EditorSession::commitFilter(std::function<void()> done)
m_filterPreviewFor = QUuid();
if (edit.kind != FilterKind::cameraRaw)
m_filterSettings = edit.settings;
if (repeatsAsLastFilter(edit.kind))
m_lastFilter = edit.kind;
setIsProjectBusy(true);
m_committingFilter = Committing{edit.id, std::move(done)};
// A mask hides the background, which can come back.
+8 -1
View File
@@ -1,7 +1,14 @@
// Swift's Filters extension: one filter at a time.
public:
bool canContentAwareFill() const;
void beginFilter(FilterKind kind);
// `repeating` is Last Filter: no preview unless automatic, no panel.
void beginFilter(FilterKind kind, bool repeating = false);
// What Filter › Last Filter runs again.
std::optional<FilterKind> lastFilter() const { return m_lastFilter; }
void setLastFilter(std::optional<FilterKind> kind);
bool canRepeatLastFilter() const;
// The last filter again, its settings kept, one step.
void repeatLastFilter(std::function<void()> done = {});
// The settings normalized; Preview off drops the queue and preview.
void updateFilter(const FilterSettings &settings, bool preview);
void cancelFilter();
+5
View File
@@ -53,6 +53,11 @@ QString rawValue(BackgroundQuality quality)
return quality == BackgroundQuality::basic ? QStringLiteral("Basic") : QStringLiteral("Advanced");
}
bool repeatsAsLastFilter(FilterKind kind)
{
return kind != FilterKind::contentAwareFill && !isImageAdjustment(kind);
}
bool isImageAdjustment(FilterKind kind)
{
return kind == FilterKind::curves || kind == FilterKind::exposure || kind == FilterKind::gradientMap || kind == FilterKind::grain
+4
View File
@@ -26,6 +26,8 @@ QString rawValue(FilterKind kind);
bool isAutomatic(FilterKind kind);
// The Image menu's colour adjustments, not under Filter.
bool isImageAdjustment(FilterKind kind);
// The Filter menu's own, which Last Filter runs again.
bool repeatsAsLastFilter(FilterKind kind);
// Swift's allCases, the menus' order.
inline constexpr std::array allFilterKinds{FilterKind::gaussianBlur, FilterKind::motionBlur, FilterKind::addNoise,
FilterKind::vignette, FilterKind::bloomGlow, FilterKind::dither,
@@ -183,6 +185,8 @@ public:
std::optional<QRectF> canvas;
// The layer had no pixels: the filter began clear.
bool startedEmpty = false;
// Last Filter: applied at once, without preview or panel.
bool repeating = false;
private:
void grow(const QRectF &extent);
+6
View File
@@ -225,6 +225,10 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, Pro
add(image, QStringLiteral("flipCanvasVertical"), QStringLiteral("Flip Canvas Vertical"), QKeySequence(), [this] { session().flipCanvas(false); });
QMenu *filter = bar.addMenu(QStringLiteral("Fil&ter"));
// Ctrl+Meta+F, Swift's Cmd+Control: Ctrl+F searches commands.
add(filter, QStringLiteral("lastFilter"), QStringLiteral("Last Filter"), QKeySequence(Qt::CTRL | Qt::META | Qt::Key_F),
[this] { session().repeatLastFilter(); });
filter->addSeparator();
for (const FilterKind kind : allFilterKinds) {
if (kind != FilterKind::contentAwareFill && !isImageAdjustment(kind))
add(filter, filterName(kind), rawValue(kind) + QStringLiteral("…"), QKeySequence(), [this, kind] { session().beginFilter(kind); });
@@ -387,6 +391,8 @@ void CompositorMenus::synchronize()
for (const char *name : {"expandSelection", "contractSelection", "featherSelection"})
action(QString::fromLatin1(name))->setEnabled(s.canModifySelection());
action(QStringLiteral("levels"))->setEnabled(s.canAdjustColors() && !s.hueSaturation());
action(QStringLiteral("lastFilter"))->setText(s.lastFilter() ? QStringLiteral("Last Filter: ") + rawValue(s.lastFilter().value()) : QStringLiteral("Last Filter"));
action(QStringLiteral("lastFilter"))->setEnabled(s.canRepeatLastFilter());
for (const FilterKind kind : allFilterKinds) {
if (kind != FilterKind::contentAwareFill)
action(filterName(kind))->setEnabled((kind == FilterKind::vignette ? s.canVignette() : s.canAdjustColors()) && !s.hueSaturation());
+1 -1
View File
@@ -130,7 +130,7 @@ const std::vector<ShortcutDefinition> &ShortcutDefinition::all()
entry("Copy", "c", 1, true), entry("Copy Merged", "c", 9, true), entry("Paste", "v", 1, true),
entry("Fill with Foreground", backspace, 2, true), entry("Fill with Background", backspace, 1, true),
entry("Content-Aware Fill", backspace, 8, true), entry("Select All", "a", 1, true), entry("Deselect", "d", 1, true),
entry("Inverse Selection", "i", 9, true), entry("Select Subject", "a", 3, true), entry("Curves", "m", 1, true),
entry("Inverse Selection", "i", 9, true), entry("Select Subject", "a", 3, true), entry("Last Filter", "f", 5, true), entry("Curves", "m", 1, true),
entry("Levels", "l", 1, true), entry("Hue/Saturation", "u", 1, true), entry("Invert Pixels / Mask", "i", 1, true),
entry("Canvas Size", "c", 3, true), entry("Image Size", "i", 3, true), entry("Transform Layer / Selection", "t", 1, true),
entry("Duplicate / Layer via Copy", "j", 1, true), entry("Toggle Clipping Mask", "g", 3, true),