Export JPEG: a zoomable full-size preview, its buttons and the View menu's zoom keys (13.4b)
This commit is contained in:
1 parent
bc49b722eb
commit
64ebd1cdf9
17 files changed
+702
-74
No files matched your search
@@ -0,0 +1,178 @@
|
||||
#include "UI/JPEGExportSheet.h"
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
double JPEGPreview::fitZoom(int width, int height, QSizeF frame, double displayScale)
|
||||
{
|
||||
const double scale = std::max(1.0, displayScale);
|
||||
return std::min(frame.width() / (width / scale), frame.height() / (height / scale));
|
||||
}
|
||||
|
||||
std::optional<double> JPEGPreview::step(double zoom, int direction)
|
||||
{
|
||||
if (direction > 0) {
|
||||
const auto next = std::find_if(steps.begin(), steps.end(), [zoom](double step) { return step > zoom * 1.001; });
|
||||
return next == steps.end() ? std::nullopt : std::optional(*next);
|
||||
}
|
||||
const auto next = std::find_if(steps.rbegin(), steps.rend(), [zoom](double step) { return step < zoom * 0.999; });
|
||||
return next == steps.rend() ? std::nullopt : std::optional(*next);
|
||||
}
|
||||
|
||||
JPEGPreview::JPEGPreview(QSize pixels, std::function<void()> zoomChanged, QWidget *parent)
|
||||
: QAbstractScrollArea(parent), m_pixels(pixels), m_zoomChanged(std::move(zoomChanged))
|
||||
{
|
||||
setFixedSize(frame);
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setToolTip(QStringLiteral("Drag or scroll to move around; double-click switches between Fit and 100%"));
|
||||
horizontalScrollBar()->setSingleStep(20);
|
||||
verticalScrollBar()->setSingleStep(20);
|
||||
}
|
||||
|
||||
void JPEGPreview::setImage(const QImage &image, bool updating)
|
||||
{
|
||||
// A new image only: the averaged copy still holds.
|
||||
if (image.cacheKey() != m_image.cacheKey())
|
||||
m_reduced = QImage();
|
||||
m_image = image;
|
||||
m_updating = updating;
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
double JPEGPreview::shownZoom() const
|
||||
{
|
||||
return m_zoom.value_or(fitZoom(m_pixels.width(), m_pixels.height(), QSizeF(frame), devicePixelRatioF()));
|
||||
}
|
||||
|
||||
void JPEGPreview::zoomBy(int direction)
|
||||
{
|
||||
if (const std::optional<double> next = step(shownZoom(), direction))
|
||||
setZoom(next);
|
||||
}
|
||||
|
||||
// Swift's keepCentered: the middle stays on the same part.
|
||||
void JPEGPreview::setZoom(std::optional<double> zoom)
|
||||
{
|
||||
if (zoom == m_zoom)
|
||||
return;
|
||||
const QSizeF view(viewport()->size());
|
||||
QPointF fraction(0.5, 0.5);
|
||||
if (m_zoom) {
|
||||
const QSizeF before = shownSize(*m_zoom);
|
||||
fraction = QPointF((horizontalScrollBar()->value() + std::min(view.width(), before.width()) / 2) / before.width(),
|
||||
(verticalScrollBar()->value() + std::min(view.height(), before.height()) / 2) / before.height());
|
||||
}
|
||||
m_zoom = zoom;
|
||||
updateRanges();
|
||||
if (m_zoom) {
|
||||
const QSizeF size = shownSize(*m_zoom);
|
||||
const QSizeF shown(viewport()->size());
|
||||
const double x = std::clamp(fraction.x() * size.width() - shown.width() / 2, 0.0, std::max(0.0, size.width() - shown.width()));
|
||||
const double y = std::clamp(fraction.y() * size.height() - shown.height() / 2, 0.0, std::max(0.0, size.height() - shown.height()));
|
||||
horizontalScrollBar()->setValue(int(std::lround(x)));
|
||||
verticalScrollBar()->setValue(int(std::lround(y)));
|
||||
}
|
||||
m_drag.reset();
|
||||
showCursor(false);
|
||||
viewport()->update();
|
||||
m_zoomChanged();
|
||||
}
|
||||
|
||||
// The image's size on screen at `zoom`, in points.
|
||||
QSizeF JPEGPreview::shownSize(double zoom) const
|
||||
{
|
||||
return QSizeF(m_pixels) / std::max(1.0, devicePixelRatioF()) * zoom;
|
||||
}
|
||||
|
||||
void JPEGPreview::updateRanges()
|
||||
{
|
||||
const QSizeF size = m_zoom ? shownSize(*m_zoom) : QSizeF(0, 0);
|
||||
const QSize view = viewport()->size();
|
||||
horizontalScrollBar()->setRange(0, std::max(0, int(std::ceil(size.width() - view.width()))));
|
||||
horizontalScrollBar()->setPageStep(view.width());
|
||||
verticalScrollBar()->setRange(0, std::max(0, int(std::ceil(size.height() - view.height()))));
|
||||
verticalScrollBar()->setPageStep(view.height());
|
||||
}
|
||||
|
||||
void JPEGPreview::showCursor(bool grabbing)
|
||||
{
|
||||
viewport()->setCursor(!m_zoom ? Qt::ArrowCursor : grabbing ? Qt::ClosedHandCursor : Qt::OpenHandCursor);
|
||||
}
|
||||
|
||||
void JPEGPreview::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(viewport());
|
||||
const QRectF view(viewport()->rect());
|
||||
painter.fillRect(view, QColor::fromRgbF(0.12f, 0.12f, 0.12f));
|
||||
if (!m_image.isNull()) {
|
||||
QRectF target;
|
||||
if (m_zoom) {
|
||||
// Smaller than the view, it sits in the middle.
|
||||
const QSizeF size = shownSize(*m_zoom);
|
||||
target = QRectF(QPointF(size.width() < view.width() ? (view.width() - size.width()) / 2 : -horizontalScrollBar()->value(),
|
||||
size.height() < view.height() ? (view.height() - size.height()) / 2 : -verticalScrollBar()->value()),
|
||||
size);
|
||||
} else {
|
||||
target = QRectF(QPointF(), QSizeF(m_image.size()).scaled(view.size(), Qt::KeepAspectRatio));
|
||||
target.moveCenter(view.center());
|
||||
}
|
||||
// Nearest from 100% up: each JPEG pixel as it is.
|
||||
if (m_zoom && *m_zoom >= 1) {
|
||||
painter.drawImage(target, m_image);
|
||||
} else {
|
||||
// Swift's high quality: an area average, kept per size.
|
||||
const QSize device = (target.size() * devicePixelRatioF()).toSize().expandedTo(QSize(1, 1));
|
||||
if (m_reduced.size() != device)
|
||||
m_reduced = m_image.scaled(device, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||
painter.drawImage(target, m_reduced);
|
||||
}
|
||||
}
|
||||
if (m_updating) {
|
||||
// Swift's regular material behind the spinner.
|
||||
QColor material = palette().color(QPalette::Window);
|
||||
material.setAlphaF(0.85f);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(material);
|
||||
QRectF plate(0, 0, 64, 64);
|
||||
plate.moveCenter(view.center());
|
||||
painter.drawRoundedRect(plate, 8, 8);
|
||||
}
|
||||
}
|
||||
|
||||
void JPEGPreview::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QAbstractScrollArea::resizeEvent(event);
|
||||
updateRanges();
|
||||
}
|
||||
|
||||
void JPEGPreview::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (m_zoom && event->button() == Qt::LeftButton)
|
||||
m_drag = {event->position().toPoint(), QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value())};
|
||||
}
|
||||
|
||||
void JPEGPreview::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_drag)
|
||||
return;
|
||||
const QPoint moved = event->position().toPoint() - m_drag->first;
|
||||
horizontalScrollBar()->setValue(m_drag->second.x() - moved.x());
|
||||
verticalScrollBar()->setValue(m_drag->second.y() - moved.y());
|
||||
showCursor(true);
|
||||
}
|
||||
|
||||
void JPEGPreview::mouseReleaseEvent(QMouseEvent *)
|
||||
{
|
||||
m_drag.reset();
|
||||
showCursor(false);
|
||||
}
|
||||
|
||||
// Double-click switches between Fit and 100%.
|
||||
void JPEGPreview::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
setZoom(m_zoom ? std::nullopt : std::optional(1.0));
|
||||
}
|
||||
+75
-45
@@ -2,6 +2,7 @@
|
||||
#include "UI/KeyboardShortcuts.h"
|
||||
#include "UI/ByteCounts.h"
|
||||
#include "UI/ColorPickerSheet+Dialog.h"
|
||||
#include "UI/LayerIcons.h"
|
||||
#include <QFutureWatcher>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
#include <QSlider>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
@@ -50,59 +52,46 @@ struct Encoded {
|
||||
};
|
||||
}
|
||||
|
||||
// Swift's ZStack: dark gray, the fitted preview, a plate.
|
||||
class JPEGExportSheet::Preview : public QWidget {
|
||||
public:
|
||||
using QWidget::QWidget;
|
||||
QImage image;
|
||||
bool updating = false;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.fillRect(rect(), QColor::fromRgbF(0.12f, 0.12f, 0.12f));
|
||||
if (!image.isNull()) {
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
QRectF target(QPointF(), QSizeF(image.size()).scaled(QSizeF(size()), Qt::KeepAspectRatio));
|
||||
target.moveCenter(QRectF(rect()).center());
|
||||
painter.drawImage(target, image);
|
||||
}
|
||||
if (updating) {
|
||||
// Swift's regular material behind the spinner.
|
||||
QColor material = palette().color(QPalette::Window);
|
||||
material.setAlphaF(0.85f);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(material);
|
||||
QRectF plate(0, 0, 64, 64);
|
||||
plate.moveCenter(QRectF(rect()).center());
|
||||
painter.drawRoundedRect(plate, 8, 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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)),
|
||||
: QWidget(parent), m_raster(std::move(raster)), m_session(session), m_finish(std::move(finish)), m_wait(new QTimer(this)), m_preview(new JPEGPreview(m_raster.image.size(), [this] { synchronize(); }, 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 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))
|
||||
m_note(text(QString(), 13, QFont::Normal, QPalette::PlaceholderText, this)), m_export(new QPushButton(QStringLiteral("Export…"), this)),
|
||||
m_fit(new QPushButton(QStringLiteral("Fit"), this)), m_zoomIn(new QPushButton(this)), m_zoomOut(new QPushButton(this))
|
||||
{
|
||||
m_options.quality = startQuality();
|
||||
auto *column = new QVBoxLayout(this);
|
||||
column->setContentsMargins(24, 24, 24, 24);
|
||||
column->setSpacing(16);
|
||||
column->addWidget(text(QStringLiteral("Export JPEG"), 17, QFont::Bold, QPalette::WindowText, this));
|
||||
// The title row sits 8 above; it inherits 8.
|
||||
auto *top = new QVBoxLayout;
|
||||
top->setSpacing(8);
|
||||
auto *title = new QHBoxLayout;
|
||||
title->addWidget(text(QStringLiteral("Export JPEG"), 17, QFont::Bold, QPalette::WindowText, this));
|
||||
title->addStretch(1);
|
||||
m_fit->setObjectName(QStringLiteral("jpegFit"));
|
||||
m_zoomIn->setObjectName(QStringLiteral("jpegZoomIn"));
|
||||
m_zoomIn->setAccessibleName(QStringLiteral("Zoom in"));
|
||||
m_zoomOut->setObjectName(QStringLiteral("jpegZoomOut"));
|
||||
m_zoomOut->setAccessibleName(QStringLiteral("Zoom out"));
|
||||
for (QPushButton *button : {m_fit, m_zoomIn, m_zoomOut}) {
|
||||
button->setAutoDefault(false);
|
||||
title->addWidget(button);
|
||||
}
|
||||
connect(m_fit, &QPushButton::clicked, this, [this] { m_preview->setZoom(std::nullopt); });
|
||||
connect(m_zoomIn, &QPushButton::clicked, this, [this] { m_preview->zoomBy(1); });
|
||||
connect(m_zoomOut, &QPushButton::clicked, this, [this] { m_preview->zoomBy(-1); });
|
||||
top->addLayout(title);
|
||||
m_preview->setObjectName(QStringLiteral("jpegPreview"));
|
||||
m_preview->setFixedSize(560, 330);
|
||||
m_spinner->setObjectName(QStringLiteral("jpegSpinner"));
|
||||
m_spinner->setRange(0, 0);
|
||||
m_spinner->setTextVisible(false);
|
||||
m_spinner->setGeometry(264, 149, 32, 32);
|
||||
column->addWidget(m_preview);
|
||||
top->addWidget(m_preview);
|
||||
column->addLayout(top);
|
||||
|
||||
auto *quality = new QHBoxLayout;
|
||||
auto *qualityLabel = new QLabel(QStringLiteral("Quality"), this);
|
||||
@@ -132,17 +121,22 @@ JPEGExportSheet::JPEGExportSheet(ExportRaster raster, EditorSession &session, st
|
||||
matte->addWidget(m_matteSwatch);
|
||||
matte->addStretch(1);
|
||||
column->addLayout(matte);
|
||||
column->addWidget(text(QStringLiteral("%1 × %2 px · sRGB").arg(grouped(m_raster.image.width()), grouped(m_raster.image.height())), 13, QFont::Normal,
|
||||
QPalette::PlaceholderText, this));
|
||||
|
||||
// The size, the result and the buttons share one row.
|
||||
auto *buttons = new QHBoxLayout;
|
||||
buttons->setSpacing(12);
|
||||
QLabel *size = text(QStringLiteral("%1 × %2 px · sRGB").arg(grouped(m_raster.image.width()), grouped(m_raster.image.height())), 13, QFont::Normal,
|
||||
QPalette::PlaceholderText, this);
|
||||
size->setObjectName(QStringLiteral("jpegSize"));
|
||||
buttons->addWidget(size);
|
||||
buttons->addStretch(1);
|
||||
m_failure->setObjectName(QStringLiteral("jpegError"));
|
||||
m_bytes->setObjectName(QStringLiteral("jpegBytes"));
|
||||
m_note->setObjectName(QStringLiteral("jpegNote"));
|
||||
m_note->setText(QStringLiteral("Updating…"));
|
||||
buttons->addWidget(m_failure);
|
||||
buttons->addWidget(m_bytes);
|
||||
buttons->addWidget(m_note);
|
||||
buttons->addStretch(1);
|
||||
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
|
||||
cancel->setObjectName(QStringLiteral("jpegCancel"));
|
||||
cancel->setAutoDefault(false);
|
||||
@@ -169,9 +163,28 @@ JPEGExportSheet::JPEGExportSheet(ExportRaster raster, EditorSession &session, st
|
||||
m_wait->setInterval(200);
|
||||
m_wait->setTimerType(Qt::PreciseTimer);
|
||||
connect(m_wait, &QTimer::timeout, this, &JPEGExportSheet::encode);
|
||||
bindZoomKeys();
|
||||
connect(&ShortcutSettings::shared(), &ShortcutSettings::changed, this, &JPEGExportSheet::bindZoomKeys);
|
||||
request();
|
||||
}
|
||||
|
||||
// A window-modal sheet blocks the menus: it takes their keys.
|
||||
void JPEGExportSheet::bindZoomKeys()
|
||||
{
|
||||
const std::array<QKeySequence, 4> originals{QKeySequence(Qt::CTRL | Qt::Key_0), QKeySequence(Qt::CTRL | Qt::Key_1),
|
||||
QKeySequence(Qt::CTRL | Qt::Key_Equal), QKeySequence(Qt::CTRL | Qt::Key_Minus)};
|
||||
const std::array<std::function<void()>, 4> actions{[this] { m_preview->setZoom(std::nullopt); }, [this] { m_preview->setZoom(1.0); },
|
||||
[this] { m_preview->zoomBy(1); }, [this] { m_preview->zoomBy(-1); }};
|
||||
for (size_t index = 0; index < m_zoomKeys.size(); ++index) {
|
||||
if (!m_zoomKeys[index]) {
|
||||
m_zoomKeys[index] = new QShortcut(this);
|
||||
connect(m_zoomKeys[index], &QShortcut::activated, this, actions[index]);
|
||||
}
|
||||
m_zoomKeys[index]->setKey(ShortcutSettings::shared().menu(originals[index]));
|
||||
}
|
||||
synchronize();
|
||||
}
|
||||
|
||||
// SwiftUI cancels the task when the sheet goes.
|
||||
JPEGExportSheet::~JPEGExportSheet()
|
||||
{
|
||||
@@ -233,20 +246,37 @@ void JPEGExportSheet::setMatte(const PaletteColor &matte)
|
||||
request();
|
||||
}
|
||||
|
||||
void JPEGExportSheet::changeEvent(QEvent *event)
|
||||
{
|
||||
QWidget::changeEvent(event);
|
||||
if (event->type() == QEvent::PaletteChange)
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void JPEGExportSheet::synchronize()
|
||||
{
|
||||
const bool ready = m_result && m_readyOptions == m_options;
|
||||
const bool updating = m_readyOptions != m_options && !m_error;
|
||||
m_preview->image = m_result ? m_result->preview : QImage();
|
||||
m_preview->updating = updating;
|
||||
m_preview->update();
|
||||
m_preview->setImage(m_result ? m_result->preview : QImage(), updating);
|
||||
m_spinner->setVisible(updating);
|
||||
m_percent->setText(QStringLiteral("%1%").arg(std::lround(m_options.quality * 100)));
|
||||
m_failure->setVisible(m_error.has_value());
|
||||
m_failure->setText(m_error.value_or(QString()));
|
||||
m_bytes->setVisible(ready && !m_error);
|
||||
m_bytes->setText(m_result ? ByteCounts::file(m_result->data.size()) : QString());
|
||||
m_note->setVisible(!m_error);
|
||||
m_note->setText(ready ? QStringLiteral("· encoded preview, fitted to window") : QStringLiteral("Updating preview…"));
|
||||
m_note->setVisible(!ready && !m_error);
|
||||
m_export->setEnabled(ready && !m_error);
|
||||
// The zoom buttons name their keys and the zoom.
|
||||
const double zoom = m_preview->shownZoom();
|
||||
const QString percent = QStringLiteral("%1%").arg(std::lround(zoom * 100));
|
||||
const auto key = [this](size_t index) { return m_zoomKeys[index] ? m_zoomKeys[index]->key().toString(QKeySequence::NativeText) : QString(); };
|
||||
m_fit->setEnabled(m_preview->zoom().has_value());
|
||||
m_fit->setToolTip(QStringLiteral("Show the whole image (%1)").arg(key(0)));
|
||||
m_zoomIn->setEnabled(JPEGPreview::step(zoom, 1).has_value());
|
||||
m_zoomIn->setToolTip(QStringLiteral("Zoom in (%1), now %2. At 100% each pixel of the JPEG is one pixel of the screen, as on the canvas").arg(key(2), percent));
|
||||
m_zoomOut->setEnabled(JPEGPreview::step(zoom, -1).has_value());
|
||||
m_zoomOut->setToolTip(QStringLiteral("Zoom out (%1), now %2").arg(key(3), percent));
|
||||
const QColor ink = palette().color(QPalette::ButtonText);
|
||||
m_zoomIn->setIcon(LayerIcons::pixmap(LayerIcon::plusMagnifyingGlass, 16, ink, devicePixelRatioF()));
|
||||
m_zoomOut->setIcon(LayerIcons::pixmap(LayerIcon::minusMagnifyingGlass, 16, ink, devicePixelRatioF()));
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
#include "Document/ColorPalette.h"
|
||||
#include "IO/ImageExporter.h"
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QWidget>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -9,12 +11,58 @@
|
||||
|
||||
class QLabel;
|
||||
class QProgressBar;
|
||||
class QShortcut;
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QTimer;
|
||||
class DialogColorSwatch;
|
||||
class EditorSession;
|
||||
|
||||
// Swift's JPEGPreview: the encoded JPEG, fitted or zoomed, dragged.
|
||||
class JPEGPreview : public QAbstractScrollArea {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static constexpr QSize frame{560, 330};
|
||||
static constexpr std::array<double, 6> steps{0.25, 0.5, 1, 2, 4, 8};
|
||||
// The zoom at which the whole image fits `frame`.
|
||||
static double fitZoom(int width, int height, QSizeF frame, double displayScale);
|
||||
// The next step past `zoom`: 1 in, −1 out.
|
||||
static std::optional<double> step(double zoom, int direction);
|
||||
|
||||
// `pixels`: the export's size, which the image may undercut.
|
||||
JPEGPreview(QSize pixels, std::function<void()> zoomChanged, QWidget *parent);
|
||||
void setImage(const QImage &image, bool updating);
|
||||
// None fits; 1 is a screen pixel a pixel.
|
||||
std::optional<double> zoom() const { return m_zoom; }
|
||||
void setZoom(std::optional<double> zoom);
|
||||
double shownZoom() const;
|
||||
void zoomBy(int direction);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QSizeF shownSize(double zoom) const;
|
||||
void updateRanges();
|
||||
void showCursor(bool grabbing);
|
||||
|
||||
const QSize m_pixels;
|
||||
// Each change of zoom, for the sheet's buttons.
|
||||
const std::function<void()> m_zoomChanged;
|
||||
QImage m_image;
|
||||
// The image shrunk to the size last drawn.
|
||||
QImage m_reduced;
|
||||
bool m_updating = false;
|
||||
std::optional<double> m_zoom;
|
||||
// A drag's press and the scroll it began from.
|
||||
std::optional<std::pair<QPoint, QPoint>> m_drag;
|
||||
};
|
||||
|
||||
// Swift's JPEGExportSheet: quality, matte and the encoded preview.
|
||||
class JPEGExportSheet : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -25,12 +73,17 @@ public:
|
||||
JPEGExportSheet(ExportRaster raster, EditorSession &session, std::function<void(std::optional<QByteArray>)> finish, QWidget *parent = nullptr);
|
||||
~JPEGExportSheet() override;
|
||||
|
||||
protected:
|
||||
// The zoom glyphs follow the theme.
|
||||
void changeEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
class Preview;
|
||||
void request();
|
||||
void encode();
|
||||
void setMatte(const PaletteColor &matte);
|
||||
void synchronize();
|
||||
// The View menu's zoom keys, as remapped, zoom the preview.
|
||||
void bindZoomKeys();
|
||||
|
||||
const ExportRaster m_raster;
|
||||
EditorSession &m_session;
|
||||
@@ -42,7 +95,7 @@ private:
|
||||
// The newest request's flag, Swift's task cancellation.
|
||||
std::shared_ptr<std::atomic_bool> m_cancelled;
|
||||
QTimer *const m_wait;
|
||||
Preview *const m_preview;
|
||||
JPEGPreview *const m_preview;
|
||||
QProgressBar *const m_spinner;
|
||||
QSlider *const m_quality;
|
||||
QLabel *const m_percent;
|
||||
@@ -51,4 +104,9 @@ private:
|
||||
QLabel *const m_bytes;
|
||||
QLabel *const m_note;
|
||||
QPushButton *const m_export;
|
||||
QPushButton *const m_fit;
|
||||
QPushButton *const m_zoomIn;
|
||||
QPushButton *const m_zoomOut;
|
||||
// Fit, Actual Pixels, Zoom In and Zoom Out.
|
||||
std::array<QShortcut *, 4> m_zoomKeys{};
|
||||
};
|
||||
@@ -241,6 +241,16 @@ void wind(QPainter &painter)
|
||||
painter.drawPath(path);
|
||||
}
|
||||
|
||||
// The lens toward the top left, its handle down right.
|
||||
void magnifyingGlass(QPainter &painter, bool plus)
|
||||
{
|
||||
painter.drawEllipse(QPointF(7.5, 7.5), 5.5, 5.5);
|
||||
painter.drawLine(QPointF(11.4, 11.4), QPointF(16, 16));
|
||||
painter.drawLine(QPointF(5, 7.5), QPointF(10, 7.5));
|
||||
if (plus)
|
||||
painter.drawLine(QPointF(7.5, 5), QPointF(7.5, 10));
|
||||
}
|
||||
|
||||
// Swift's circle.dotted: twelve dots round a ring.
|
||||
void dottedCircle(QPainter &painter)
|
||||
{
|
||||
@@ -301,6 +311,8 @@ void LayerIcons::paint(QPainter &painter, LayerIcon icon, QPointF origin, double
|
||||
case LayerIcon::drop: drop(painter); break;
|
||||
case LayerIcon::wind: wind(painter); break;
|
||||
case LayerIcon::dottedCircle: dottedCircle(painter); break;
|
||||
case LayerIcon::plusMagnifyingGlass: magnifyingGlass(painter, true); break;
|
||||
case LayerIcon::minusMagnifyingGlass: magnifyingGlass(painter, false); break;
|
||||
}
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
enum class LayerIcon {
|
||||
eye, eyeSlash, chevronRight, chevronDown, folder, link, newLayer, newFolder, addMask, trash, layers, text,
|
||||
halfFilledCircle, sliders, curvePath, plusMinusCircle, paintPalette, circleGrid, sparkles, rightHalfCircle, hatchedCircle, axes,
|
||||
scope, lineDiagonal, drop, wind, dottedCircle
|
||||
scope, lineDiagonal, drop, wind, dottedCircle, plusMagnifyingGlass, minusMagnifyingGlass
|
||||
};
|
||||
|
||||
namespace LayerIcons {
|
||||
|
||||
Reference in new issue
Block a user