Skip to content

feat(editor): Snap on and surroundings fill by default - #113

Open
lukejmorrison wants to merge 8 commits into
omacom:mainfrom
lukejmorrison:feat/clip-snap-defaults
Open

feat(editor): Snap on and surroundings fill by default#113
lukejmorrison wants to merge 8 commits into
omacom:mainfrom
lukejmorrison:feat/clip-snap-defaults

Conversation

@lukejmorrison

@lukejmorrison lukejmorrison commented Aug 29, 2026

Copy link
Copy Markdown

Depends on #111. Unique commit vs clip-out: lukejmorrison/omasnap@feat/select-clip-out...feat/clip-snap-defaults

Until #111 merges, GitHub will show the full clip-out stack in the Files tab. The change this PR adds is the last commit only. Independent of Ctrl+Cut insert-band (#114).

What this changes

On top of clip-out from Select:

  • Snap starts on. Select is ready to grab the object inside the drag box. Toggle the Snap chip off for a free rectangle / ellipse / lasso mask.
  • Hole fill starts as match-surroundings when there is an outside ring to sample (page colour around the mask), not transparent.
  • T while a mask is locked cycles surroundings → Sample from image → transparent. Idle T is still Text.

No new config key. Defaults are the product (Omarchy).

Why it is a separate PR

Clip-out in #111 keeps Snap off and transparent holes, matching the first version Copilot reviewed. This is the opinionated default we actually use day to day. Easy to take clip-out without taking the defaults.

Tests

runClipMappingSmoke covers Snap-on and surroundings fill as the starting state, plus the T cycle. make check is green on the stacked branch.

lukejmorrison and others added 8 commits August 28, 2026 07:05
Select already grows the canvas when you drag a layer off the screenshot;
this lets the screenshot itself be the source of that layer. An empty
marquee locks a pixel clip; drag it to lift those pixels, punch a hole
(backdrop shows through), and commit one Clip operation. Repeat to pull
another region. Ctrl+Z undoes the hole and the layer together.

Covered by runClipSmoke (copy/punch/mapping) and runClipMappingSmoke
(empty marquee, lift, hole, undo, repeat). The suite fails without the
change. make check is green.
After an empty Select marquee locks a region, a swatch row appears under
the rect: transparent (checkerboard, default), the palette colours, and
custom. 1–8 and the eyedropper set the infill while the clip is armed.
The fill is stored on Operation::Clip so undo/replay keep the hole.
Ctrl+Z during a lift cancels only the in-progress drag — QTest (and
real keyboards) send Control before Z, so a modifier-only press must
not drop the lift and then let Z undo the previous op. Empty marquees
need a 12px floor, crop chrome stays until lift, and the hole-fill
fly-out docks to the side. Replay keeps a present clip tile, PNG loads
only on Clip, and live lift paints a downsampled pixmap with snap
hysteresis.

Co-authored-by: Luke Morrison <lukejmorrison@users.noreply.github.com>
Rectangle, ellipse, and lasso masks with click-to-snap, keyboard hole
fill, and drag-to-lift. Spec for the follow-on to rectangular clip-out.
Select cycles clip shape with V and a chip strip. Empty-canvas drags
that miss layers still lock a mask; R/E/F keep arming drawing tools.
Snap ray-casts to an axis-aligned ellipse. Lifted tiles carry alpha
outside the path. Fable and Sol P0s applied to the key layer.
Cycling clip shape now updates the path already on screen (rect to
ellipse, and Snap ray-casts from the mask centre). Installed to
~/.local/bin so Print no longer launches last morning's build.
…e hole

Snap treats the drag as a search window: 8-connected flood with a 1px
close, an enclosing circle for Ellipse (pins stay in), and a rounded
rect for Rect when two corners agree on a radius. Crop chrome hides
while a mask is locked. The hole-fill fly-out adds match-surroundings
and the same Sample from image eyedropper as the toolbar.
Select starts with Snap armed. A locked clip fills with the page colour
around the mask. T cycles surroundings → Sample from image → transparent
while the mask is locked; idle T is still Text.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A confirmed logic bug in the click-snap path clears snapped lasso points, causing lasso click-snap to fall back to a rectangle mask.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR adjusts the new Select-based clip-out workflow defaults so it’s immediately optimized for “grab the object” usage: Snap starts enabled, hole fill defaults to match-surroundings when possible, and T cycles fill modes while a mask is locked (without changing global configurability).

Changes:

  • Adds a clip-out engine (ClipOp, masked tile copy, hole fill/punch, snap helpers) and persists clip ops + clip tiles through the operation log/JSON.
  • Integrates clip-out into CaptureEditor (shape chips, Snap toggle, mask lock/resize/lift, fill fly-out + T cycling, snap-trace animation).
  • Extends smoke coverage and docs to reflect the new default behaviors and keybindings.
File summaries
File Description
tests/editor-smoke.cpp Wires new clip smoke suites into the main smoke runner.
tests/clip-smoke.hpp Declares clip-engine smoke entrypoint.
tests/clip-smoke.cpp Adds pixel-level engine tests (copy/punch/fill/mapping/snap/PNG gate).
tests/clip-mapping-smoke.hpp Declares editor clip mapping smoke entrypoint.
tests/clip-mapping-smoke.cpp Adds editor-level interaction tests for locking/lifting/fill cycling/snap defaults.
src/editor.hpp Introduces clip-related editor state, helpers, and test hooks.
src/editor.cpp Implements clip UI/interaction flow, operation replay integration, and snap-trace visuals.
src/clip.hpp Defines clip model + APIs (shapes, path, masked copy, fill, snap helpers).
src/clip.cpp Implements clip-out engine and snap detection algorithms.
src/capture.hpp Adds Annotation::Kind::Clip + Operation::Type::Clip data plumbing.
src/capture.cpp Renders clip layers and serializes/deserializes clip tiles + clip ops in JSON.
README.md Documents clip-out behavior, defaults, and updated keybinding semantics.
docs/superpowers/specs/2026-08-28-clip-shape-masks-design.md Adds design spec describing intended UX/model/serialization.
docs/editing-model.md Updates operation-log model documentation to include Clip ops.
CMakeLists.txt Adds new clip sources and tests to the build.
AGENTS.md Updates repository layout to include the new clip engine files.
.gitignore Ignores .superpowers/ directory.
Review details
  • Files reviewed: 16/18 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/editor.cpp
Comment on lines +5960 to +5967
if (matches.isEmpty() && !pixelClipLargeEnough(area) &&
pixelClipSnapEnabled_) {
trySnapAt(dragStart_);
pixelClipPoints_.clear();
updatePointerCursor();
update();
return;
}
Comment thread src/editor.cpp
Comment on lines +278 to +293
void paintCheckerboard(QPainter &painter, const QRectF &bounds, qreal cell) {
painter.fillRect(bounds, QColor(245, 245, 247));
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(170, 172, 178));
const int columns = std::max(1, static_cast<int>(std::ceil(bounds.width() / cell)));
const int rows = std::max(1, static_cast<int>(std::ceil(bounds.height() / cell)));
for (int row = 0; row < rows; ++row) {
for (int column = 0; column < columns; ++column) {
if (((row + column) & 1) == 0)
continue;
painter.fillRect(QRectF(bounds.left() + column * cell,
bounds.top() + row * cell, cell, cell),
QColor(170, 172, 178));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants