12.2a: folders duplicate with their contents, folder opacity, format version 8, View > Snap (Compositor 1.1.5–1.1.6)
This commit is contained in:
1 parent
ee34c338d4
commit
f1b2c65c1e
52 files changed
+909
-239
No files matched your search
@@ -118,6 +118,8 @@ CompositorMenus::CompositorMenus(ProjectWorkspace &workspace, QMenuBar &bar, QWi
|
||||
add(view, QStringLiteral("zoomOut"), QStringLiteral("Zoom Out"), QKeySequence(Qt::CTRL | Qt::Key_Minus), [this] { session().zoom(session().viewport.zoom() / 1.25); });
|
||||
add(view, QStringLiteral("pixelGrid"), QStringLiteral("Pixel Grid (800% and above)"), QKeySequence(), [this] { session().setShowsPixelGrid(!session().showsPixelGrid()); })
|
||||
->setCheckable(true);
|
||||
add(view, QStringLiteral("snap"), QStringLiteral("Snap"), QKeySequence(), [this] { session().setSnappingEnabled(!session().snappingEnabled()); })
|
||||
->setCheckable(true);
|
||||
// Ctrl+H: the Move tool's box; the desktop hides apps.
|
||||
add(view, QStringLiteral("transformControls"), QStringLiteral("Show Transform Controls"), QKeySequence(Qt::CTRL | Qt::Key_H),
|
||||
[this] { session().setShowsTransformControls(!session().showsTransformControls()); })
|
||||
@@ -275,6 +277,7 @@ void CompositorMenus::synchronize()
|
||||
for (const char *name : {"fit", "actualPixels", "zoomIn", "zoomOut"})
|
||||
action(QString::fromLatin1(name))->setEnabled(drawn);
|
||||
action(QStringLiteral("pixelGrid"))->setChecked(s.showsPixelGrid());
|
||||
action(QStringLiteral("snap"))->setChecked(s.snappingEnabled());
|
||||
action(QStringLiteral("transformControls"))->setChecked(s.showsTransformControls());
|
||||
action(QStringLiteral("transformControls"))->setEnabled(s.tool() == NavigationTool::move && s.document().has_value());
|
||||
const bool selected = s.selection().has_value();
|
||||
|
||||
+11
-10
@@ -53,7 +53,7 @@ EffectsSheet::EffectsSheet(EditorSession &session, LayerEffectKind kind, QWidget
|
||||
header(QStringLiteral("Stroke"), true);
|
||||
colour(true);
|
||||
slider(QStringLiteral("Size"), [](const LayerEffects &e) { return e.stroke ? std::optional(e.stroke->size) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.stroke->size = value; }, 1, StrokeEffect::maxSize, QStringLiteral("px"));
|
||||
[](LayerEffects &e, double value) { e.stroke->size = value; }, 0, 20, QStringLiteral("px"), StrokeEffect::maxSize);
|
||||
slider(QStringLiteral("Opacity"), [](const LayerEffects &e) { return percent(e.stroke); },
|
||||
[](LayerEffects &e, double value) { e.stroke->opacity = value / 100; }, 0, 100, QStringLiteral("%"));
|
||||
break;
|
||||
@@ -64,9 +64,9 @@ EffectsSheet::EffectsSheet(EditorSession &session, LayerEffectKind kind, QWidget
|
||||
slider(QStringLiteral("Angle"), [](const LayerEffects &e) { return e.shadow ? std::optional(e.shadow->angle) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.shadow->angle = value; }, -180, 180, QStringLiteral("°"));
|
||||
slider(QStringLiteral("Distance"), [](const LayerEffects &e) { return e.shadow ? std::optional(e.shadow->distance) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.shadow->distance = value; }, 0, 300, QStringLiteral("px"));
|
||||
[](LayerEffects &e, double value) { e.shadow->distance = value; }, 0, 100, QStringLiteral("px"), 5000);
|
||||
slider(QStringLiteral("Blur"), [](const LayerEffects &e) { return e.shadow ? std::optional(e.shadow->blur) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.shadow->blur = value; }, 0, 300, QStringLiteral("px"));
|
||||
[](LayerEffects &e, double value) { e.shadow->blur = value; }, 0, 100, QStringLiteral("px"), 500);
|
||||
break;
|
||||
case LayerEffectKind::colorOverlay:
|
||||
header(QStringLiteral("Color Overlay"), false);
|
||||
@@ -80,9 +80,9 @@ EffectsSheet::EffectsSheet(EditorSession &session, LayerEffectKind kind, QWidget
|
||||
slider(QStringLiteral("Angle"), [](const LayerEffects &e) { return e.innerShadow ? std::optional(e.innerShadow->angle) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.innerShadow->angle = value; }, -180, 180, QStringLiteral("°"));
|
||||
slider(QStringLiteral("Distance"), [](const LayerEffects &e) { return e.innerShadow ? std::optional(e.innerShadow->distance) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.innerShadow->distance = value; }, 0, 300, QStringLiteral("px"));
|
||||
[](LayerEffects &e, double value) { e.innerShadow->distance = value; }, 0, 50, QStringLiteral("px"), 5000);
|
||||
slider(QStringLiteral("Blur"), [](const LayerEffects &e) { return e.innerShadow ? std::optional(e.innerShadow->blur) : std::nullopt; },
|
||||
[](LayerEffects &e, double value) { e.innerShadow->blur = value; }, 0, 300, QStringLiteral("px"));
|
||||
[](LayerEffects &e, double value) { e.innerShadow->blur = value; }, 0, 100, QStringLiteral("px"), 500);
|
||||
break;
|
||||
}
|
||||
auto *cancel = new QPushButton(QStringLiteral("Cancel"), this);
|
||||
@@ -139,7 +139,7 @@ void EffectsSheet::header(const QString &title, bool position)
|
||||
// The effect's colour, opened in the app's own picker.
|
||||
void EffectsSheet::colour(bool labelled)
|
||||
{
|
||||
auto *swatch = new SwatchButton([this] { return m_session.editingEffects().color(m_kind).value_or(PaletteColor::black()); }, 3, 0, 0.5, this);
|
||||
auto *swatch = new SwatchButton([this] { return m_session.editingEffects().color(m_kind).value_or(PaletteColor::black()); }, 3, 1, 1, this);
|
||||
swatch->setObjectName(QStringLiteral("effectColor"));
|
||||
swatch->setFixedSize(36, 18);
|
||||
swatch->setToolTip(rawValue(m_kind) + QStringLiteral(" color"));
|
||||
@@ -162,7 +162,8 @@ void EffectsSheet::colour(bool labelled)
|
||||
}
|
||||
|
||||
void EffectsSheet::slider(const QString &title, std::function<std::optional<double>(const LayerEffects &)> value,
|
||||
std::function<void(LayerEffects &, double)> change, double low, double high, const QString &unit)
|
||||
std::function<void(LayerEffects &, double)> change, double low, double high, const QString &unit,
|
||||
std::optional<double> typedHigh)
|
||||
{
|
||||
const size_t index = m_sliders.size();
|
||||
const QString name = title.toLower();
|
||||
@@ -215,12 +216,12 @@ void EffectsSheet::slider(const QString &title, std::function<std::optional<doub
|
||||
layout->addLayout(suffixed);
|
||||
m_column->addWidget(row);
|
||||
m_controls.push_back(row);
|
||||
m_sliders.push_back(Slider{std::move(value), std::move(change), low, high, slider, field});
|
||||
m_sliders.push_back(Slider{std::move(value), std::move(change), low, high, typedHigh.value_or(high), slider, field});
|
||||
}
|
||||
|
||||
void EffectsSheet::apply(const Slider &control, double value)
|
||||
{
|
||||
const double clamped = std::clamp(value, control.low, control.high);
|
||||
const double clamped = std::clamp(value, control.low, control.typedHigh);
|
||||
m_session.changeEffects([&control, clamped](LayerEffects &effects) { control.change(effects, clamped); });
|
||||
}
|
||||
|
||||
@@ -244,7 +245,7 @@ void EffectsSheet::synchronize()
|
||||
if (!value)
|
||||
continue;
|
||||
{
|
||||
// The slider shows the session's number without writing back.
|
||||
// QSlider pins a larger typed value at its end.
|
||||
const QSignalBlocker quiet(control.slider);
|
||||
control.slider->setValue(int(std::lround((*value - control.low) / (control.high - control.low) * travel)));
|
||||
}
|
||||
|
||||
@@ -22,14 +22,17 @@ private:
|
||||
std::function<void(LayerEffects &, double)> change;
|
||||
double low;
|
||||
double high;
|
||||
// What a field or step may reach, past the slider.
|
||||
double typedHigh;
|
||||
QSlider *slider;
|
||||
PickerField *field;
|
||||
};
|
||||
void header(const QString &title, bool position);
|
||||
void colour(bool labelled);
|
||||
void slider(const QString &title, std::function<std::optional<double>(const LayerEffects &)> value,
|
||||
std::function<void(LayerEffects &, double)> change, double low, double high, const QString &unit);
|
||||
// A value clamped to the slider's range, as Swift's binding.
|
||||
std::function<void(LayerEffects &, double)> change, double low, double high, const QString &unit,
|
||||
std::optional<double> typedHigh = std::nullopt);
|
||||
// A value clamped to the typed range, as Swift's setAmount.
|
||||
void apply(const Slider &control, double value);
|
||||
void synchronize();
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ LayerAppearanceControls::LayerAppearanceControls(EditorSession &session, QWidget
|
||||
column->setContentsMargins(12, 12, 12, 12);
|
||||
column->setSpacing(8);
|
||||
auto *blend = new QHBoxLayout;
|
||||
blend->addWidget(caption(QStringLiteral("Blend"), this));
|
||||
m_blendCaption = caption(QStringLiteral("Blend"), this);
|
||||
blend->addWidget(m_blendCaption);
|
||||
blend->addWidget(m_picker, 1);
|
||||
column->addLayout(blend);
|
||||
auto *opacity = new QHBoxLayout;
|
||||
@@ -74,7 +75,9 @@ LayerAppearanceControls::~LayerAppearanceControls()
|
||||
|
||||
void LayerAppearanceControls::synchronize()
|
||||
{
|
||||
setEnabled(m_session.canEditAppearance());
|
||||
// A folder takes an opacity; blending rests with the picker.
|
||||
setEnabled(m_session.canEditOpacity());
|
||||
m_blendCaption->setEnabled(m_session.canEditAppearance());
|
||||
const std::optional<ImageLayer> active = m_session.activeLayer();
|
||||
const double opacity = active ? active->opacity : 1;
|
||||
m_syncing = true;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
class BlendModePicker;
|
||||
class QLabel;
|
||||
|
||||
// The active layer's blend mode and opacity.
|
||||
class LayerAppearanceControls : public QWidget {
|
||||
@@ -25,6 +26,7 @@ private:
|
||||
|
||||
EditorSession &m_session;
|
||||
BlendModePicker *const m_picker;
|
||||
QLabel *m_blendCaption = nullptr;
|
||||
QSlider *const m_slider;
|
||||
QLineEdit *const m_percentage;
|
||||
// The layer the controls were last made for.
|
||||
|
||||
@@ -157,7 +157,7 @@ void LayerCell::configure(const ImageLayer &layer, bool enabled, int depth, bool
|
||||
// A pending rename closes the edits: rows never move.
|
||||
if (!m_renaming)
|
||||
m_name->setText((layer.maskSourceID ? QStringLiteral("↳ ") : QString()) + layer.name);
|
||||
m_dimensions->setText(m_editableText ? QStringLiteral("Text · Double-click to edit")
|
||||
m_dimensions->setText(m_editableText ? QStringLiteral("Text")
|
||||
: m_isAdjustment ? QStringLiteral("Adjustment · Double-click to edit")
|
||||
: layer.isGroup ? QStringLiteral("Folder")
|
||||
: QStringLiteral("%1 × %2 px").arg(std::lround(layer.size().width())).arg(std::lround(layer.size().height())));
|
||||
|
||||
@@ -127,11 +127,6 @@ std::optional<LayerDropTarget> NativeLayerList::dropTarget(const QMimeData &data
|
||||
return std::nullopt;
|
||||
// Swift duplicates when the source offers a copy alone (Alt).
|
||||
const bool copying = offered == Qt::CopyAction;
|
||||
// Folders are not duplicated: a copy with one is refused.
|
||||
for (const QUuid &id : ids) {
|
||||
if (copying && m_rows[size_t(indexOf(m_rows, id))].isGroup)
|
||||
return std::nullopt;
|
||||
}
|
||||
const QPoint inColumn = m_column->mapFrom(viewport(), viewport()->mapFrom(this, listPoint));
|
||||
int row = int(m_rows.size());
|
||||
bool onRow = false;
|
||||
@@ -326,7 +321,7 @@ QCursor NativeLayerList::cursorFor(QPoint listPoint, Qt::KeyboardModifiers modif
|
||||
if (cell.maskThumbnail().isVisible() && cell.maskThumbnail().geometry().contains(inCell))
|
||||
return editable ? CanvasView::duplicateCursor(ratio) : QCursor(Qt::ArrowCursor);
|
||||
if (!isClippingZone(cell, inCell))
|
||||
return editable && !layer.isGroup ? CanvasView::duplicateCursor(ratio) : QCursor(Qt::ArrowCursor);
|
||||
return editable ? CanvasView::duplicateCursor(ratio) : QCursor(Qt::ArrowCursor);
|
||||
if (!m_session.canToggleClippingMask(layer.id))
|
||||
return QCursor(Qt::ArrowCursor);
|
||||
return clippingCursor(layer.maskSourceID.has_value(), ratio);
|
||||
|
||||
@@ -91,7 +91,8 @@ int NativeLayerList::rowAt(QPoint listPoint) const
|
||||
|
||||
bool NativeLayerList::isClippingZone(const LayerCell &cell, QPoint cellPoint)
|
||||
{
|
||||
return cellPoint.y() >= cell.height() - 10;
|
||||
// A folder clips nothing, so it has no strip.
|
||||
return !cell.isGroup() && cellPoint.y() >= cell.height() - 10;
|
||||
}
|
||||
|
||||
bool NativeLayerList::clickRow(LayerCell &cell, Qt::KeyboardModifiers modifiers, QPoint cellPoint)
|
||||
@@ -177,7 +178,7 @@ void NativeLayerList::keyPressEvent(QKeyEvent *event)
|
||||
{Qt::Key_Z, NavigationTool::zoom}, {Qt::Key_G, NavigationTool::gradient}, {Qt::Key_L, NavigationTool::lasso}, {Qt::Key_M, NavigationTool::marquee},
|
||||
{Qt::Key_W, NavigationTool::wand}, {Qt::Key_J, NavigationTool::spotHealing}, {Qt::Key_S, NavigationTool::cloneStamp},
|
||||
{Qt::Key_U, NavigationTool::shape}, {Qt::Key_R, NavigationTool::blur}, {Qt::Key_I, NavigationTool::eyedropper},
|
||||
{Qt::Key_C, NavigationTool::crop}};
|
||||
{Qt::Key_C, NavigationTool::crop}, {Qt::Key_T, NavigationTool::type}};
|
||||
if (plain && (key == Qt::Key_Backspace || key == Qt::Key_Delete)) {
|
||||
m_session.deleteKeyPressed();
|
||||
return;
|
||||
|
||||
@@ -124,6 +124,7 @@ public:
|
||||
void beginRenaming();
|
||||
bool isOnControl(QPoint point) const;
|
||||
bool isRenaming() const { return m_renaming; }
|
||||
bool isGroup() const { return m_isGroup; }
|
||||
QUuid layerID() const { return m_layerID; }
|
||||
LayerThumbnailButton &thumbnail() { return *m_thumbnail; }
|
||||
LayerThumbnailButton &maskThumbnail() { return *m_maskThumbnail; }
|
||||
|
||||
Reference in new issue
Block a user