12.3a: guides, the layout grid, Snap To and the View entries (Compositor 1.1.7)
This commit is contained in:
1 parent
4bfc6c237e
commit
338451fc35
25 files changed
+1029
-52
No files matched your search
+6
-34
@@ -133,8 +133,7 @@ QRectF CropSnap::apply(QRectF rect, const CropDrag &drag, QPointF point, std::op
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
QRectF boxAround(const LayerTransform &transform)
|
||||
QRectF TransformSnap::box(const LayerTransform &transform)
|
||||
{
|
||||
const Corners corners = DistortWarp::corners(transform);
|
||||
QPointF low = corners[0], high = corners[0];
|
||||
@@ -144,25 +143,11 @@ QRectF boxAround(const LayerTransform &transform)
|
||||
}
|
||||
return QRectF(low, high);
|
||||
}
|
||||
}
|
||||
|
||||
// The canvas's edges and middle, and every other visible layer's.
|
||||
// View > Snap To's targets, centres included.
|
||||
SnapGuides EditorSession::transformSnapTargets(const QSet<QUuid> &moving) const
|
||||
{
|
||||
SnapGuides targets;
|
||||
if (!m_document)
|
||||
return targets;
|
||||
const QSizeF size = m_document->size();
|
||||
targets.xs = {0, size.width() / 2, size.width()};
|
||||
targets.ys = {0, size.height() / 2, size.height()};
|
||||
for (const ImageLayer &layer : m_document->renderLayers()) {
|
||||
if (!layer.asset || moving.contains(layer.id))
|
||||
continue;
|
||||
const QRectF box = boxAround(displayedTransform(layer));
|
||||
targets.xs.insert(targets.xs.end(), {std::round(box.left()), std::round(box.center().x()), std::round(box.right())});
|
||||
targets.ys.insert(targets.ys.end(), {std::round(box.top()), std::round(box.center().y()), std::round(box.bottom())});
|
||||
}
|
||||
return targets;
|
||||
return alignmentSnapTargets(moving, true);
|
||||
}
|
||||
|
||||
// `draft` nudged onto a nearby edge or middle, within `tolerance`.
|
||||
@@ -173,7 +158,7 @@ LayerTransform EditorSession::snappedMove(const LayerTransform &draft, const QSe
|
||||
return draft;
|
||||
}
|
||||
const SnapGuides targets = transformSnapTargets(moving);
|
||||
const TransformSnap::Offset snap = TransformSnap::offset(boxAround(draft), targets.xs, targets.ys, tolerance);
|
||||
const TransformSnap::Offset snap = TransformSnap::offset(TransformSnap::box(draft), targets.xs, targets.ys, tolerance);
|
||||
snapGuides = {snap.x ? std::vector<double>{*snap.x} : std::vector<double>{}, snap.y ? std::vector<double>{*snap.y} : std::vector<double>{}};
|
||||
if (snap.offset.isNull())
|
||||
return draft;
|
||||
@@ -182,23 +167,10 @@ LayerTransform EditorSession::snappedMove(const LayerTransform &draft, const QSe
|
||||
return snapped;
|
||||
}
|
||||
|
||||
// The canvas's edges and every visible layer's upright bounds.
|
||||
// View > Snap To's targets, without the centres.
|
||||
SnapGuides EditorSession::cropSnapTargets() const
|
||||
{
|
||||
SnapGuides targets;
|
||||
if (!m_document)
|
||||
return targets;
|
||||
const QSizeF size = m_document->size();
|
||||
targets.xs = {0, size.width()};
|
||||
targets.ys = {0, size.height()};
|
||||
for (const ImageLayer &layer : m_document->renderLayers()) {
|
||||
if (!layer.asset)
|
||||
continue;
|
||||
const QRectF box = boxAround(displayedTransform(layer));
|
||||
targets.xs.insert(targets.xs.end(), {std::round(box.left()), std::round(box.right())});
|
||||
targets.ys.insert(targets.ys.end(), {std::round(box.top()), std::round(box.bottom())});
|
||||
}
|
||||
return targets;
|
||||
return alignmentSnapTargets({}, false);
|
||||
}
|
||||
|
||||
std::optional<QRectF> EditorSession::visibleCropRect() const
|
||||
|
||||
@@ -45,7 +45,7 @@ CanvasDocument::CanvasDocument(int width, int height)
|
||||
|
||||
CanvasDocument::CanvasDocument(const ProjectSnapshot &snapshot)
|
||||
: id(snapshot.manifest.documentID), width(int(snapshot.manifest.width)), height(int(snapshot.manifest.height)),
|
||||
resolution(snapshot.manifest.resolution.value_or(72))
|
||||
resolution(snapshot.manifest.resolution.value_or(72)), guides(snapshot.manifest.guides.value_or(std::vector<CanvasGuide>()))
|
||||
{
|
||||
for (const ProjectLayerRecord &record : snapshot.manifest.layers) {
|
||||
const auto image = snapshot.images.find(record.id);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "Document/LayerAdjustment.h"
|
||||
#include "Document/LayerAppearance.h"
|
||||
#include "Document/Guides.h"
|
||||
#include "Document/LayerGroups.h"
|
||||
#include "Document/LayerMask.h"
|
||||
#include "Document/LayerTransform.h"
|
||||
@@ -63,6 +64,8 @@ struct CanvasDocument {
|
||||
int height;
|
||||
double resolution = 72;
|
||||
std::vector<ImageLayer> layers; // Bottom to top.
|
||||
// Saved with the project; undo covers them.
|
||||
std::vector<CanvasGuide> guides;
|
||||
// Part of the document so undo covers it; never saved.
|
||||
std::optional<DocumentSelection> selection;
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ std::optional<ProjectSnapshot> EditorSession::projectSnapshot() const
|
||||
snapshot.masks.insert({layer.id, layer.mask->asset});
|
||||
snapshot.manifest.layers.push_back(layer.hierarchyRecord());
|
||||
}
|
||||
// No guides leave the key out, as Swift writes nil.
|
||||
if (!m_document->guides.empty())
|
||||
snapshot.manifest.guides = m_document->guides;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -23,6 +26,7 @@ void EditorSession::installProject(const ProjectSnapshot &snapshot, const QStrin
|
||||
m_collapsedGroupIDs.clear();
|
||||
m_isMaskSelected = false;
|
||||
m_cropRect.reset();
|
||||
m_guideDrag = std::nullopt;
|
||||
m_transformEdit = std::nullopt;
|
||||
m_document = CanvasDocument(snapshot);
|
||||
// Install loads shapes and text; Swift's resize rebuild does not.
|
||||
@@ -53,6 +57,7 @@ void EditorSession::clearProject()
|
||||
m_isMaskSelected = false;
|
||||
m_cropRect.reset();
|
||||
m_transformEdit = std::nullopt;
|
||||
m_guideDrag = std::nullopt;
|
||||
m_document = std::nullopt;
|
||||
setActiveLayerID(std::nullopt);
|
||||
m_renamingLayerID = std::nullopt;
|
||||
|
||||
@@ -297,6 +297,7 @@ public:
|
||||
#include "Document/LayerAdjustment+Session.h"
|
||||
#include "Document/AdjustmentEditing+Session.h"
|
||||
#include "Document/LayerEffects+Session.h"
|
||||
#include "Document/Guides+Session.h"
|
||||
signals:
|
||||
void changed();
|
||||
|
||||
@@ -340,6 +341,17 @@ private:
|
||||
std::optional<QString> m_projectPath;
|
||||
bool m_showsPixelGrid = true;
|
||||
bool m_snappingEnabled = true;
|
||||
// The layout grid starts off; guides show; rulers hide.
|
||||
bool m_showsGrid = false;
|
||||
bool m_showsGuides = true;
|
||||
bool m_showsRulers = false;
|
||||
bool m_snapEnabled = true;
|
||||
bool m_snapToGuides = true;
|
||||
bool m_snapToGrid = false;
|
||||
bool m_snapToLayers = true;
|
||||
bool m_snapToDocumentBounds = true;
|
||||
bool m_locksGuides = false;
|
||||
std::optional<GuideDrag> m_guideDrag;
|
||||
bool m_showsSampleRing = true;
|
||||
std::optional<QRectF> m_cropRect;
|
||||
QString m_cropRatioChoice = QStringLiteral("Free");
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Swift's Guides extension, and the View menu's guide state.
|
||||
public:
|
||||
static constexpr double guideHitDistance = 5;
|
||||
bool showsGrid() const { return m_showsGrid; }
|
||||
void setShowsGrid(bool shows);
|
||||
bool showsGuides() const { return m_showsGuides; }
|
||||
void setShowsGuides(bool shows);
|
||||
bool showsRulers() const { return m_showsRulers; }
|
||||
void setShowsRulers(bool shows);
|
||||
// View > Snap To's master switch, beside 1.1.6's Snap.
|
||||
bool snapEnabled() const { return m_snapEnabled; }
|
||||
void setSnapEnabled(bool enabled);
|
||||
bool snapToGuides() const { return m_snapToGuides; }
|
||||
void setSnapToGuides(bool snaps);
|
||||
bool snapToGrid() const { return m_snapToGrid; }
|
||||
void setSnapToGrid(bool snaps);
|
||||
bool snapToLayers() const { return m_snapToLayers; }
|
||||
void setSnapToLayers(bool snaps);
|
||||
bool snapToDocumentBounds() const { return m_snapToDocumentBounds; }
|
||||
void setSnapToDocumentBounds(bool snaps);
|
||||
bool locksGuides() const { return m_locksGuides; }
|
||||
void setLocksGuides(bool locks);
|
||||
const std::optional<GuideDrag> &guideDrag() const { return m_guideDrag; }
|
||||
bool canClearGuides() const;
|
||||
bool canEditGuides() const;
|
||||
// The guides as shown, a drag under way included.
|
||||
std::vector<CanvasGuide> displayedGuides() const;
|
||||
std::optional<CanvasGuide> hitGuide(QPointF viewPoint, double tolerance = guideHitDistance) const;
|
||||
void beginGuideCreation(CanvasGuide::Axis axis, double position);
|
||||
void beginGuideMove(const CanvasGuide &guide);
|
||||
void moveGuideDrag(double position);
|
||||
// Dropped on a ruler, the guide goes.
|
||||
void finishGuideDrag(bool removing);
|
||||
void cancelGuideDrag();
|
||||
void clearGuides();
|
||||
void addGuide(const CanvasGuide &guide);
|
||||
// What a move or crop snaps to, per Snap To.
|
||||
SnapGuides alignmentSnapTargets(const QSet<QUuid> &moving, bool includeCenters) const;
|
||||
double snappedGuidePosition(double position, CanvasGuide::Axis axis, std::optional<QUuid> excluding) const;
|
||||
@@ -0,0 +1,313 @@
|
||||
#include "Document/Guides.h"
|
||||
#include "Document/EditorSession.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
CanvasGuide CanvasGuide::offset(double x, double y) const
|
||||
{
|
||||
CanvasGuide guide = *this;
|
||||
guide.position += axis == Axis::vertical ? x : y;
|
||||
return guide;
|
||||
}
|
||||
|
||||
CanvasGuide CanvasGuide::scaled(double x, double y) const
|
||||
{
|
||||
CanvasGuide guide = *this;
|
||||
guide.position *= axis == Axis::vertical ? x : y;
|
||||
return guide;
|
||||
}
|
||||
|
||||
CanvasGuide CanvasGuide::mirrored(bool horizontally, double center) const
|
||||
{
|
||||
CanvasGuide guide = *this;
|
||||
if ((horizontally && axis == Axis::vertical) || (!horizontally && axis == Axis::horizontal))
|
||||
guide.position = 2 * center - guide.position;
|
||||
return guide;
|
||||
}
|
||||
|
||||
QString rawValue(CanvasGuide::Axis axis)
|
||||
{
|
||||
return axis == CanvasGuide::Axis::vertical ? QStringLiteral("vertical") : QStringLiteral("horizontal");
|
||||
}
|
||||
|
||||
std::optional<CanvasGuide::Axis> guideAxis(const QString &text)
|
||||
{
|
||||
if (text == QLatin1String("vertical"))
|
||||
return CanvasGuide::Axis::vertical;
|
||||
if (text == QLatin1String("horizontal"))
|
||||
return CanvasGuide::Axis::horizontal;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<double> LayoutGrid::lines(double length)
|
||||
{
|
||||
if (!(length >= 0))
|
||||
return {0};
|
||||
std::vector<double> result;
|
||||
for (double value = 0; value <= length + 0.001; value += step)
|
||||
result.push_back(std::round(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LayoutGrid::isMajor(double value)
|
||||
{
|
||||
return std::abs(std::fmod(std::round(value), spacing)) < 0.001;
|
||||
}
|
||||
|
||||
void EditorSession::setShowsGrid(bool shows)
|
||||
{
|
||||
m_showsGrid = shows;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsGuides(bool shows)
|
||||
{
|
||||
m_showsGuides = shows;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsRulers(bool shows)
|
||||
{
|
||||
m_showsRulers = shows;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapEnabled(bool enabled)
|
||||
{
|
||||
m_snapEnabled = enabled;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToGuides(bool snaps)
|
||||
{
|
||||
m_snapToGuides = snaps;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToGrid(bool snaps)
|
||||
{
|
||||
m_snapToGrid = snaps;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToLayers(bool snaps)
|
||||
{
|
||||
m_snapToLayers = snaps;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToDocumentBounds(bool snaps)
|
||||
{
|
||||
m_snapToDocumentBounds = snaps;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setLocksGuides(bool locks)
|
||||
{
|
||||
m_locksGuides = locks;
|
||||
notify();
|
||||
}
|
||||
|
||||
bool EditorSession::canClearGuides() const
|
||||
{
|
||||
return m_document && !m_document->guides.empty();
|
||||
}
|
||||
|
||||
bool EditorSession::canEditGuides() const
|
||||
{
|
||||
return m_document && !m_locksGuides && !m_isProjectBusy && !m_isImporting && !m_showsNewDocument && !m_levels && !m_hueSaturation
|
||||
&& !m_filterEdit && !m_renamingLayerID;
|
||||
}
|
||||
|
||||
std::vector<CanvasGuide> EditorSession::displayedGuides() const
|
||||
{
|
||||
std::vector<CanvasGuide> guides = m_document ? m_document->guides : std::vector<CanvasGuide>();
|
||||
if (!m_guideDrag)
|
||||
return guides;
|
||||
const CanvasGuide current{m_guideDrag->id, m_guideDrag->axis, m_guideDrag->position};
|
||||
const auto found = std::find_if(guides.begin(), guides.end(), [&](const CanvasGuide &guide) { return guide.id == current.id; });
|
||||
if (found != guides.end())
|
||||
guides[size_t(found - guides.begin())] = current;
|
||||
else if (m_guideDrag->isNew)
|
||||
guides.push_back(current);
|
||||
return guides;
|
||||
}
|
||||
|
||||
std::optional<CanvasGuide> EditorSession::hitGuide(QPointF viewPoint, double tolerance) const
|
||||
{
|
||||
if (!m_showsGuides || m_locksGuides || !m_document)
|
||||
return std::nullopt;
|
||||
std::optional<CanvasGuide> best;
|
||||
double nearest = 0;
|
||||
for (const CanvasGuide &guide : displayedGuides()) {
|
||||
const QPointF at = viewport.viewPoint(QPointF(guide.position, guide.position), m_document->size());
|
||||
const double distance = guide.axis == CanvasGuide::Axis::vertical ? std::abs(viewPoint.x() - at.x()) : std::abs(viewPoint.y() - at.y());
|
||||
if (distance <= tolerance && (!best || distance < nearest)) {
|
||||
best = guide;
|
||||
nearest = distance;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void EditorSession::beginGuideCreation(CanvasGuide::Axis axis, double position)
|
||||
{
|
||||
if (!canEditGuides())
|
||||
return;
|
||||
m_showsGuides = true;
|
||||
m_guideDrag = GuideDrag{QUuid::createUuid(), axis, snappedGuidePosition(position, axis, std::nullopt), true, std::nullopt};
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::beginGuideMove(const CanvasGuide &guide)
|
||||
{
|
||||
if (!canEditGuides())
|
||||
return;
|
||||
m_guideDrag = GuideDrag{guide.id, guide.axis, guide.position, false, guide.position};
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::moveGuideDrag(double position)
|
||||
{
|
||||
if (!m_guideDrag)
|
||||
return;
|
||||
m_guideDrag->position = snappedGuidePosition(position, m_guideDrag->axis, m_guideDrag->id);
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::finishGuideDrag(bool removing)
|
||||
{
|
||||
if (!m_guideDrag)
|
||||
return;
|
||||
const GuideDrag drag = *std::exchange(m_guideDrag, std::nullopt);
|
||||
if (removing) {
|
||||
// A new guide dropped on a ruler was never there.
|
||||
if (!drag.isNew) {
|
||||
beginEdit(QStringLiteral("Delete Guide"));
|
||||
std::erase_if(m_document->guides, [&](const CanvasGuide &guide) { return guide.id == drag.id; });
|
||||
endEdit();
|
||||
}
|
||||
notify();
|
||||
return;
|
||||
}
|
||||
if (drag.isNew) {
|
||||
beginEdit(QStringLiteral("New Guide"));
|
||||
m_document->guides.push_back({drag.id, drag.axis, drag.position});
|
||||
endEdit();
|
||||
} else if (drag.original != drag.position) {
|
||||
beginEdit(QStringLiteral("Move Guide"));
|
||||
for (CanvasGuide &guide : m_document->guides) {
|
||||
if (guide.id == drag.id)
|
||||
guide.position = drag.position;
|
||||
}
|
||||
endEdit();
|
||||
}
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::cancelGuideDrag()
|
||||
{
|
||||
m_guideDrag = std::nullopt;
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::clearGuides()
|
||||
{
|
||||
if (!canClearGuides())
|
||||
return;
|
||||
beginEdit(QStringLiteral("Clear Guides"));
|
||||
m_document->guides.clear();
|
||||
endEdit();
|
||||
}
|
||||
|
||||
void EditorSession::addGuide(const CanvasGuide &guide)
|
||||
{
|
||||
if (!canEditGuides())
|
||||
return;
|
||||
m_showsGuides = true;
|
||||
beginEdit(QStringLiteral("New Guide"));
|
||||
m_document->guides.push_back(guide);
|
||||
endEdit();
|
||||
}
|
||||
|
||||
SnapGuides EditorSession::alignmentSnapTargets(const QSet<QUuid> &moving, bool includeCenters) const
|
||||
{
|
||||
SnapGuides targets;
|
||||
if (!m_snapEnabled || !m_document)
|
||||
return targets;
|
||||
const QSizeF size = m_document->size();
|
||||
if (m_snapToDocumentBounds) {
|
||||
targets.xs = {0, size.width()};
|
||||
targets.ys = {0, size.height()};
|
||||
if (includeCenters) {
|
||||
targets.xs.push_back(size.width() / 2);
|
||||
targets.ys.push_back(size.height() / 2);
|
||||
}
|
||||
}
|
||||
if (m_snapToLayers) {
|
||||
for (const ImageLayer &layer : m_document->renderLayers()) {
|
||||
if (!layer.asset || moving.contains(layer.id))
|
||||
continue;
|
||||
const QRectF box = TransformSnap::box(displayedTransform(layer));
|
||||
const QPointF low = box.topLeft(), high = box.bottomRight();
|
||||
// Swift's order: low edge, the middle when asked, high edge.
|
||||
targets.xs.push_back(std::round(low.x()));
|
||||
targets.ys.push_back(std::round(low.y()));
|
||||
if (includeCenters) {
|
||||
targets.xs.push_back(std::round((low.x() + high.x()) / 2));
|
||||
targets.ys.push_back(std::round((low.y() + high.y()) / 2));
|
||||
}
|
||||
targets.xs.push_back(std::round(high.x()));
|
||||
targets.ys.push_back(std::round(high.y()));
|
||||
}
|
||||
}
|
||||
// Hidden extras do not snap, as in Photoshop.
|
||||
if (m_snapToGrid && m_showsGrid) {
|
||||
for (double x : LayoutGrid::lines(size.width()))
|
||||
targets.xs.push_back(x);
|
||||
for (double y : LayoutGrid::lines(size.height()))
|
||||
targets.ys.push_back(y);
|
||||
}
|
||||
if (m_snapToGuides && m_showsGuides) {
|
||||
for (const CanvasGuide &guide : displayedGuides())
|
||||
(guide.axis == CanvasGuide::Axis::vertical ? targets.xs : targets.ys).push_back(guide.position);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
double EditorSession::snappedGuidePosition(double position, CanvasGuide::Axis axis, std::optional<QUuid> excluding) const
|
||||
{
|
||||
if (!m_snapEnabled || !m_document)
|
||||
return position;
|
||||
const double tolerance = TransformSnap::distance / std::max(viewport.pointsPerPixel(), 0.0001);
|
||||
const bool vertical = axis == CanvasGuide::Axis::vertical;
|
||||
const double length = vertical ? m_document->size().width() : m_document->size().height();
|
||||
std::vector<double> targets;
|
||||
if (m_snapToGrid && m_showsGrid)
|
||||
targets = LayoutGrid::lines(length);
|
||||
if (m_snapToGuides && m_showsGuides) {
|
||||
for (const CanvasGuide &guide : displayedGuides()) {
|
||||
if (guide.axis == axis && guide.id != excluding)
|
||||
targets.push_back(guide.position);
|
||||
}
|
||||
}
|
||||
if (m_snapToDocumentBounds)
|
||||
targets.insert(targets.end(), {0, length / 2, length});
|
||||
if (m_snapToLayers) {
|
||||
for (const ImageLayer &layer : m_document->renderLayers()) {
|
||||
if (!layer.asset)
|
||||
continue;
|
||||
const QRectF box = TransformSnap::box(displayedTransform(layer));
|
||||
const QPointF low = box.topLeft(), high = box.bottomRight();
|
||||
const double from = vertical ? low.x() : low.y(), to = vertical ? high.x() : high.y();
|
||||
targets.insert(targets.end(), {std::round(from), std::round((from + to) / 2), std::round(to)});
|
||||
}
|
||||
}
|
||||
// The nearest within reach; the first of equals wins.
|
||||
std::optional<double> best;
|
||||
for (const double target : targets) {
|
||||
if (std::abs(target - position) <= tolerance && (!best || std::abs(*best - position) > std::abs(target - position)))
|
||||
best = target;
|
||||
}
|
||||
return best.value_or(position);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
// A user-placed alignment line, in document pixels.
|
||||
struct CanvasGuide {
|
||||
enum class Axis { horizontal, vertical };
|
||||
QUuid id;
|
||||
Axis axis;
|
||||
// Y for a horizontal guide, X for a vertical one.
|
||||
double position;
|
||||
|
||||
CanvasGuide offset(double x, double y) const;
|
||||
CanvasGuide scaled(double x, double y) const;
|
||||
// Mirrored when it crosses the flip, staying on content.
|
||||
CanvasGuide mirrored(bool horizontally, double center) const;
|
||||
friend bool operator==(const CanvasGuide &, const CanvasGuide &) = default;
|
||||
};
|
||||
|
||||
// Swift's raw values, "horizontal" and "vertical".
|
||||
QString rawValue(CanvasGuide::Axis axis);
|
||||
std::optional<CanvasGuide::Axis> guideAxis(const QString &text);
|
||||
|
||||
// The non-printing grid: majors every 64 px, eight subdivisions.
|
||||
namespace LayoutGrid {
|
||||
inline constexpr double spacing = 64;
|
||||
inline constexpr int subdivisions = 8;
|
||||
inline constexpr double step = spacing / subdivisions;
|
||||
// Every line along a document edge, whole pixels.
|
||||
std::vector<double> lines(double length);
|
||||
bool isMajor(double value);
|
||||
}
|
||||
|
||||
// A create or move under way; the document changes after.
|
||||
struct GuideDrag {
|
||||
QUuid id;
|
||||
CanvasGuide::Axis axis;
|
||||
double position;
|
||||
bool isNew;
|
||||
std::optional<double> original;
|
||||
friend bool operator==(const GuideDrag &, const GuideDrag &) = default;
|
||||
};
|
||||
@@ -67,5 +67,7 @@ void EditorSession::flipCanvas(bool horizontally)
|
||||
const QTransform mirror = horizontally ? QTransform(-1, 0, 0, 1, size.width(), 0) : QTransform(1, 0, 0, -1, 0, size.height());
|
||||
m_document->selection->path = mirror.map(m_document->selection->path);
|
||||
}
|
||||
for (CanvasGuide &guide : m_document->guides)
|
||||
guide = guide.mirrored(horizontally, axis);
|
||||
endEdit();
|
||||
}
|
||||
@@ -108,4 +108,6 @@ struct Offset {
|
||||
|
||||
Offset offset(const QRectF &box, const std::vector<double> &xs, const std::vector<double> &ys,
|
||||
double tolerance);
|
||||
// The upright box round a transform's corners; in Crop.cpp.
|
||||
QRectF box(const LayerTransform &transform);
|
||||
}
|
||||
Reference in new issue
Block a user