View › Grid Settings…: spacing, subdivisions, colour, style and opacity, kept across launches (13.5b)
This commit is contained in:
1 parent
3992bc913d
commit
93a4c62b8f
29 files changed
+1353
-46
No files matched your search
@@ -188,7 +188,6 @@ public:
|
||||
int brushRevision() const { return m_brushRevision; }
|
||||
// Selected pixels on the move, from a Ctrl-drag or Ctrl-arrow.
|
||||
const PixelMove *pixelMove() const { return m_pixelMove.get(); }
|
||||
|
||||
// The one filter open; its sheet shows while it lasts.
|
||||
const std::optional<FilterEdit> &filterEdit() const { return m_filterEdit; }
|
||||
// The last committed filter's settings, where the next one starts.
|
||||
@@ -212,7 +211,6 @@ public:
|
||||
void setEffectsEditingOriginal(std::optional<LayerEffects> original);
|
||||
// The effect chosen in the list; see selectedEffect.
|
||||
const std::optional<LayerEffectSelection> &effectSelection() const { return m_effectSelection; }
|
||||
|
||||
bool isModified() const { return history.isModified(); }
|
||||
bool canUseHistory() const;
|
||||
// Undo's first press discards a pending gradient.
|
||||
@@ -223,7 +221,6 @@ public:
|
||||
// A transaction that nests; a whole gesture is one entry.
|
||||
void beginEdit(const QString &name);
|
||||
void endEdit();
|
||||
|
||||
bool canEditLayers() const;
|
||||
void addBlankLayer();
|
||||
void deleteLayer(QUuid id);
|
||||
@@ -367,6 +364,8 @@ private:
|
||||
bool m_snappingEnabled = true;
|
||||
// The layout grid starts off; guides show; rulers hide.
|
||||
bool m_showsGrid = ToolDefaults::boolean(QStringLiteral("grid"), false);
|
||||
LayoutGrid m_layoutGrid = LayoutGrid::stored();
|
||||
GridAppearance m_gridAppearance = GridAppearance::stored();
|
||||
bool m_showsGuides = ToolDefaults::boolean(QStringLiteral("guides"), true);
|
||||
bool m_showsRulers = ToolDefaults::boolean(QStringLiteral("rulers"), false);
|
||||
bool m_snapEnabled = ToolDefaults::boolean(QStringLiteral("snap"), true);
|
||||
|
||||
@@ -5,6 +5,11 @@ public:
|
||||
static constexpr double guideHitDistance = 5;
|
||||
bool showsGrid() const { return m_showsGrid; }
|
||||
void setShowsGrid(bool shows);
|
||||
// View > Grid Settings…: the person's, kept across launches.
|
||||
const LayoutGrid &layoutGrid() const { return m_layoutGrid; }
|
||||
void setLayoutGrid(const LayoutGrid &grid);
|
||||
const GridAppearance &gridAppearance() const { return m_gridAppearance; }
|
||||
void setGridAppearance(const GridAppearance &appearance);
|
||||
bool showsGuides() const { return m_showsGuides; }
|
||||
void setShowsGuides(bool shows);
|
||||
bool showsRulers() const { return m_showsRulers; }
|
||||
|
||||
+149
-8
@@ -1,5 +1,6 @@
|
||||
#include "Document/Guides.h"
|
||||
#include "Document/EditorSession.h"
|
||||
#include "Logging.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@@ -39,19 +40,159 @@ std::optional<CanvasGuide::Axis> guideAxis(const QString &text)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<double> LayoutGrid::lines(double length)
|
||||
LayoutGrid::LayoutGrid(int spacing, int subdivisions)
|
||||
: spacing(std::clamp(spacing, spacingLow, spacingHigh)), subdivisions(std::min({std::max(subdivisions, subdivisionLow), subdivisionHigh, this->spacing}))
|
||||
{
|
||||
}
|
||||
|
||||
// Counted, not summed: uneven steps stay on the majors.
|
||||
std::vector<double> LayoutGrid::lines(double length) const
|
||||
{
|
||||
if (!(length >= 0))
|
||||
return {0};
|
||||
const int count = int(std::floor(length / step() + 0.001));
|
||||
std::vector<double> result;
|
||||
for (double value = 0; value <= length + 0.001; value += step)
|
||||
result.push_back(std::round(value));
|
||||
for (int index = 0; index <= count; ++index)
|
||||
result.push_back(std::round(index * step()));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LayoutGrid::isMajor(double value)
|
||||
bool LayoutGrid::isMajor(double value) const
|
||||
{
|
||||
return std::abs(std::fmod(std::round(value), spacing)) < 0.001;
|
||||
return std::abs(std::fmod(std::round(value), double(spacing))) < 0.001;
|
||||
}
|
||||
|
||||
LayoutGrid LayoutGrid::stored()
|
||||
{
|
||||
return LayoutGrid(ToolDefaults::integer(QStringLiteral("gridSpacing"), 64), ToolDefaults::integer(QStringLiteral("gridSubdivisions"), 8));
|
||||
}
|
||||
|
||||
void LayoutGrid::store() const
|
||||
{
|
||||
ToolDefaults::set(spacing, QStringLiteral("gridSpacing"));
|
||||
ToolDefaults::set(subdivisions, QStringLiteral("gridSubdivisions"));
|
||||
}
|
||||
|
||||
QString rawValue(GridAppearance::Preset preset)
|
||||
{
|
||||
using Preset = GridAppearance::Preset;
|
||||
switch (preset) {
|
||||
case Preset::lightGray: return QStringLiteral("Light Gray");
|
||||
case Preset::lightBlue: return QStringLiteral("Light Blue");
|
||||
case Preset::lightRed: return QStringLiteral("Light Red");
|
||||
case Preset::green: return QStringLiteral("Green");
|
||||
case Preset::mediumBlue: return QStringLiteral("Medium Blue");
|
||||
case Preset::yellow: return QStringLiteral("Yellow");
|
||||
case Preset::magenta: return QStringLiteral("Magenta");
|
||||
case Preset::cyan: return QStringLiteral("Cyan");
|
||||
case Preset::black: return QStringLiteral("Black");
|
||||
case Preset::custom: return QStringLiteral("Custom");
|
||||
}
|
||||
throw std::logic_error("unknown grid preset");
|
||||
}
|
||||
|
||||
QString rawValue(GridAppearance::Style style)
|
||||
{
|
||||
switch (style) {
|
||||
case GridAppearance::Style::lines: return QStringLiteral("Lines");
|
||||
case GridAppearance::Style::dashedLines: return QStringLiteral("Dashed Lines");
|
||||
case GridAppearance::Style::dots: return QStringLiteral("Dots");
|
||||
}
|
||||
throw std::logic_error("unknown grid style");
|
||||
}
|
||||
|
||||
std::optional<PaletteColor> presetColor(GridAppearance::Preset preset)
|
||||
{
|
||||
using Preset = GridAppearance::Preset;
|
||||
switch (preset) {
|
||||
case Preset::lightGray: return PaletteColor{0.7, 0.7, 0.7};
|
||||
case Preset::lightBlue: return PaletteColor{0.29, 0.78, 1};
|
||||
case Preset::lightRed: return PaletteColor{1, 0.4, 0.4};
|
||||
case Preset::green: return PaletteColor{0.25, 0.8, 0.25};
|
||||
case Preset::mediumBlue: return PaletteColor{0.2, 0.4, 1};
|
||||
case Preset::yellow: return PaletteColor{1, 1, 0};
|
||||
case Preset::magenta: return PaletteColor{1, 0, 1};
|
||||
case Preset::cyan: return PaletteColor{0, 1, 1};
|
||||
case Preset::black: return PaletteColor::black();
|
||||
case Preset::custom: return std::nullopt;
|
||||
}
|
||||
throw std::logic_error("unknown grid preset");
|
||||
}
|
||||
|
||||
std::vector<double> dashes(GridAppearance::Style style)
|
||||
{
|
||||
switch (style) {
|
||||
case GridAppearance::Style::lines: return {};
|
||||
case GridAppearance::Style::dashedLines: return {4, 3};
|
||||
case GridAppearance::Style::dots: return {1, 2};
|
||||
}
|
||||
throw std::logic_error("unknown grid style");
|
||||
}
|
||||
|
||||
PaletteColor GridAppearance::color() const
|
||||
{
|
||||
return presetColor(preset).value_or(customColor);
|
||||
}
|
||||
|
||||
// A grid that is on never disappears: 1% at least.
|
||||
double GridAppearance::majorAlpha() const
|
||||
{
|
||||
return std::clamp(opacity, opacityLow, opacityHigh) / 100.0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// A stored name, else the default and a warning.
|
||||
template <typename Value, typename Values>
|
||||
Value named(const QString &key, const Values &values, Value fallback)
|
||||
{
|
||||
const QString stored = ToolDefaults::text(key);
|
||||
for (const Value value : values)
|
||||
if (rawValue(value) == stored)
|
||||
return value;
|
||||
if (!stored.isEmpty())
|
||||
qCWarning(lcApp).noquote() << "ignoring the tool setting" << key << "of" << stored;
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
GridAppearance GridAppearance::stored()
|
||||
{
|
||||
GridAppearance appearance;
|
||||
appearance.preset = named(QStringLiteral("gridColor"), allGridPresets, appearance.preset);
|
||||
const QString custom = ToolDefaults::text(QStringLiteral("gridCustomColor"));
|
||||
if (const std::optional<PaletteColor> color = PaletteColor::fromHex(custom))
|
||||
appearance.customColor = *color;
|
||||
else if (!custom.isEmpty())
|
||||
qCWarning(lcApp).noquote() << "ignoring the tool setting gridCustomColor of" << custom;
|
||||
appearance.style = named(QStringLiteral("gridStyle"), allGridStyles, appearance.style);
|
||||
appearance.opacity = ToolDefaults::integer(QStringLiteral("gridOpacity"), appearance.opacity);
|
||||
return appearance;
|
||||
}
|
||||
|
||||
void GridAppearance::store() const
|
||||
{
|
||||
ToolDefaults::set(rawValue(preset), QStringLiteral("gridColor"));
|
||||
ToolDefaults::set(customColor.hex(), QStringLiteral("gridCustomColor"));
|
||||
ToolDefaults::set(rawValue(style), QStringLiteral("gridStyle"));
|
||||
ToolDefaults::set(opacity, QStringLiteral("gridOpacity"));
|
||||
}
|
||||
|
||||
void EditorSession::setLayoutGrid(const LayoutGrid &grid)
|
||||
{
|
||||
if (grid == m_layoutGrid)
|
||||
return;
|
||||
m_layoutGrid = grid;
|
||||
grid.store();
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setGridAppearance(const GridAppearance &appearance)
|
||||
{
|
||||
if (appearance == m_gridAppearance)
|
||||
return;
|
||||
m_gridAppearance = appearance;
|
||||
appearance.store();
|
||||
notify();
|
||||
}
|
||||
|
||||
void EditorSession::setShowsGrid(bool shows)
|
||||
@@ -274,9 +415,9 @@ SnapGuides EditorSession::alignmentSnapTargets(const QSet<QUuid> &moving, bool i
|
||||
}
|
||||
// Hidden extras do not snap, as in Photoshop.
|
||||
if (m_snapToGrid && m_showsGrid) {
|
||||
for (double x : LayoutGrid::lines(size.width()))
|
||||
for (double x : m_layoutGrid.lines(size.width()))
|
||||
targets.xs.push_back(x);
|
||||
for (double y : LayoutGrid::lines(size.height()))
|
||||
for (double y : m_layoutGrid.lines(size.height()))
|
||||
targets.ys.push_back(y);
|
||||
}
|
||||
if (m_snapToGuides && m_showsGuides) {
|
||||
@@ -295,7 +436,7 @@ double EditorSession::snappedGuidePosition(double position, CanvasGuide::Axis ax
|
||||
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);
|
||||
targets = m_layoutGrid.lines(length);
|
||||
if (m_snapToGuides && m_showsGuides) {
|
||||
for (const CanvasGuide &guide : displayedGuides()) {
|
||||
if (guide.axis == axis && guide.id != excluding)
|
||||
|
||||
+52
-9
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include "Document/ColorPalette.h"
|
||||
#include <array>
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
@@ -24,15 +26,56 @@ struct CanvasGuide {
|
||||
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);
|
||||
}
|
||||
// The non-printing grid: majors every `spacing` px, split in parts.
|
||||
struct LayoutGrid {
|
||||
static constexpr int spacingLow = 2, spacingHigh = 4096;
|
||||
static constexpr int subdivisionLow = 1, subdivisionHigh = 64;
|
||||
// Plain members, Swift's `let`s, so the struct stays assignable.
|
||||
int spacing;
|
||||
// Never finer than a pixel.
|
||||
int subdivisions;
|
||||
explicit LayoutGrid(int spacing = 64, int subdivisions = 8);
|
||||
double step() const { return double(spacing) / subdivisions; }
|
||||
// Every line along an edge from the origin, whole pixels.
|
||||
std::vector<double> lines(double length) const;
|
||||
bool isMajor(double value) const;
|
||||
// The person's grid, kept across projects and launches.
|
||||
static LayoutGrid stored();
|
||||
void store() const;
|
||||
friend bool operator==(const LayoutGrid &, const LayoutGrid &) = default;
|
||||
};
|
||||
|
||||
// How the grid is drawn, after Photoshop's Grid settings.
|
||||
struct GridAppearance {
|
||||
enum class Preset { lightGray, lightBlue, lightRed, green, mediumBlue, yellow, magenta, cyan, black, custom };
|
||||
enum class Style { lines, dashedLines, dots };
|
||||
static constexpr int opacityLow = 1, opacityHigh = 100;
|
||||
Preset preset = Preset::lightGray;
|
||||
// Custom's colour, kept while another preset is chosen.
|
||||
PaletteColor customColor{0.7, 0.7, 0.7};
|
||||
Style style = Style::lines;
|
||||
// The majors' opacity, in percent.
|
||||
int opacity = 45;
|
||||
PaletteColor color() const;
|
||||
double majorAlpha() const;
|
||||
// A little over half the majors': 28% beside 45%.
|
||||
double subdivisionAlpha() const { return majorAlpha() * 28 / 45; }
|
||||
static GridAppearance stored();
|
||||
void store() const;
|
||||
friend bool operator==(const GridAppearance &, const GridAppearance &) = default;
|
||||
};
|
||||
|
||||
inline constexpr std::array allGridPresets{GridAppearance::Preset::lightGray, GridAppearance::Preset::lightBlue, GridAppearance::Preset::lightRed,
|
||||
GridAppearance::Preset::green, GridAppearance::Preset::mediumBlue, GridAppearance::Preset::yellow,
|
||||
GridAppearance::Preset::magenta, GridAppearance::Preset::cyan, GridAppearance::Preset::black,
|
||||
GridAppearance::Preset::custom};
|
||||
inline constexpr std::array allGridStyles{GridAppearance::Style::lines, GridAppearance::Style::dashedLines, GridAppearance::Style::dots};
|
||||
QString rawValue(GridAppearance::Preset preset);
|
||||
QString rawValue(GridAppearance::Style style);
|
||||
// None for Custom, which takes the appearance's own.
|
||||
std::optional<PaletteColor> presetColor(GridAppearance::Preset preset);
|
||||
// On and off in screen points; empty is solid.
|
||||
std::vector<double> dashes(GridAppearance::Style style);
|
||||
|
||||
// A create or move under way; the document changes after.
|
||||
struct GuideDrag {
|
||||
|
||||
@@ -31,3 +31,34 @@ void ToolDefaults::set(bool value, const QString &key)
|
||||
if (enabled)
|
||||
QSettings().setValue(prefix + key, value);
|
||||
}
|
||||
|
||||
int ToolDefaults::integer(const QString &key, int fallback)
|
||||
{
|
||||
if (!enabled)
|
||||
return fallback;
|
||||
const QString stored = QSettings().value(prefix + key).toString();
|
||||
bool number = false;
|
||||
const int value = stored.toInt(&number);
|
||||
if (number)
|
||||
return value;
|
||||
if (!stored.isEmpty())
|
||||
qCWarning(lcApp).noquote() << "ignoring the tool setting" << key << "of" << stored;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void ToolDefaults::set(int value, const QString &key)
|
||||
{
|
||||
if (enabled)
|
||||
QSettings().setValue(prefix + key, value);
|
||||
}
|
||||
|
||||
QString ToolDefaults::text(const QString &key)
|
||||
{
|
||||
return enabled ? QSettings().value(prefix + key).toString() : QString();
|
||||
}
|
||||
|
||||
void ToolDefaults::set(const QString &value, const QString &key)
|
||||
{
|
||||
if (enabled)
|
||||
QSettings().setValue(prefix + key, value);
|
||||
}
|
||||
@@ -8,4 +8,10 @@ void enable();
|
||||
// The stored toggle, else the fallback.
|
||||
bool boolean(const QString &key, bool fallback);
|
||||
void set(bool value, const QString &key);
|
||||
// The grid's numbers and names, as Swift's int and string.
|
||||
int integer(const QString &key, int fallback);
|
||||
void set(int value, const QString &key);
|
||||
// Empty when nothing is stored, as Swift's "" fallback.
|
||||
QString text(const QString &key);
|
||||
void set(const QString &value, const QString &key);
|
||||
}
|
||||
Reference in new issue
Block a user