feat(regionSelector): add Crop Mode with adjustable-handle editor

CaptureMode { Instant, Crop } toggled by C or Instant/Crop toolbar tabs.
In Crop, release seeds an 8-handle + interior-move editor over the
retained frame (min-size and screen clamps); Enter or the confirm FAB
dispatches through the existing snip(), Esc cancels back to draw.
Action-agnostic: screenshots and recording both confirm through it.
New config: regionSelector.crop.minSize / handleSize.
This commit is contained in:
DJ committed 2026-07-04 22:47:52 -04:00
1 parent fc69fe6642
commit 285a7b4e5f
11 files changed
+418 -4

No files matched your search

+1
View File
@@ -9,6 +9,7 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
### Added
- **Crop Mode** - Press `C` (or the new Instant/Crop toolbar tabs) to switch the region selector into Crop mode: releasing a selection opens an adjustable editor (8 resize handles + interior move, min-size and screen clamping, live size label) instead of capturing immediately; **Enter** or the confirm button captures, **Escape** returns to drawing. Works for screenshots and recording alike; configurable via `regionSelector.crop.minSize`/`handleSize`
- **`regionSelector.dragThreshold` Config Key** - Pixel dead-zone (default `6`) for the region selector's click/drag discriminator; `0` disables it, restoring the previous per-pixel sensitivity (circle mode always uses `0` — every circle gesture is the drawn shape)
### Changed
+15
View File
@@ -44,6 +44,17 @@ Toggle between modes in config:
}
```
### Crop Mode
By default captures happen instantly when you release the mouse. Press `C` (or use the Instant/Crop toolbar tabs) to switch the current monitor's overlay into Crop mode:
- Releasing a selection (or clicking a detected window / monitor button) opens an adjustable editor instead of capturing
- Drag the 8 handles to resize, or drag inside the rect to move it
- `Enter` or the confirm button captures the adjusted region; `Escape` goes back to drawing
- Works for all actions, including recording; right-click selections still open the editor and route to Edit on confirm
Crop mode is per monitor, always starts off (the overlay opens in Instant mode), and temporarily forces rectangle selection — your previous selection mode is restored when you leave it. Minimum size and handle size are configurable via `regionSelector.crop` in the config.
## Screenshot Actions
### Copy (Super+Shift+S)
@@ -224,6 +235,10 @@ Full config schema (`~/.config/hypr-lens/config.json`):
"circle": {
"strokeWidth": 6, // Circle selection line thickness
"padding": 10 // Extra padding around circle bounds
},
"crop": {
"minSize": 20, // Minimum crop width/height in px
"handleSize": 12 // Visual size of resize handles in px
}
}
}
+11
View File
@@ -162,6 +162,17 @@ Annotation tool used by the **Edit** action.
With satty, **Enter** (or the copy/save toolbar buttons) keeps the edit — copying to the clipboard and, when `screenSnip.copyAlsoSaves` is `true`, saving into the hypr-lens save dir — while **Escape** discards it. satty is configured via the bundled `~/.config/hypr-lens/satty.toml` (edit it to taste; it never touches your own `~/.config/satty`). The swappy fallback is a plain hand-off — swappy uses its own Save/Copy buttons and its own save directory.
### regionSelector.crop
Crop mode: an adjustable crop editor as an alternative to the default instant capture. Toggle it with the **C** key (hardcoded, per monitor) or the Instant/Crop toolbar tabs. In Crop mode, releasing a selection opens an editor with 8 resize handles and interior move-drag instead of capturing immediately; **Enter** (or the confirm button) captures the adjusted region, **Escape** goes back to drawing. The overlay always opens in Instant mode.
| Setting | Default | Description |
|---------|---------|-------------|
| `minSize` | `20` | Minimum crop width/height in pixels, enforced per axis while resizing |
| `handleSize` | `12` | Visual size of the resize handles in pixels |
Values are sanitized on use: `minSize` is clamped to at least `1` and `handleSize` to at least `4`, so zero/negative values can't collapse the crop rect or make the handles ungrabbable.
---
## Tips
+4
View File
@@ -45,6 +45,10 @@
},
"annotation": {
"useSatty": true
},
"crop": {
"minSize": 20,
"handleSize": 12
}
}
}
+5
View File
@@ -108,6 +108,11 @@ Singleton {
property JsonObject annotation: JsonObject {
property bool useSatty: true // true = satty (preferred), false = swappy. Falls back to swappy if satty isn't installed.
}
// Crop mode adjustable editor (toggled with the C key)
property JsonObject crop: JsonObject {
property int minSize: 20 // Minimum crop width/height in px
property int handleSize: 12 // Visual size of resize handles in px
}
}
}
}
@@ -0,0 +1,151 @@
pragma ComponentBehavior: Bound
import "../common"
import "RegionUtils.js" as RegionUtils
import QtQuick
// Crop editor interaction layer: 8 resize handles (4 corners + 4 edges) plus an
// interior move-grab over the current selection region.
//
// Load-bearing invariant: dragState.region* are bindings over the drag endpoints
// (dragStart*/dragging*). This component NEVER assigns region* directly — every
// mutation goes through dragState.seedRect (endpoint writes), which also keeps the
// rect normalized so "drag left edge past right" cannot flip min/abs on us.
// startDrag is never called either, so dragState.mouseButton (the Copy/Edit
// discriminator from the seeding gesture) survives the whole edit session.
Item {
id: root
required property var dragState
required property real screenWidth
required property real screenHeight
required property color handleColor
required property color handleBorderColor
// Config values clamped at the consumption site (same idea as the
// Math.max(0, ...dragThreshold) guard in RegionSelection): minSize <= 0 would
// let the rect collapse to nothing, handleSize <= 0 would be ungrabbable
readonly property int minSize: Math.max(1, Config.options.regionSelector.crop.minSize)
readonly property int handleSize: Math.max(4, Config.options.regionSelector.crop.handleSize)
// Hit target is larger than the visual so handles stay grabbable when small,
// but shrinks with the rect (floored at the visual size) so the 8 hit areas
// can't fully overlap — and shadow each other — at min rect size
readonly property real hitSize: Math.max(handleSize,
Math.min(handleSize * 2, Math.min(dragState.regionWidth, dragState.regionHeight) / 2))
// Rect + pointer position captured at press; deltas are applied against these
// so per-event rounding/clamping never accumulates
property real pressRectX: 0
property real pressRectY: 0
property real pressRectW: 0
property real pressRectH: 0
property real pressMouseX: 0
property real pressMouseY: 0
function beginGrab(item, mouse) {
pressRectX = root.dragState.regionX;
pressRectY = root.dragState.regionY;
pressRectW = root.dragState.regionWidth;
pressRectH = root.dragState.regionHeight;
const p = item.mapToItem(root, mouse.x, mouse.y);
pressMouseX = p.x;
pressMouseY = p.y;
}
// Resize: move only the grabbed edges, clamping each axis to >= minSize and
// to the screen bounds. Edges that aren't grabbed stay anchored.
function applyResize(item, mouse, grabLeft, grabTop, grabRight, grabBottom) {
const p = item.mapToItem(root, mouse.x, mouse.y);
const dx = p.x - pressMouseX;
const dy = p.y - pressMouseY;
let left = pressRectX;
let top = pressRectY;
let right = pressRectX + pressRectW;
let bottom = pressRectY + pressRectH;
if (grabLeft) left = Math.min(Math.max(0, pressRectX + dx), right - root.minSize);
if (grabTop) top = Math.min(Math.max(0, pressRectY + dy), bottom - root.minSize);
if (grabRight) right = Math.max(Math.min(root.screenWidth, right + dx), left + root.minSize);
if (grabBottom) bottom = Math.max(Math.min(root.screenHeight, bottom + dy), top + root.minSize);
seedClamped(left, top, right - left, bottom - top);
}
// Move: translate the whole rect, keeping its size
function applyMove(item, mouse) {
const p = item.mapToItem(root, mouse.x, mouse.y);
const x = Math.max(0, Math.min(pressRectX + (p.x - pressMouseX), root.screenWidth - pressRectW));
const y = Math.max(0, Math.min(pressRectY + (p.y - pressMouseY), root.screenHeight - pressRectH));
seedClamped(x, y, pressRectW, pressRectH);
}
// Final guard on every mutation: clamp to screen, then write endpoints only
function seedClamped(x, y, w, h) {
const clamped = RegionUtils.clampRegionToScreen(
{ x: x, y: y, width: w, height: h },
root.screenWidth, root.screenHeight
);
root.dragState.seedRect(clamped.x, clamped.y, clamped.width, clamped.height);
}
// Interior move-grab (below the handles so they win on overlap)
MouseArea {
id: moveArea
z: 1
x: root.dragState.regionX
y: root.dragState.regionY
width: root.dragState.regionWidth
height: root.dragState.regionHeight
cursorShape: Qt.SizeAllCursor
acceptedButtons: Qt.LeftButton
preventStealing: true
onPressed: (mouse) => root.beginGrab(moveArea, mouse)
onPositionChanged: (mouse) => {
if (moveArea.pressed) root.applyMove(moveArea, mouse);
}
}
// 8 handles: fx/fy are the handle's fractional position on the rect
// (0 = left/top edge, 0.5 = edge midpoint, 1 = right/bottom edge)
Repeater {
model: [
{ fx: 0.0, fy: 0.0, cursor: Qt.SizeFDiagCursor }, // top-left
{ fx: 0.5, fy: 0.0, cursor: Qt.SizeVerCursor }, // top
{ fx: 1.0, fy: 0.0, cursor: Qt.SizeBDiagCursor }, // top-right
{ fx: 0.0, fy: 0.5, cursor: Qt.SizeHorCursor }, // left
{ fx: 1.0, fy: 0.5, cursor: Qt.SizeHorCursor }, // right
{ fx: 0.0, fy: 1.0, cursor: Qt.SizeBDiagCursor }, // bottom-left
{ fx: 0.5, fy: 1.0, cursor: Qt.SizeVerCursor }, // bottom
{ fx: 1.0, fy: 1.0, cursor: Qt.SizeFDiagCursor } // bottom-right
]
delegate: MouseArea {
id: handleArea
required property var modelData
// Corners above edge midpoints: on small rects their hit areas still
// brush against each other, and the corner must win the overlap
z: (handleArea.modelData.fx === 0.5 || handleArea.modelData.fy === 0.5) ? 2 : 3
x: root.dragState.regionX + handleArea.modelData.fx * root.dragState.regionWidth - root.hitSize / 2
y: root.dragState.regionY + handleArea.modelData.fy * root.dragState.regionHeight - root.hitSize / 2
width: root.hitSize
height: root.hitSize
cursorShape: handleArea.modelData.cursor
acceptedButtons: Qt.LeftButton
preventStealing: true
onPressed: (mouse) => root.beginGrab(handleArea, mouse)
onPositionChanged: (mouse) => {
if (handleArea.pressed) {
root.applyResize(handleArea, mouse,
handleArea.modelData.fx === 0.0, handleArea.modelData.fy === 0.0,
handleArea.modelData.fx === 1.0, handleArea.modelData.fy === 1.0);
}
}
// Handle visual: filled dot matching the selection border color
Rectangle {
anchors.centerIn: parent
width: root.handleSize
height: root.handleSize
radius: root.handleSize / 2
color: root.handleColor
border.color: root.handleBorderColor
border.width: 1
}
}
}
}
@@ -21,6 +21,9 @@ Toolbar {
// Use a synchronizer on these
property var action
property var selectionMode
property var captureMode
// True while the adjustable crop editor is open (plain binding from RegionSelection)
property bool cropEditing: false
// Monitor list for full-screen capture buttons
property var monitors: []
// Signals
@@ -31,6 +34,18 @@ Toolbar {
// Use ActionConfig singleton for action metadata
readonly property var actionConfig: ActionConfig.getConfig(root.action)
readonly property bool showMonitorButtons: actionConfig.allowsMonitorButtons
readonly property bool cropActive: root.captureMode === RegionSelection.CaptureMode.Crop
// Keep tab indices in sync with externally driven state (C-key toggle swaps
// captureMode; entering Crop forces selectionMode back to RectCorners)
onCaptureModeChanged: {
const idx = root.cropActive ? 1 : 0;
if (captureModeTabBar.currentIndex !== idx) captureModeTabBar.setCurrentIndex(idx);
}
onSelectionModeChanged: {
const idx = root.selectionMode === RegionSelection.SelectionMode.RectCorners ? 0 : 1;
if (tabBar.currentIndex !== idx) tabBar.setCurrentIndex(idx);
}
// Action indicator shape
MaterialShape {
@@ -50,9 +65,11 @@ Toolbar {
}
}
// Selection mode tabs (Rect/Circle)
// Selection mode tabs (Rect/Circle) - inert while Crop is active (Crop implies rectangles)
ToolbarTabBar {
id: tabBar
enabled: !root.cropActive
opacity: enabled ? 1 : 0.4
tabButtonList: [
{"icon": "activity_zone", "name": Translation.tr("Rect")},
{"icon": "gesture", "name": Translation.tr("Circle")}
@@ -65,6 +82,25 @@ Toolbar {
}
}
// Capture mode tabs (Instant/Crop) - inert while the crop editor is open so a
// stray click/wheel can't flip to Instant and destroy the adjusted crop
// (Esc, C and the FABs are the editor exits)
ToolbarTabBar {
id: captureModeTabBar
enabled: !root.cropEditing
opacity: enabled ? 1 : 0.4
tabButtonList: [
{"icon": "bolt", "name": Translation.tr("Instant")},
{"icon": "crop", "name": Translation.tr("Crop")}
]
Component.onCompleted: {
currentIndex = root.cropActive ? 1 : 0
}
onCurrentIndexChanged: {
root.captureMode = currentIndex === 0 ? RegionSelection.CaptureMode.Instant : RegionSelection.CaptureMode.Crop;
}
}
// Monitor capture buttons - one-click full-screen capture for each monitor
// Left-click: capture and copy to clipboard
// Right-click: capture and edit with swappy
@@ -61,6 +61,17 @@ QtObject {
regionHeight = targetedRegionHeight + padding * 2;
}
// Seed the selection region by writing drag endpoints (Crop mode).
// The only sanctioned way Crop paths set geometry: region* are bindings
// over dragStart*/dragging*, and assigning them directly would break the
// bindings for the rest of the overlay session (Esc-back-to-draw).
function seedRect(x: real, y: real, w: real, h: real) {
dragStartX = x;
dragStartY = y;
draggingX = x + w;
draggingY = y + h;
}
function startDrag(x: real, y: real, button) {
dragStartX = x;
dragStartY = y;
@@ -29,10 +29,42 @@ PanelWindow {
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
enum SelectionMode { RectCorners, Circle }
enum CaptureMode { Instant, Crop }
property var action: RegionSelection.SnipAction.Copy
property var selectionMode: RegionSelection.SelectionMode.RectCorners
property var captureMode: RegionSelection.CaptureMode.Instant
// Crop mode: true while the adjustable crop editor is open (post-release, pre-confirm)
property bool cropEditing: false
// Selection mode in effect before Crop forced RectCorners; restored on exit
property var preCropSelectionMode: RegionSelection.SelectionMode.RectCorners
signal dismiss()
property bool isCropMode: (root.captureMode === RegionSelection.CaptureMode.Crop)
// Swapping capture modes resets the editor and any in-progress drag — never the frame.
// Crop implies rectangles: force RectCorners while active (toolbar tab bar is disabled
// too), remembering the previous mode so leaving Crop restores it (e.g. OCR's circle
// config). Lives here so both the C key and the toolbar tabs get the same behavior.
onCaptureModeChanged: {
root.cropEditing = false;
dragState.reset();
if (root.isCropMode) {
root.preCropSelectionMode = root.selectionMode;
root.selectionMode = RegionSelection.SelectionMode.RectCorners;
} else {
root.selectionMode = root.preCropSelectionMode;
}
}
// Restore session defaults when a capture shortcut re-triggers while the overlay is
// already open. Local writes (C key, toolbar Synchronizer) break the captureMode
// binding from RegionSelector, so the reset must be pushed as a call, not a binding.
function sessionReset() {
root.cropEditing = false;
dragState.reset();
root.captureMode = RegionSelection.CaptureMode.Instant;
}
// Monitor capture support
property var allMonitors: []
property string targetMonitorCapture: ""
@@ -44,8 +76,42 @@ PanelWindow {
captureFullMonitorLocal();
}
// Open the crop editor seeded with the given rect, normalized for editability:
// clamped to screen bounds, then grown per axis to >= minSize while staying on
// screen. Every editor entry path (drag release, targeted click, full monitor)
// goes through here so sub-minSize or offscreen seeds can't produce an editor
// with unreachable handles or jumping clamps.
// Seeds via dragState.seedRect (endpoint writes) — region* bindings stay intact.
function openCropEditor(x, y, w, h) {
const minSize = Math.max(1, Config.options.regionSelector.crop.minSize);
const clamped = RegionUtils.clampRegionToScreen(
{ x: x, y: y, width: w, height: h },
root.screen.width, root.screen.height
);
const rw = Math.min(root.screen.width, Math.max(clamped.width, minSize));
const rh = Math.min(root.screen.height, Math.max(clamped.height, minSize));
const rx = Math.min(clamped.x, root.screen.width - rw);
const ry = Math.min(clamped.y, root.screen.height - rh);
dragState.seedRect(rx, ry, rw, rh);
dragState.endDrag();
root.cropEditing = true;
}
// Confirm the crop editor: latch it closed before dispatching so a held Enter
// key or a queued FAB click can't fire snip() a second time before teardown
function confirmCrop() {
if (!root.cropEditing) return;
root.cropEditing = false;
root.snip();
}
// Capture the full region of this monitor
function captureFullMonitorLocal() {
if (root.isCropMode) {
// Crop: seed the editor with the full-monitor rect instead of snipping
root.openCropEditor(0, 0, root.screen.width, root.screen.height);
return;
}
dragState.regionX = 0;
dragState.regionY = 0;
dragState.regionWidth = root.screen.width;
@@ -261,14 +327,36 @@ PanelWindow {
}
ScreencopyView {
id: screencopyView
anchors.fill: parent
live: false
captureSource: root.screen
focus: root.visible
Keys.onPressed: (event) => { // Esc to close
// QQC2 buttons (toolbar tabs, FABs, monitor buttons) grab focus on click and
// would silently kill Esc/C/Enter. Nothing else in the overlay takes keyboard
// input, so always reclaim it while visible.
onActiveFocusChanged: {
if (root.visible && !screencopyView.activeFocus) screencopyView.forceActiveFocus();
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
root.dismiss();
if (root.cropEditing) {
// First Esc: cancel the editor, back to draw (frame retained)
root.cropEditing = false;
dragState.reset();
} else {
root.dismiss();
}
} else if (event.key === Qt.Key_C && event.modifiers === Qt.NoModifier && !dragState.dragging) {
// Hardcoded toggle, per-screen scope (same as the Rect/Circle toolbar
// toggle). Ignored mid-drag: the mode swap resets dragState, and the
// orphaned release would then dismiss the overlay as an empty click.
root.captureMode = root.isCropMode
? RegionSelection.CaptureMode.Instant
: RegionSelection.CaptureMode.Crop;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && root.cropEditing && !event.isAutoRepeat) {
root.confirmCrop();
}
}
@@ -278,6 +366,9 @@ PanelWindow {
cursorShape: Qt.CrossCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
// While the crop editor is open, CropHandles owns the mouse
// (MouseArea.enabled only mutes this area, not its children)
enabled: !root.cropEditing
// Controls
onPressed: (mouse) => {
@@ -289,7 +380,19 @@ PanelWindow {
if (!dragState.draggedAway) {
if (dragState.targetedRegionValid()) {
const padding = Config.options.regionSelector.targetRegions.selectionPadding;
dragState.setRegionToTargeted(padding);
if (root.isCropMode) {
// Crop: open the editor seeded with the padded target
// (openCropEditor normalizes edge-of-screen overhang)
root.openCropEditor(
dragState.targetedRegionX - padding,
dragState.targetedRegionY - padding,
dragState.targetedRegionWidth + padding * 2,
dragState.targetedRegionHeight + padding * 2
);
return;
} else {
dragState.setRegionToTargeted(padding);
}
} else {
dragState.endDrag();
root.dismiss();
@@ -302,6 +405,13 @@ PanelWindow {
dragState.setRegionFromCirclePoints(padding, mouseArea.mouseX, mouseArea.mouseY);
}
dragState.endDrag();
if (root.isCropMode) {
// Crop: open the adjustable editor instead of dispatching
// (openCropEditor normalizes sub-minSize/one-axis drags)
root.openCropEditor(dragState.regionX, dragState.regionY,
dragState.regionWidth, dragState.regionHeight);
return;
}
root.snip();
}
onPositionChanged: (mouse) => {
@@ -322,6 +432,23 @@ PanelWindow {
mouseY: mouseArea.mouseY
color: root.selectionBorderColor
overlayColor: root.overlayColor
// mouseArea is muted while crop-editing, so hide the (frozen) crosshair
showAimLines: Config.options.regionSelector.rect.showAimLines && !root.cropEditing
}
}
// Crop editor: resize handles + interior move-grab, rendered above the
// selection details (border/label stay live via the region* bindings)
Loader {
z: 5
anchors.fill: parent
active: root.cropEditing
sourceComponent: CropHandles {
dragState: dragState
screenWidth: root.screen.width
screenHeight: root.screen.height
handleColor: root.selectionBorderColor
handleBorderColor: root.onBorderColor
}
}
@@ -416,16 +543,41 @@ PanelWindow {
OptionsToolbar {
monitors: root.allMonitors
cropEditing: root.cropEditing
Synchronizer on action {
property alias source: root.action
}
Synchronizer on selectionMode {
property alias source: root.selectionMode
}
Synchronizer on captureMode {
property alias source: root.captureMode
}
onDismiss: root.dismiss();
onCaptureFullMonitor: (monitorName) => root.captureFullMonitor(monitorName)
onEditFullMonitor: (monitorName) => root.editFullMonitor(monitorName)
}
Item {
visible: root.cropEditing
anchors {
verticalCenter: parent.verticalCenter
}
implicitWidth: confirmFab.implicitWidth
implicitHeight: confirmFab.implicitHeight
StyledRectangularShadow {
target: confirmFab
radius: confirmFab.buttonRadius
}
FloatingActionButton {
id: confirmFab
baseSize: 48
iconText: "check"
onClicked: root.confirmCrop();
StyledToolTip {
text: Translation.tr("Confirm")
}
}
}
Item {
anchors {
verticalCenter: parent.verticalCenter
@@ -27,6 +27,11 @@ Scope {
property var action: RegionSelection.SnipAction.Copy
property var selectionMode: RegionSelection.SelectionMode.RectCorners
property var captureMode: RegionSelection.CaptureMode.Instant
// Emitted by openWithAction so live RegionSelection instances (whose property
// bindings may have been broken by local writes) reset to session defaults
signal sessionReset()
// ─── Monitor List Management ─────────────────────────────────────────────────
property var monitorList: []
@@ -63,6 +68,11 @@ Scope {
function captureMonitor(monitorName: string) {
root.targetMonitorCapture = monitorName;
// Clear immediately (the change signal has already fired synchronously on the
// target instance): in Crop mode the capture seeds the editor without
// dismissing, and a stale value would make re-clicking the same monitor
// button a silent no-op
root.targetMonitorCapture = "";
}
// ─── Region Selection Windows ────────────────────────────────────────────────
@@ -74,6 +84,7 @@ Scope {
active: GlobalStates.regionSelectorOpen
sourceComponent: RegionSelection {
id: selectionWindow
screen: regionSelectorLoader.modelData
allMonitors: root.monitorList
targetMonitorCapture: root.targetMonitorCapture
@@ -81,6 +92,18 @@ Scope {
onCaptureMonitorRequested: (monitorName) => root.captureMonitor(monitorName)
action: root.action
selectionMode: root.selectionMode
captureMode: root.captureMode
// Re-triggering a capture shortcut while the overlay is open must
// restore Instant/draw defaults even on instances whose captureMode/
// selectionMode bindings were broken by local writes (C key, toolbar)
Connections {
target: root
function onSessionReset() {
selectionWindow.sessionReset();
selectionWindow.selectionMode = root.selectionMode;
}
}
}
}
}
@@ -91,6 +114,10 @@ Scope {
function openWithAction(action) {
root.action = action;
root.selectionMode = getSelectionModeForAction(action);
root.captureMode = RegionSelection.CaptureMode.Instant; // Never persists across opens
// Instances alive from a still-open overlay may hold locally-written state
// (broken bindings); push the Instant/draw defaults to them explicitly
root.sessionReset();
GlobalStates.regionSelectorOpen = true;
}
+1
View File
@@ -9,4 +9,5 @@ MonitorButton 1.0 MonitorButton.qml
OptionsToolbar 1.0 OptionsToolbar.qml
TargetRegion 1.0 TargetRegion.qml
CircleSelectionDetails 1.0 CircleSelectionDetails.qml
CropHandles 1.0 CropHandles.qml
RectCornersSelectionDetails 1.0 RectCornersSelectionDetails.qml