14.20: the Navigator minimap from 300%
This commit is contained in:
17 files changed
+733
-1
No files matched your search
@@ -2,6 +2,7 @@
|
||||
#include "IO/RecentProjects.h"
|
||||
#include "Logging.h"
|
||||
#include "UI/KeyboardShortcuts.h"
|
||||
#include "UI/NavigatorMinimap.h"
|
||||
#include "UI/ProjectWorkspaceView.h"
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
@@ -142,6 +143,10 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, Pro
|
||||
};
|
||||
add(view, QStringLiteral("zoomIn"), QStringLiteral("Zoom In"), QKeySequence(Qt::CTRL | Qt::Key_Equal), [zoomBy] { zoomBy(1); });
|
||||
add(view, QStringLiteral("zoomOut"), QStringLiteral("Zoom Out"), QKeySequence(Qt::CTRL | Qt::Key_Minus), [zoomBy] { zoomBy(-1); });
|
||||
add(view, QStringLiteral("navigator"), QStringLiteral("Navigator (300% and above)"), QKeySequence(), [this] {
|
||||
NavigatorMinimap::setVisibleSetting(!NavigatorMinimap::visibleSetting());
|
||||
session().notify();
|
||||
})->setCheckable(true);
|
||||
add(view, QStringLiteral("pixelGrid"), QStringLiteral("Pixel Grid (800% and above)"), QKeySequence(), [this] { session().setShowsPixelGrid(!session().showsPixelGrid()); })
|
||||
->setCheckable(true);
|
||||
// Ctrl+H: the Move tool's box; the desktop hides apps.
|
||||
@@ -369,6 +374,7 @@ void CompositorMenus::synchronize()
|
||||
action(QStringLiteral("redo"))->setEnabled(typing || s.canRedo());
|
||||
for (const char *name : {"fit", "actualPixels", "zoomIn", "zoomOut"})
|
||||
action(QString::fromLatin1(name))->setEnabled(drawn);
|
||||
action(QStringLiteral("navigator"))->setChecked(NavigatorMinimap::visibleSetting());
|
||||
action(QStringLiteral("pixelGrid"))->setChecked(s.showsPixelGrid());
|
||||
action(QStringLiteral("toggleFullscreen"))->setChecked(s.canvasOnly());
|
||||
action(QStringLiteral("toggleFullscreen"))->setEnabled(s.canToggleCanvasOnly());
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
#include "UI/NavigatorMinimap.h"
|
||||
#include "Rendering/NavigatorGeometry.h"
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QSettings>
|
||||
#include <cmath>
|
||||
|
||||
bool NavigatorMinimap::visibleSetting()
|
||||
{
|
||||
return QSettings().value(QLatin1String(visibleKey), false).toBool();
|
||||
}
|
||||
|
||||
void NavigatorMinimap::setVisibleSetting(bool visible)
|
||||
{
|
||||
QSettings().setValue(QLatin1String(visibleKey), visible);
|
||||
}
|
||||
|
||||
QSizeF NavigatorMinimap::sizeFor(QSizeF document)
|
||||
{
|
||||
if (!(document.width() > 0 && document.height() > 0))
|
||||
return QSizeF(0, 0);
|
||||
const double scale = std::min(largest.width() / document.width(), largest.height() / document.height());
|
||||
return QSizeF(std::round(document.width() * scale), std::round(document.height() * scale));
|
||||
}
|
||||
|
||||
NavigatorMinimap::RenderKey::RenderKey(const std::optional<CanvasDocument> &document, int revision)
|
||||
: documentID(document ? document->id : QUuid()), size(document ? document->size() : QSizeF(0, 0)),
|
||||
layers(document ? document->layers : std::vector<ImageLayer>()), revision(revision)
|
||||
{
|
||||
}
|
||||
|
||||
NavigatorMinimap::NavigatorMinimap(EditorSession &session, QWidget *parent) : QWidget(parent), m_session(session), m_wait(new QTimer(this))
|
||||
{
|
||||
setObjectName(QStringLiteral("navigatorMinimap"));
|
||||
setAccessibleName(QStringLiteral("Navigator"));
|
||||
setAccessibleDescription(QStringLiteral("Click or drag to move the view"));
|
||||
setCursor(Qt::ArrowCursor);
|
||||
m_wait->setObjectName(QStringLiteral("navigatorWait"));
|
||||
m_wait->setSingleShot(true);
|
||||
m_wait->setInterval(250);
|
||||
m_wait->setTimerType(Qt::PreciseTimer);
|
||||
connect(m_wait, &QTimer::timeout, this, &NavigatorMinimap::render);
|
||||
connect(&session, &EditorSession::changed, this, &NavigatorMinimap::synchronize);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
// Swift's ContentView condition, then its task(id:).
|
||||
void NavigatorMinimap::synchronize()
|
||||
{
|
||||
const std::optional<CanvasDocument> &document = m_session.document();
|
||||
const bool shown = visibleSetting() && !m_session.canvasOnly() && document && m_session.viewport.zoom() >= zoomShown;
|
||||
setVisible(shown);
|
||||
if (!shown) {
|
||||
// SwiftUI drops the view and its picture with it.
|
||||
m_picture = QImage();
|
||||
m_key.reset();
|
||||
m_wait->stop();
|
||||
return;
|
||||
}
|
||||
setFixedSize(sizeFor(document->size()).toSize());
|
||||
RenderKey key(document, m_session.brushRevision());
|
||||
if (key != m_key) {
|
||||
m_key = std::move(key);
|
||||
if (m_picture.isNull())
|
||||
render();
|
||||
else
|
||||
m_wait->start();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void NavigatorMinimap::render()
|
||||
{
|
||||
m_picture = m_session.navigatorImage(picturePixels).value_or(QImage());
|
||||
update();
|
||||
}
|
||||
|
||||
void NavigatorMinimap::centre(QPointF point)
|
||||
{
|
||||
const QSizeF document = m_session.document().value().size();
|
||||
const NavigatorGeometry geometry{document, sizeFor(document)};
|
||||
m_session.viewport.centerView(geometry.documentPoint(point), document);
|
||||
m_session.notify();
|
||||
}
|
||||
|
||||
void NavigatorMinimap::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
centre(event->position());
|
||||
}
|
||||
|
||||
void NavigatorMinimap::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton)
|
||||
centre(event->position());
|
||||
}
|
||||
|
||||
void NavigatorMinimap::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
const QRectF bounds(rect());
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(bounds, 4, 4);
|
||||
painter.save();
|
||||
painter.setClipPath(shape);
|
||||
painter.fillRect(bounds, QColor::fromRgbF(0.11f, 0.11f, 0.11f));
|
||||
if (!m_picture.isNull()) {
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
painter.drawImage(bounds, m_picture);
|
||||
}
|
||||
// The view's box in the accent, inside its rect.
|
||||
const QSizeF document = m_session.document().value().size();
|
||||
const NavigatorGeometry geometry{document, bounds.size()};
|
||||
const QRectF shown = geometry.thumbnailRect(m_session.viewport.visibleDocumentRect(document)).intersected(geometry.imageRect());
|
||||
if (!shown.isEmpty()) {
|
||||
const QRectF box(shown.topLeft(), QSizeF(std::max(3.0, shown.width()), std::max(3.0, shown.height())));
|
||||
painter.setPen(QPen(palette().color(QPalette::Active, QPalette::Highlight), 2.5));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(box.adjusted(1.25, 1.25, -1.25, -1.25));
|
||||
}
|
||||
painter.restore();
|
||||
// A white rim at 25%, inside the rounded edge.
|
||||
painter.setPen(QPen(QColor::fromRgbF(1, 1, 1, 0.25f), 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRoundedRect(bounds.adjusted(0.5, 0.5, -0.5, -0.5), 3.5, 3.5);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "Document/EditorSession.h"
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
// Swift's NavigatorMinimap: the document in small, from 300%.
|
||||
class NavigatorMinimap : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static constexpr double zoomShown = 3;
|
||||
// The most room the document takes, in points.
|
||||
static constexpr QSizeF largest{126, 90};
|
||||
// Pixels on the picture's longer side.
|
||||
static constexpr int picturePixels = 300;
|
||||
// Swift's AppStorage key; off until View › Navigator.
|
||||
static constexpr const char *visibleKey = "navigator.visible";
|
||||
static bool visibleSetting();
|
||||
static void setVisibleSetting(bool visible);
|
||||
// Fitted inside `largest`, its proportions kept, rounded.
|
||||
static QSizeF sizeFor(QSizeF document);
|
||||
|
||||
// What the picture comes from: layers, not the selection.
|
||||
struct RenderKey {
|
||||
QUuid documentID;
|
||||
QSizeF size;
|
||||
std::vector<ImageLayer> layers;
|
||||
int revision = 0;
|
||||
RenderKey(const std::optional<CanvasDocument> &document, int revision);
|
||||
friend bool operator==(const RenderKey &, const RenderKey &) = default;
|
||||
};
|
||||
|
||||
explicit NavigatorMinimap(EditorSession &session, QWidget *parent = nullptr);
|
||||
void synchronize();
|
||||
const QImage &picture() const { return m_picture; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void render();
|
||||
void centre(QPointF point);
|
||||
|
||||
EditorSession &m_session;
|
||||
QImage m_picture;
|
||||
std::optional<RenderKey> m_key;
|
||||
// Edits settle first: the picture redraws once, afterwards.
|
||||
QTimer *const m_wait;
|
||||
};
|
||||
Reference in new issue
Block a user