// TRACES: FR-CULL-3 | NFR-A11Y-3 // The focus-peaking switch, and the two choices it exposes. // // **An instrument, not an operation**, exactly as the histogram above it is: // it has no parameters in the edit graph, changes nothing about the // photograph, and answers a question rather than asking one. So it is written // by hand rather than generated from a descriptor, and FR-DEV-3a is untroubled // by it — nothing here names an operation or reads a parameter out of one. // // **Both choices are words, not swatches.** The colour picker is the obvious // place to draw four coloured squares, and NFR-A11Y-3 is the reason not to: // a control for choosing between hues, presented only as hues, is unusable by // the person most likely to need to change it. The chips say "Red" and "Cyan". // // **Why the two chip rows only exist while peaking is on.** They are settings // for something that is not happening, and the develop column is the // photographer's instrument panel — every row it holds is a slider pushed // below the fold. The panel's own height is bound to its content, so the // column reflows rather than leaving a gap. import { Theme } from "theme.slint"; import { Button, PanelHeading, Caption } from "widgets.slint"; import { Segmented } from "controls.slint"; // TRACES: FR-CULL-3 // The marks themselves, composited over the canvas. // // **A layer over the photograph and not a tint in it**, which is the same rule // `app.slint` states on the region map: a diagnostic "must not reach the // histogram, an export, or the texture the develop pass hands the compositor". // `HistogramPass` counts whatever the develop pass last rendered, so marks // painted into that frame would arrive in the histogram as a spike and in the // clipping figure as blown highlights. The layer is transparent everywhere // except where something is in focus. // // **No `source-clip` and no rotation**, unlike the region map. That is a // source-space picture being windowed down to the visible part; this was // measured on the rendered frame itself, so it is already cropped, zoomed and // turned exactly as the canvas is. One fewer thing that can drift out of // registration. // // The caller places it on `canvas-area`'s fitted rect, which is the shared // contract for anything that lands on the picture. export component FocusMarks inherits Image { /// The overlay `DevelopSession::focus_overlay` produced for this frame. in property marks; source: root.marks; image-fit: fill; // Nearest-neighbour: a mark is one pixel wide, and smoothing spreads it // into a grey haze that reads as softness — the opposite of what it is // reporting. image-rendering: ImageRendering.pixelated; } /// TRACES: FR-CULL-3 /// The focus overlay: whether it can be drawn, whether it is, and how. /// /// A global rather than four properties and three callbacks on the panel — see /// `session.slint` for why the develop panels stopped taking their wiring /// through the window root. /// /// The overlay is one thing on one photograph, so every drawing of this panel /// is a view of the same state: turning peaking on in the dock and finding the /// column's copy still saying "Show focus peaking" would be two controls /// disagreeing about one fact. export global Peaking { /// Whether this device could build the overlay at all. /// /// A compute pass can fail to compile on a driver nobody here has, and the /// honest response is to say so rather than to offer a switch that does /// nothing when pressed. The develop view keeps working without it; only /// this panel changes. in property available: true; /// Whether the overlay is currently being drawn. /// /// `showing` rather than the obvious `on`: Slint has no reserved word /// there today, and a one-word property that might become one is not worth /// the bet on a panel this small. in property showing: false; /// Index into `PeakSensitivity`, in the order Rust declares it. in property sensitivity: 1; /// Index into `PeakColour`, likewise. in property colour: 0; callback toggled(bool); callback sensitivity-picked(int); callback colour-picked(int); } // TRACES: FR-CULL-3 export component FocusPanel inherits Rectangle { background: Theme.surface; // Both chip rows wrap at three rather than running four abreast. That was // once about not setting the sidebar's width for every other panel in it; // the column is a mandated `panel-width` now and no panel sets it, so what // the wrap buys today is simply that these fit inside it rather than // needing to be panned to. // // Flat rather than nested, for the reason `SpotPanel` and `MaskPanel` both // give: a nested conditional layout under-reports its height here and the // rows below it get drawn on top of one another. Every row carries its own // `if`. layout := VerticalLayout { padding: Theme.gap; spacing: Theme.gap-sm; alignment: start; PanelHeading { text: "FOCUS"; } if !Peaking.available: Caption { text: "This device could not build the overlay."; wrap: word-wrap; } if Peaking.available: Button { // The label states the action rather than the state, as the mask // overlay's does: a photographer reads a button for what pressing // it will do. text: Peaking.showing ? "Hide focus peaking" : "Show focus peaking"; active: Peaking.showing; clicked => { Peaking.toggled(!Peaking.showing); } } if Peaking.available && Peaking.showing: Segmented { label: "Sensitivity"; hint: "lower on a noisy frame"; options: ["Low", "Medium", "High"]; selected: Peaking.sensitivity; columns: 3; picked(i) => { Peaking.sensitivity-picked(i); } } if Peaking.available && Peaking.showing: Segmented { label: "Marks"; hint: "pick what the subject is not"; options: ["Red", "Yellow", "Cyan", "Magenta"]; selected: Peaking.colour; columns: 3; picked(i) => { Peaking.colour-picked(i); } } if Peaking.available && Peaking.showing: Caption { // Said once, here, rather than left to be discovered: the marks go // away while a control is moving because a half-resolution draft // frame cannot be measured for sharpness (see `FocusPeakPass`). // // Kept to one short sentence on purpose. A wrapping Text reports // its *unwrapped* width as its preferred one, so a paragraph here // makes this panel's preferred width the length of the paragraph. // That no longer drags the sidebar open with it — the column is a // mandated width — but it does still decide how much of the row // the panel wants, and a sentence is the honest amount to ask for. text: "Marks pause while a control is dragged."; wrap: word-wrap; } } }