Middle-button pan, persistent tool settings, Auto Select's foreground rule, the new icon (12.6b)
This commit is contained in:
1 parent
5a2fa91bc7
commit
29eab10edc
27 files changed
+390
-22
No files matched your search
@@ -1,4 +1,5 @@
|
||||
#include "Document/ProjectWorkspace.h"
|
||||
#include "Document/ToolDefaults.h"
|
||||
#include "Logging.h"
|
||||
#include "UI/OmarchyTheme.h"
|
||||
#include "UI/ProjectWorkspaceView.h"
|
||||
@@ -11,6 +12,8 @@ int main(int argc, char **argv)
|
||||
qSetMessagePattern(QStringLiteral("%{time yyyy-MM-dd hh:mm:ss.zzz} %{type} %{category}: %{message}"));
|
||||
QApplication application(argc, argv);
|
||||
QApplication::setApplicationName(QStringLiteral("OmaPhoto"));
|
||||
// Tool toggles outlive the run, as Swift's user defaults.
|
||||
ToolDefaults::enable();
|
||||
QApplication::setApplicationVersion(QStringLiteral(OMAPHOTO_VERSION));
|
||||
// The desktop entry's name: icons and windows find each other.
|
||||
QGuiApplication::setDesktopFileName(QStringLiteral("io.github.ZacharyZhang_NY.OmaPhoto"));
|
||||
|
||||
@@ -85,12 +85,14 @@ void EditorSession::beginTransform(bool persistent)
|
||||
void EditorSession::setTransformAutoSelect(bool picks)
|
||||
{
|
||||
m_transformAutoSelect = picks;
|
||||
ToolDefaults::set(picks, QStringLiteral("autoSelect"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsTransformControls(bool shows)
|
||||
{
|
||||
m_showsTransformControls = shows;
|
||||
ToolDefaults::set(shows, QStringLiteral("transformControls"));
|
||||
notify();
|
||||
}
|
||||
|
||||
|
||||
@@ -318,6 +318,7 @@ void EditorSession::zoom(double value, std::optional<QPointF> anchor)
|
||||
void EditorSession::setShowsPixelGrid(bool shows)
|
||||
{
|
||||
m_showsPixelGrid = shows;
|
||||
ToolDefaults::set(shows, QStringLiteral("pixelGrid"));
|
||||
notify();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Document/SelectionClipboard.h"
|
||||
#include "Document/SelectionEdits.h"
|
||||
#include "Document/SmudgeLiquify.h"
|
||||
#include "Document/ToolDefaults.h"
|
||||
#include "Document/DocumentHistory.h"
|
||||
#include "Document/EditorSession+Jobs.h"
|
||||
#include "Document/EditorSession+Model.h"
|
||||
@@ -344,8 +345,8 @@ private:
|
||||
|
||||
std::optional<TransformEdit> m_transformEdit;
|
||||
std::optional<TransformDuplicate> m_transformDuplicate;
|
||||
bool m_transformAutoSelect = false;
|
||||
bool m_showsTransformControls = true;
|
||||
bool m_transformAutoSelect = ToolDefaults::boolean(QStringLiteral("autoSelect"), false);
|
||||
bool m_showsTransformControls = ToolDefaults::boolean(QStringLiteral("transformControls"), true);
|
||||
bool m_locksTransformRatio = true;
|
||||
std::optional<BlendPreview> m_blendPreview;
|
||||
std::optional<QUuid> m_opacityEditLayerID;
|
||||
@@ -358,18 +359,18 @@ private:
|
||||
std::optional<QString> m_importError;
|
||||
std::optional<QUuid> m_renamingLayerID;
|
||||
std::optional<QString> m_projectPath;
|
||||
bool m_showsPixelGrid = true;
|
||||
bool m_showsPixelGrid = ToolDefaults::boolean(QStringLiteral("pixelGrid"), 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;
|
||||
bool m_showsGrid = ToolDefaults::boolean(QStringLiteral("grid"), false);
|
||||
bool m_showsGuides = ToolDefaults::boolean(QStringLiteral("guides"), true);
|
||||
bool m_showsRulers = ToolDefaults::boolean(QStringLiteral("rulers"), false);
|
||||
bool m_snapEnabled = ToolDefaults::boolean(QStringLiteral("snap"), true);
|
||||
bool m_snapToGuides = ToolDefaults::boolean(QStringLiteral("snapGuides"), true);
|
||||
bool m_snapToGrid = ToolDefaults::boolean(QStringLiteral("snapGrid"), false);
|
||||
bool m_snapToLayers = ToolDefaults::boolean(QStringLiteral("snapLayers"), true);
|
||||
bool m_snapToDocumentBounds = ToolDefaults::boolean(QStringLiteral("snapBounds"), true);
|
||||
bool m_locksGuides = ToolDefaults::boolean(QStringLiteral("lockGuides"), false);
|
||||
std::optional<GuideDrag> m_guideDrag;
|
||||
bool m_showsSampleRing = true;
|
||||
std::optional<QRectF> m_cropRect;
|
||||
|
||||
@@ -57,54 +57,63 @@ bool LayoutGrid::isMajor(double value)
|
||||
void EditorSession::setShowsGrid(bool shows)
|
||||
{
|
||||
m_showsGrid = shows;
|
||||
ToolDefaults::set(shows, QStringLiteral("grid"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsGuides(bool shows)
|
||||
{
|
||||
m_showsGuides = shows;
|
||||
ToolDefaults::set(shows, QStringLiteral("guides"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsRulers(bool shows)
|
||||
{
|
||||
m_showsRulers = shows;
|
||||
ToolDefaults::set(shows, QStringLiteral("rulers"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapEnabled(bool enabled)
|
||||
{
|
||||
m_snapEnabled = enabled;
|
||||
ToolDefaults::set(enabled, QStringLiteral("snap"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToGuides(bool snaps)
|
||||
{
|
||||
m_snapToGuides = snaps;
|
||||
ToolDefaults::set(snaps, QStringLiteral("snapGuides"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToGrid(bool snaps)
|
||||
{
|
||||
m_snapToGrid = snaps;
|
||||
ToolDefaults::set(snaps, QStringLiteral("snapGrid"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToLayers(bool snaps)
|
||||
{
|
||||
m_snapToLayers = snaps;
|
||||
ToolDefaults::set(snaps, QStringLiteral("snapLayers"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setSnapToDocumentBounds(bool snaps)
|
||||
{
|
||||
m_snapToDocumentBounds = snaps;
|
||||
ToolDefaults::set(snaps, QStringLiteral("snapBounds"));
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setLocksGuides(bool locks)
|
||||
{
|
||||
m_locksGuides = locks;
|
||||
ToolDefaults::set(locks, QStringLiteral("lockGuides"));
|
||||
notify();
|
||||
}
|
||||
|
||||
@@ -155,6 +164,7 @@ void EditorSession::beginGuideCreation(CanvasGuide::Axis axis, double position)
|
||||
if (!canEditGuides())
|
||||
return;
|
||||
m_showsGuides = true;
|
||||
ToolDefaults::set(true, QStringLiteral("guides"));
|
||||
m_guideDrag = GuideDrag{QUuid::createUuid(), axis, snappedGuidePosition(position, axis, std::nullopt), true, std::nullopt};
|
||||
notify();
|
||||
}
|
||||
@@ -225,6 +235,7 @@ void EditorSession::addGuide(const CanvasGuide &guide)
|
||||
if (!canEditGuides())
|
||||
return;
|
||||
m_showsGuides = true;
|
||||
ToolDefaults::set(true, QStringLiteral("guides"));
|
||||
beginEdit(QStringLiteral("New Guide"));
|
||||
m_document->guides.push_back(guide);
|
||||
endEdit();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "Document/ToolDefaults.h"
|
||||
#include "Logging.h"
|
||||
#include <QSettings>
|
||||
|
||||
namespace {
|
||||
const QString prefix = QStringLiteral("tool.");
|
||||
// Swift's XCTest check: only the app keeps the toggles.
|
||||
bool enabled = false;
|
||||
}
|
||||
|
||||
void ToolDefaults::enable()
|
||||
{
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
bool ToolDefaults::boolean(const QString &key, bool fallback)
|
||||
{
|
||||
if (!enabled)
|
||||
return fallback;
|
||||
// The settings file keeps text; this run's writes are booleans.
|
||||
const QString stored = QSettings().value(prefix + key).toString();
|
||||
if (stored == QLatin1String("true") || stored == QLatin1String("false"))
|
||||
return stored == QLatin1String("true");
|
||||
if (!stored.isEmpty())
|
||||
qCWarning(lcApp).noquote() << "ignoring the tool setting" << key << "of" << stored;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void ToolDefaults::set(bool value, const QString &key)
|
||||
{
|
||||
if (enabled)
|
||||
QSettings().setValue(prefix + key, value);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
|
||||
// Swift's ToolDefaults: toggles kept for the person, across launches.
|
||||
namespace ToolDefaults {
|
||||
// The app turns them on; tests keep the compiled defaults.
|
||||
void enable();
|
||||
// The stored toggle, else the fallback.
|
||||
bool boolean(const QString &key, bool fallback);
|
||||
void set(bool value, const QString &key);
|
||||
}
|
||||
@@ -114,7 +114,7 @@ void CanvasView::endBrushTipDrag(QPointF point)
|
||||
// The circle at the pointer, a stroke's own size.
|
||||
void CanvasView::updateBrushCursor()
|
||||
{
|
||||
const bool shows = isBrushTool(m_session.tool()) && !m_spaceHeld && !picking();
|
||||
const bool shows = isBrushTool(m_session.tool()) && !m_spaceHeld && !picking() && !m_middlePanPoint;
|
||||
const double diameter = m_session.brushStroke() ? m_session.brushStroke()->settings.diameter : m_session.brushSettings().diameter;
|
||||
// Clone Stamp marks its source; between strokes, previews a click.
|
||||
std::optional<QPointF> sample;
|
||||
|
||||
@@ -250,8 +250,10 @@ QCursor CanvasView::pixelDragCursor(bool duplicate, double ratio)
|
||||
void CanvasView::updateCursor()
|
||||
{
|
||||
const NavigationTool tool = m_session.tool();
|
||||
// A transform, outline or pixel drag keeps its cursor.
|
||||
if (m_dragCursor)
|
||||
// A middle-button pan outranks every drag; another keeps its cursor.
|
||||
if (m_middlePanPoint)
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
else if (m_dragCursor)
|
||||
setCursor(*m_dragCursor);
|
||||
else if (m_selectionDragStart)
|
||||
setCursor(moveSelectionCursor(devicePixelRatio()));
|
||||
|
||||
@@ -114,6 +114,13 @@ void CanvasView::wheelEvent(QWheelEvent *event)
|
||||
|
||||
void CanvasView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
// The middle button pans in any tool, beside the left.
|
||||
if (event->button() == Qt::MiddleButton && m_session.document()) {
|
||||
m_middlePanPoint = event->position();
|
||||
updateBrushCursor();
|
||||
updateCursor();
|
||||
return;
|
||||
}
|
||||
if (event->button() == Qt::RightButton && isBrushTool(m_session.tool()) && !m_session.brushStroke() && !m_session.warpStroke() && !m_spaceHeld) {
|
||||
beginBrushTipDrag(event->position(), event->modifiers());
|
||||
return;
|
||||
@@ -199,6 +206,16 @@ void CanvasView::mouseMoveEvent(QMouseEvent *event)
|
||||
const QPointF point = event->position();
|
||||
m_hover = point;
|
||||
readModifiers(event->modifiers());
|
||||
if (m_middlePanPoint && event->buttons().testFlag(Qt::MiddleButton)) {
|
||||
m_session.viewport.translate(QSizeF(point.x() - m_middlePanPoint->x(), point.y() - m_middlePanPoint->y()));
|
||||
m_middlePanPoint = point;
|
||||
m_session.notify();
|
||||
if (!event->buttons().testFlag(Qt::LeftButton))
|
||||
return;
|
||||
} else if (m_middlePanPoint) {
|
||||
// Qt can lose a release: the pan ends here.
|
||||
endMiddlePan();
|
||||
}
|
||||
// Without the left button a text gesture's release was lost.
|
||||
const bool held = event->buttons().testFlag(Qt::LeftButton);
|
||||
if (m_textBoxAnchor) {
|
||||
@@ -258,6 +275,10 @@ void CanvasView::mouseMoveEvent(QMouseEvent *event)
|
||||
|
||||
void CanvasView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::MiddleButton && m_middlePanPoint) {
|
||||
endMiddlePan();
|
||||
return;
|
||||
}
|
||||
if (event->button() == Qt::RightButton && m_brushTipDrag) {
|
||||
endBrushTipDrag(event->position());
|
||||
return;
|
||||
@@ -344,3 +365,11 @@ void CanvasView::leaveEvent(QEvent *event)
|
||||
updateBrushCursor();
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
// The tool's own cursor and circle come back at once.
|
||||
void CanvasView::endMiddlePan()
|
||||
{
|
||||
m_middlePanPoint.reset();
|
||||
updateCursor();
|
||||
updateBrushCursor();
|
||||
}
|
||||
@@ -6,6 +6,19 @@
|
||||
#include <numbers>
|
||||
|
||||
namespace {
|
||||
// Whether `upper` is painted above `lower`: later is higher.
|
||||
bool above(const std::vector<ImageLayer> &layers, QUuid upper, QUuid lower)
|
||||
{
|
||||
const auto index = [&](QUuid id) -> std::optional<size_t> {
|
||||
for (size_t at = 0; at < layers.size(); ++at)
|
||||
if (layers[at].id == id)
|
||||
return at;
|
||||
return std::nullopt;
|
||||
};
|
||||
// Both are drawn: the pointer's layer, the visible active one.
|
||||
return index(upper).value() > index(lower).value();
|
||||
}
|
||||
|
||||
// Four arrows about `center`, each `reach` long, traced clockwise.
|
||||
QPainterPath fourArrowPath(QPointF center, double reach, double shaft, double head, double headLength)
|
||||
{
|
||||
@@ -149,8 +162,12 @@ std::optional<CanvasView::PressTarget> CanvasView::transformPressLayer(QPointF p
|
||||
if ((box && box->contains(pixel)) || !(picks && m_session.transformAutoSelect()) || !underPointer)
|
||||
return PressTarget{*m_session.activeLayerID(), false};
|
||||
}
|
||||
if (active && m_session.editedTransform(*active).contains(pixel))
|
||||
if (active && m_session.editedTransform(*active).contains(pixel)) {
|
||||
// Auto Select prefers a layer above, as over a background.
|
||||
if (picks && m_session.transformAutoSelect() && underPointer && above(rendered, *underPointer, active->id))
|
||||
return PressTarget{*underPointer, true};
|
||||
return PressTarget{active->id, false};
|
||||
}
|
||||
if (picks && (m_session.transformAutoSelect() || control) && underPointer)
|
||||
return PressTarget{*underPointer, true};
|
||||
if (active)
|
||||
|
||||
@@ -208,6 +208,7 @@ private:
|
||||
void dragBrushTip(QPointF point, Qt::KeyboardModifiers modifiers);
|
||||
void endBrushTipDrag(QPointF point);
|
||||
void updateBrushCursor();
|
||||
void endMiddlePan();
|
||||
// Clone Stamp's preview: one click's coverage, and the source there.
|
||||
QImage cloneTip(double diameter, double hardness);
|
||||
QImage clonePreview(QPointF center, double diameter, const CanvasDocument &document);
|
||||
@@ -268,6 +269,8 @@ private:
|
||||
std::optional<int> m_panKey;
|
||||
bool m_optionHeld = false;
|
||||
std::optional<QPointF> m_lastDragPoint;
|
||||
// Where a middle-button pan last was, its own drag point.
|
||||
std::optional<QPointF> m_middlePanPoint;
|
||||
std::optional<ZoomDrag> m_zoomDrag;
|
||||
bool m_shiftHeld = false;
|
||||
// Document point where a drag of the outline began.
|
||||
|
||||
Reference in new issue
Block a user