Export JPEG and Canvas Size take the app's colour picker; a panel opens over a modal sheet (13.4a)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-01 07:16:16 -04:00
1 parent 0b7eb6cb6f
commit bc49b722eb
23 files changed
+470 -134

No files matched your search

+16 -25
View File
@@ -2,12 +2,11 @@
#include "Document/DocumentLimits.h"
#include "UI/KeyboardShortcuts.h"
#include "UI/ByteCounts.h"
#include "UI/ColorPaletteControls.h"
#include "UI/ColorPickerSheet+Dialog.h"
#include "UI/ColorPickerSheet.h"
#include "UI/NumericScrub.h"
#include <QButtonGroup>
#include <QCheckBox>
#include <QColorDialog>
#include <QComboBox>
#include <QFrame>
#include <QGridLayout>
@@ -109,13 +108,14 @@ protected:
};
}
CanvasSizeSheet::CanvasSizeSheet(const CanvasDocument &document, PaletteColor foreground, PaletteColor background,
std::function<void(std::optional<CanvasSizeOptions>)> finish, QWidget *parent)
: QWidget(parent), m_foreground(foreground), m_background(background), m_finish(std::move(finish)),
CanvasSizeSheet::CanvasSizeSheet(const CanvasDocument &document, EditorSession &session, std::function<void(std::optional<CanvasSizeOptions>)> finish,
QWidget *parent)
: QWidget(parent), m_session(session), m_foreground(session.foregroundColor()), m_background(session.backgroundColor()), m_finish(std::move(finish)),
m_draft(document.width, document.height, document.resolution), m_width(dimension(true)), m_height(dimension(false)),
m_note(text(QString(), 12, QFont::Normal, QPalette::PlaceholderText, this)),
m_anchors(new QButtonGroup(this)), m_anchorName(text(QString(), 12, QFont::Bold, QPalette::WindowText, this)), m_extension(new QComboBox(this)),
m_customRow(new QWidget(this)), m_customSwatch(new SwatchButton([this] { return m_custom; }, 3, 0, 0.5, m_customRow)),
m_customRow(new QWidget(this)), m_customSwatch(new DialogColorSwatch(
QStringLiteral("Extension Color"), [this] { return m_custom; }, [this](const PaletteColor &custom) { m_custom = custom; }, session, m_customRow)),
m_ok(new QPushButton(QStringLiteral("OK"), this))
{
setFixedWidth(450);
@@ -224,14 +224,13 @@ CanvasSizeSheet::CanvasSizeSheet(const CanvasDocument &document, PaletteColor fo
extension->addWidget(extensionLabel);
extension->addWidget(m_extension, 1);
column->addLayout(extension);
// Swift's ColorPicker: its label and a well, no opacity.
// Its label and the app's picker on a swatch.
m_customRow->setObjectName(QStringLiteral("extensionColorRow"));
m_customSwatch->setObjectName(QStringLiteral("extensionColor"));
m_customSwatch->setFixedSize(36, 18);
m_customSwatch->setAccessibleName(QStringLiteral("Extension color"));
connect(m_customSwatch, &QAbstractButton::clicked, this, &CanvasSizeSheet::pickCustomColor);
m_customSwatch->setToolTip(QStringLiteral("Color for the added canvas"));
auto *customLayout = new QHBoxLayout(m_customRow);
customLayout->setContentsMargins(0, 0, 0, 0);
customLayout->setSpacing(8);
customLayout->addWidget(new QLabel(QStringLiteral("Extension color"), m_customRow));
customLayout->addWidget(m_customSwatch);
customLayout->addStretch(1);
@@ -240,13 +239,18 @@ CanvasSizeSheet::CanvasSizeSheet(const CanvasDocument &document, PaletteColor fo
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
cancel->setObjectName(QStringLiteral("canvasCancel"));
cancel->setAutoDefault(false);
connect(cancel, &QPushButton::clicked, this, [this] { m_finish(std::nullopt); });
connect(cancel, &QPushButton::clicked, this, [this] {
DialogColorSwatch::closePicker(m_session);
m_finish(std::nullopt);
});
m_ok->setObjectName(QStringLiteral("canvasOK"));
m_ok->setDefault(true);
// Swift's configuredNativeShortcut: Return and Escape, as remapped.
NativeShortcut::bind(*this, m_ok, cancel);
// OK rests while the draft is invalid: no guard needed.
connect(m_ok, &QPushButton::clicked, this, [this] {
// An open picker's colour counts: it closes first.
DialogColorSwatch::closePicker(m_session);
m_finish(CanvasSizeOptions{.width = qint64(std::round(m_draft.width)), .height = qint64(std::round(m_draft.height)), .anchor = m_anchor,
.fill = fill()});
});
@@ -300,20 +304,6 @@ std::optional<CanvasExtensionColor> CanvasSizeSheet::fill() const
}
}
// The desktop's colour dialog, SwiftUI's colour panel, without alpha.
void CanvasSizeSheet::pickCustomColor()
{
auto *dialog = new QColorDialog(m_custom.color(), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(QStringLiteral("Extension color"));
connect(dialog, &QColorDialog::colorSelected, this, [this](const QColor &picked) {
const QColor srgb = picked.toRgb();
m_custom = PaletteColor{srgb.redF(), srgb.greenF(), srgb.blueF()};
m_customSwatch->update();
});
dialog->open();
}
// Units, Relative and Lock change only through their own controls.
void CanvasSizeSheet::synchronize()
{
@@ -347,4 +337,5 @@ void CanvasSizeSheet::synchronize()
CanvasSizeSheet::~CanvasSizeSheet()
{
releaseFocus(*this);
DialogColorSwatch::closePicker(m_session);
}
+6 -5
View File
@@ -12,14 +12,15 @@ class QButtonGroup;
class QComboBox;
class QLabel;
class QPushButton;
class SwatchButton;
class DialogColorSwatch;
class EditorSession;
// Swift's CanvasSizeSheet: the new size, the point kept, the extension.
class CanvasSizeSheet : public QWidget {
Q_OBJECT
public:
CanvasSizeSheet(const CanvasDocument &document, PaletteColor foreground, PaletteColor background,
std::function<void(std::optional<CanvasSizeOptions>)> finish, QWidget *parent = nullptr);
CanvasSizeSheet(const CanvasDocument &document, EditorSession &session, std::function<void(std::optional<CanvasSizeOptions>)> finish,
QWidget *parent = nullptr);
~CanvasSizeSheet() override;
private:
@@ -27,9 +28,9 @@ private:
// Swift's scrubbable Width or Height title.
NumericScrub *scrub(QLabel *title, bool widthAxis);
std::optional<CanvasExtensionColor> fill() const;
void pickCustomColor();
void synchronize();
EditorSession &m_session;
const PaletteColor m_foreground;
const PaletteColor m_background;
const std::function<void(std::optional<CanvasSizeOptions>)> m_finish;
@@ -45,6 +46,6 @@ private:
QLabel *const m_anchorName;
QComboBox *const m_extension;
QWidget *const m_customRow;
SwatchButton *const m_customSwatch;
DialogColorSwatch *const m_customSwatch;
QPushButton *const m_ok;
};
+37
View File
@@ -0,0 +1,37 @@
#include "UI/ColorPickerSheet+Dialog.h"
#include <QHideEvent>
// The brush's swatch: 34 by 18, a white ring.
DialogColorSwatch::DialogColorSwatch(const QString &title, std::function<PaletteColor()> colour, std::function<void(const PaletteColor &)> change,
EditorSession &session, QWidget *parent)
: SwatchButton(colour, 4, 1, 1, parent), m_session(session)
{
setFixedSize(34, 18);
setAccessibleName(title);
connect(this, &QAbstractButton::clicked, this, [this, title, colour, change, &session] {
session.openDialogColorPicker(title, colour(), [this, change](const PaletteColor &picked) {
change(picked);
update();
});
});
connect(&session, &EditorSession::changed, this, [this, &session] {
const std::optional<PaletteColor> now = session.colorPicker() ? std::optional(session.colorPicker()->color()) : std::nullopt;
if (now == m_seen)
return;
m_seen = now;
session.previewDialogColor();
});
}
void DialogColorSwatch::hideEvent(QHideEvent *event)
{
if (!event->spontaneous())
closePicker(m_session);
SwatchButton::hideEvent(event);
}
void DialogColorSwatch::closePicker(EditorSession &session)
{
if (session.pickingForDialog())
session.closeColorPicker(true);
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Document/EditorSession.h"
#include "UI/ColorPaletteControls.h"
// Swift's DialogColorSwatch: a dialog's colour in the app's picker.
class DialogColorSwatch : public SwatchButton {
Q_OBJECT
public:
DialogColorSwatch(const QString &title, std::function<PaletteColor()> colour, std::function<void(const PaletteColor &)> change, EditorSession &session,
QWidget *parent);
// Puts the picker away with the dialog, keeping its colour.
static void closePicker(EditorSession &session);
protected:
// Swift's onDisappear: the picker goes with the swatch.
void hideEvent(QHideEvent *event) override;
private:
EditorSession &m_session;
// Swift's onChange: the picker's colour as last seen.
std::optional<PaletteColor> m_seen;
};
+11 -7
View File
@@ -235,19 +235,23 @@ ColorPickerSheet::ColorPickerSheet(EditorSession &session, std::function<void(bo
m_hex->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
fields->addWidget(label(QStringLiteral("#"), this), 3, 0);
fields->addWidget(m_hex, 3, 1, Qt::AlignLeft);
auto *hint = new QLabel(QStringLiteral("Click the canvas to sample"), this);
QFont small = hint->font();
small.setPixelSize(10);
hint->setFont(small);
hint->setForegroundRole(QPalette::PlaceholderText);
auto *column = new QVBoxLayout;
column->setSpacing(0);
column->addLayout(top);
column->addStretch();
column->addSpacing(12);
column->addLayout(fields);
column->addSpacing(8);
column->addWidget(hint);
// A dialog covers the canvas: nothing to sample.
if (!m_session.pickingForDialog()) {
auto *hint = new QLabel(QStringLiteral("Click the canvas to sample"), this);
hint->setObjectName(QStringLiteral("pickerHint"));
QFont small = hint->font();
small.setPixelSize(10);
hint->setFont(small);
hint->setForegroundRole(QPalette::PlaceholderText);
column->addSpacing(8);
column->addWidget(hint);
}
auto *side = new QWidget(this);
side->setFixedSize(180, int(fieldSize));
side->setLayout(column);
+5 -1
View File
@@ -65,8 +65,12 @@ void FloatingPanel::show(const QString &title, QWidget *content, QWidget *dock)
showDocked(title, content, *dock);
return;
}
// A modal sheet blocks its window's panels, not its own.
QWidget *parent = QApplication::activeModalWidget() ? QApplication::activeModalWidget() : m_owner.window();
if (m_panel && m_panel->parentWidget() != parent)
m_panel->setParent(parent, m_panel->windowFlags());
if (!m_panel) {
m_panel = new PanelWindow(*this, m_owner.window());
m_panel = new PanelWindow(*this, parent);
m_panel->setObjectName(m_name);
}
const bool wasVisible = m_panel->isVisible();
+25 -29
View File
@@ -1,8 +1,7 @@
#include "UI/JPEGExportSheet.h"
#include "UI/KeyboardShortcuts.h"
#include "UI/ByteCounts.h"
#include "UI/ColorPaletteControls.h"
#include <QColorDialog>
#include "UI/ColorPickerSheet+Dialog.h"
#include <QFutureWatcher>
#include <QHBoxLayout>
#include <QLabel>
@@ -83,10 +82,12 @@ protected:
}
};
JPEGExportSheet::JPEGExportSheet(ExportRaster raster, std::function<void(std::optional<QByteArray>)> finish, QWidget *parent)
: QWidget(parent), m_raster(std::move(raster)), m_finish(std::move(finish)), m_wait(new QTimer(this)), m_preview(new Preview(this)),
JPEGExportSheet::JPEGExportSheet(ExportRaster raster, EditorSession &session, std::function<void(std::optional<QByteArray>)> finish, QWidget *parent)
: QWidget(parent), m_raster(std::move(raster)), m_session(session), m_finish(std::move(finish)), m_wait(new QTimer(this)), m_preview(new Preview(this)),
m_spinner(new QProgressBar(m_preview)), m_quality(new QSlider(Qt::Horizontal, this)),
m_percent(text(QString(), 13, QFont::Normal, QPalette::WindowText, this)), m_matteSwatch(new SwatchButton([this] { return PaletteColor{m_options.red, m_options.green, m_options.blue}; }, 3, 0, 0.5, this)),
m_percent(text(QString(), 13, QFont::Normal, QPalette::WindowText, this)), m_matteSwatch(new DialogColorSwatch(
QStringLiteral("JPEG Background"), [this] { return PaletteColor{m_options.red, m_options.green, m_options.blue}; },
[this](const PaletteColor &matte) { setMatte(matte); }, session, this)),
m_failure(text(QString(), 13, QFont::Normal, QPalette::BrightText, this)), m_bytes(text(QString(), 13, QFont::Normal, QPalette::WindowText, this)),
m_note(text(QString(), 13, QFont::Normal, QPalette::PlaceholderText, this)), m_export(new QPushButton(QStringLiteral("Export…"), this))
{
@@ -122,13 +123,12 @@ JPEGExportSheet::JPEGExportSheet(ExportRaster raster, std::function<void(std::op
quality->addWidget(m_percent);
column->addLayout(quality);
// Swift's ColorPicker: its label and a well, no opacity.
// Its label and the app's picker on a swatch.
auto *matte = new QHBoxLayout;
matte->setSpacing(8);
matte->addWidget(new QLabel(QStringLiteral("Background for transparency"), this));
m_matteSwatch->setObjectName(QStringLiteral("jpegMatte"));
m_matteSwatch->setFixedSize(36, 18);
m_matteSwatch->setAccessibleName(QStringLiteral("Background for transparency"));
connect(m_matteSwatch, &QAbstractButton::clicked, this, &JPEGExportSheet::pickMatte);
m_matteSwatch->setToolTip(QStringLiteral("Color that fills transparent areas"));
matte->addWidget(m_matteSwatch);
matte->addStretch(1);
column->addLayout(matte);
@@ -146,13 +146,17 @@ JPEGExportSheet::JPEGExportSheet(ExportRaster raster, std::function<void(std::op
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
cancel->setObjectName(QStringLiteral("jpegCancel"));
cancel->setAutoDefault(false);
connect(cancel, &QPushButton::clicked, this, [this] { m_finish(std::nullopt); });
connect(cancel, &QPushButton::clicked, this, [this] {
DialogColorSwatch::closePicker(m_session);
m_finish(std::nullopt);
});
m_export->setObjectName(QStringLiteral("jpegExport"));
m_export->setDefault(true);
// Swift's configuredNativeShortcut: Return and Escape, as remapped.
NativeShortcut::bind(*this, m_export, cancel);
// Export rests until a result is ready.
connect(m_export, &QPushButton::clicked, this, [this] {
DialogColorSwatch::closePicker(m_session);
QSettings().setValue(qualityKey, m_options.quality);
m_finish(m_result.value().data);
});
@@ -172,6 +176,7 @@ JPEGExportSheet::JPEGExportSheet(ExportRaster raster, std::function<void(std::op
JPEGExportSheet::~JPEGExportSheet()
{
m_cancelled->store(true);
DialogColorSwatch::closePicker(m_session);
}
// Swift's task(id:): the last request stops; this waits 200 ms.
@@ -215,26 +220,17 @@ void JPEGExportSheet::encode()
}));
}
// The desktop's colour dialog, SwiftUI's colour panel, without alpha.
void JPEGExportSheet::pickMatte()
void JPEGExportSheet::setMatte(const PaletteColor &matte)
{
auto *dialog = new QColorDialog(QColor::fromRgbF(float(m_options.red), float(m_options.green), float(m_options.blue)), this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(QStringLiteral("Background for transparency"));
connect(dialog, &QColorDialog::colorSelected, this, [this](const QColor &picked) {
const QColor srgb = picked.toRgb();
JPEGOptions next = m_options;
next.red = srgb.redF();
next.green = srgb.greenF();
next.blue = srgb.blueF();
// Swift's onChange: the same colour asks nothing.
if (next == m_options)
return;
m_options = next;
m_matteSwatch->update();
request();
});
dialog->open();
JPEGOptions next = m_options;
next.red = matte.red;
next.green = matte.green;
next.blue = matte.blue;
// Equal options ask nothing, as Swift's task id.
if (next == m_options)
return;
m_options = next;
request();
}
void JPEGExportSheet::synchronize()
+6 -4
View File
@@ -12,7 +12,8 @@ class QProgressBar;
class QPushButton;
class QSlider;
class QTimer;
class SwatchButton;
class DialogColorSwatch;
class EditorSession;
// Swift's JPEGExportSheet: quality, matte and the encoded preview.
class JPEGExportSheet : public QWidget {
@@ -21,17 +22,18 @@ public:
// The last export's quality, where the next one starts.
static const QString qualityKey;
JPEGExportSheet(ExportRaster raster, std::function<void(std::optional<QByteArray>)> finish, QWidget *parent = nullptr);
JPEGExportSheet(ExportRaster raster, EditorSession &session, std::function<void(std::optional<QByteArray>)> finish, QWidget *parent = nullptr);
~JPEGExportSheet() override;
private:
class Preview;
void request();
void encode();
void pickMatte();
void setMatte(const PaletteColor &matte);
void synchronize();
const ExportRaster m_raster;
EditorSession &m_session;
const std::function<void(std::optional<QByteArray>)> m_finish;
JPEGOptions m_options;
std::optional<JPEGResult> m_result;
@@ -44,7 +46,7 @@ private:
QProgressBar *const m_spinner;
QSlider *const m_quality;
QLabel *const m_percent;
SwatchButton *const m_matteSwatch;
DialogColorSwatch *const m_matteSwatch;
QLabel *const m_failure;
QLabel *const m_bytes;
QLabel *const m_note;