// TRACES: FR-DEV-4 // The crop rectangle drawn on the photograph. // // Four dimmed, desaturated panels around the crop, then the rect itself with // handles. Panels rather than one shape with a hole: Slint has no cut-out, and // four rectangles are exact. // // Sized by the develop view to the fitted image rect, so the fractions below // are fractions of the picture and not of the canvas box it is letterboxed // inside. The rect itself is mirrored from Rust rather than owned here, for // the reason `AppWindow` gives: Rust clamps and squares it, so the overlay // must draw what was actually applied and not what the drag asked for. import { Theme } from "theme.slint"; export component CropOverlay inherits Rectangle { /// The crop rect, in fractions of the frame. in property crop-x: 0.0; in property crop-y: 0.0; in property crop-w: 1.0; in property crop-h: 1.0; /// A dragged crop rect, in the same fractions. callback crop-changed(float, float, float, float); property rx: root.crop-x * self.width; property ry: root.crop-y * self.height; property rw: root.crop-w * self.width; property rh: root.crop-h * self.height; // The surround: dimmed and drained of colour, so the crop // reads as the photograph and everything else as context // being discarded. Slint cannot desaturate a live image, // so this is a heavy neutral wash over it — the dimming // carries the separation and the neutrality kills the // colour cue. property veil: #20242aE0; Rectangle { x: 0; y: 0; width: parent.width; height: root.ry; background: root.veil; } Rectangle { x: 0; y: root.ry + root.rh; width: parent.width; height: parent.height - root.ry - root.rh; background: root.veil; } Rectangle { x: 0; y: root.ry; width: root.rx; height: root.rh; background: root.veil; } Rectangle { x: root.rx + root.rw; y: root.ry; width: parent.width - root.rx - root.rw; height: root.rh; background: root.veil; } // The rect, its thirds, and the handles. Rectangle { x: parent.rx; y: parent.ry; width: parent.rw; height: parent.rh; border-width: 1px; border-color: #ffffffCC; // Rule of thirds, the reason a crop overlay is worth // drawing at all rather than typing numbers. Rectangle { x: parent.width / 3; y: 0; width: 1px; height: parent.height; background: #ffffff44; } Rectangle { x: parent.width * 2 / 3; y: 0; width: 1px; height: parent.height; background: #ffffff44; } Rectangle { x: 0; y: parent.height / 3; width: parent.width; height: 1px; background: #ffffff44; } Rectangle { x: 0; y: parent.height * 2 / 3; width: parent.width; height: 1px; background: #ffffff44; } // Drag the whole rect. move-area := TouchArea { width: 100%; height: 100%; mouse-cursor: MouseCursor.move; property start-x; property start-y; pointer-event(ev) => { if (ev.kind == PointerEventKind.down) { self.start-x = root.crop-x; self.start-y = root.crop-y; } } moved => { if (self.pressed) { root.crop-changed( self.start-x + (self.mouse-x - self.pressed-x) / max(root.width, 1px), self.start-y + (self.mouse-y - self.pressed-y) / max(root.height, 1px), root.crop-w, root.crop-h, ); } } } } // Corner handles. Each drags one corner while the opposite // stays put, which is the only behaviour that lets a crop // be shaped rather than merely moved. for corner in [ { hx: 0.0, hy: 0.0 }, { hx: 1.0, hy: 0.0 }, { hx: 0.0, hy: 1.0 }, { hx: 1.0, hy: 1.0 }, ]: Rectangle { property size: 18px; x: parent.rx + corner.hx * parent.rw - self.size / 2; y: parent.ry + corner.hy * parent.rh - self.size / 2; width: self.size; height: self.size; Rectangle { width: 12px; height: 12px; background: #ffffff; border-radius: 2px; } TouchArea { width: 100%; height: 100%; mouse-cursor: (corner.hx == corner.hy) ? MouseCursor.nwse-resize : MouseCursor.nesw-resize; property ox; property oy; property ow; property oh; pointer-event(ev) => { if (ev.kind == PointerEventKind.down) { self.ox = root.crop-x; self.oy = root.crop-y; self.ow = root.crop-w; self.oh = root.crop-h; } } // Movement as a fraction of the frame, live while // the handle is held. property dx: (self.mouse-x - self.pressed-x) / max(root.width, 1px); property dy: (self.mouse-y - self.pressed-y) / max(root.height, 1px); moved => { if (!self.pressed) { return; } // Dragging a left/top handle moves the origin // and shrinks the extent by the same amount; // a right/bottom handle moves only the extent. // Rust clamps the result, so an over-drag // slides rather than inverting. root.crop-changed( corner.hx == 0 ? self.ox + self.dx : self.ox, corner.hy == 0 ? self.oy + self.dy : self.oy, corner.hx == 0 ? self.ow - self.dx : self.ow + self.dx, corner.hy == 0 ? self.oh - self.dy : self.oh + self.dy, ); } } } }