feat: polish the drawing UI and add a fading laser pointer - #394
Merged
Merged
Conversation
Pen smoothing and Shape Pen sensitivity were bare "− 3 +" steppers; with
Shape Pen two identical ones sat side by side and the value text was dim.
The shared model now gives the tool steppers a short caption ("Smooth",
"Detect"), and both toolbars draw it before the − half in the caption
tone. The readout between the halves uses the primary foreground at the
numeral buttons' weight, centered in its slot. The builtin tree reserves
the caption slot, so the width planner budgets it for both frontends.
Docked selection steppers keep their unit-bearing readouts uncaptioned.
With context_aware_ui on (the default), show_text_controls = true pinned the text size, Bold, and font family onto every tool, so Pen, Shape Pen, Marker, and Arrow carried three controls that changed nothing they draw. Context-aware UI now offers them only where they reach the next shape: text and sticky notes, arrows while auto-number labels are on, and step markers, whose number uses the font's family and weight but takes its size from the marker's own slider (so they get Bold and family, not the text size). Both toolbars read the same spec. show_text_controls keeps its meaning in classic mode (context_aware_ui = false); the example config, CONFIG.md, the Settings tooltip, and the configurator row say so.
The Shapes picker read as active whenever any shape-family tool was up, so Shape Pen, Line, and Arrow lit both their own strip button and the picker, although full layouts leave those three out of the picker. The picker now stands in for the active tool only while that tool lives inside it (Rect, Ellipse, Blur, Spotlight, polygons; plus Shape Pen, Line, and Arrow in the simple layout), or while its popover is open. Both toolbars read the shared control state, so both change together.
The palette's black (#241F31) sits at about 1.2:1 against the dark toolbar, so its swatch read as a gap in the color row, the radial ring, and the help overlay's K badge. A shared helper in ui::theme::swatch measures WCAG contrast (linearized sRGB) between a swatch, as it shows over its chrome, and that chrome; below 3:1 (WCAG 1.4.11) the swatch's edge becomes a 1.5px ring in the tone that contrasts with the chrome (light on dark, dark on light). The GTK and builtin toolbar swatches, the radial color ring, and the help overlay color badges use it; every other swatch keeps its quiet hairline.
Filled slots already drew their tool glyph and color, but replaced the
slot number, and empty slots looked like five ordinary buttons. A filled
slot now keeps its number as a small caption in the corner opposite its
color swatch, and its tooltip and accessible name summarize what it
applies ("Preset 1: Pen, Red, 4px"; the color in the quick-color
palette's words, else hex; a user-given name leads). An empty slot shows
its number dimmed until hovered, and its tooltip says it is empty and
names the configured save binding ("click or press Shift+1 to save the
current tool"). Both toolbars change together; slot sizes are unchanged,
so the strip width planner needs no new budget.
Toolbar islands, popovers, and tooltips (built-in and GTK), the context menu and submenus, board picker, properties panel, command palette, modal cards, the capture chooser, the region-capture readout and legend, toasts, and the help panel used 0.85-0.98 alpha fills, so terminal text bled through labels and a white board turned the chrome muddy. Their backgrounds are now opaque; theme colors, borders, and the translucent full-screen dim scrims are unchanged. The help panel's default ui.help_overlay_style.bg_color alpha moves from 0.92 to 1.0 (example config and CONFIG.md updated); a user-set alpha is still honored.
With ui.toolbar.idle_fade on, the top strip and its style pill used to dim to 55% opacity, which read as a broken ghost bar over busy content. They now fade out to fully transparent after ~4 s without drawing or toolbar use, and fade back in when the pointer comes within 64 px of the strip or touches the top screen edge, while the pointer or keyboard focus is on it, and for 1.5 s after a tool or color change made away from the strip (a modifier held for a shortcut does not count). A new stroke keeps a visible strip up but never brings back a hidden one, and the strip lingers for the idle delay after the pointer leaves. Open menus still hold it; idle_fade = false keeps it always visible. The hidden strip stays mapped at its size but takes no input: the built-in surface gets an empty input region, the inline strip ignores hits, and the GTK window disables input and shows its transparent capture frame. Proximity is measured on the canvas surface. Labels read "Hide toolbar when idle" in the settings popover and the configurator; config docs, example config, and search terms follow.
Presenters had no disappearing ink. The new Laser tool (L, the toolbar next to Marker in Regular and Advanced, or Laser Pointer Tool in the palette) draws a glowing stroke with the same live preview path as the pen. On release the stroke goes to a LaserInk owner instead of the frame, so it never reaches undo, the session, selection, exports, or captures, which render without transients. Ink stays for hold_ms after the latest release, then fades over fade_ms. Strokes drawn before the fade ends keep the whole group on screen, so a multi-stroke gesture disappears together. Only a fade in motion ticks the animation clock; a hold schedules one wakeup, idle ink costs nothing, and damage follows each stroke's own path. Reduced motion removes the ink in one step instead of fading. Color, width, and timing come from a new [laser] section with clamping, also on the configurator's Drawing page. Clear Canvas wipes laser ink, locked presenter mode allows the laser, and light mode keeps it when it is the active tool.
Click-select, rubber-band select, deselect, undo and paste changed the selection without damaging where its halo and handles were or now belong, so only the status bar repainted: new chrome appeared late and old halos stayed on screen until an unrelated full repaint. Route every membership change through one InputState helper that measures the selection chrome (halos, handle frame, and a lone text shape's resize handle) before and after the change and damages both. Undo/redo and delete measure the chrome before they mutate the frame, and releasing a held selection repaints the handles it hid. Measurer aware _with variants replace the ad hoc damage at the old call sites.
A sticky note or text block placed near an edge, or one that grew past the edge while typing, rendered partly off-screen and was committed there, cutting the note off at the output boundary. Every draft change already passes through the text preview damage update, so fit the draft there: shift its anchor left or up until the rendered block (glyphs, background, or note card and shadow) sits inside the visible canvas rect, which follows pan and zoom. Commit fits once more to cover style changes made since the last keystroke. An edited block may keep reaching as far past the edge as it already did, so opening a note that straddles the edge does not move it.
A modifier change that arrived without a key press, such as a compositor wl_keyboard.modifiers sync or a Ctrl release, updated the modifier state and the active drag tool but requested no redraw, so the status bar kept showing the Ctrl drag tool (Rectangle) until the next unrelated event. Route modifier presses, releases, resets, and compositor syncs through one update helper that requests a redraw when any modifier changed. sync_modifiers now reports whether it changed anything. The canvas is not dirtied; the render repaints only the status bar and tool preview through their existing UI effect damage.
When Shape Pen turned a stroke into a shape, nothing said so or pointed to the one-undo escape hatch, so the ink-back contract was easy to miss. A recognized stroke now shows a small chip beside the new shape, such as "Circle · Ctrl+Z keeps ink", using the configured undo shortcut (or "Undo keeps ink" when undo is unbound). It holds for about a second, fades out by 1.5 s, and stays opaque under reduced motion. It is overlay chrome with its own UI effect slot, so each frame damages only its footprint; it is never drawn into frames, exports, captures, or sessions. Undo, redo, and board or page changes take it away. [drawing] shape_recognition_feedback (default true) turns it off, also on the configurator's Drawing page as "Name recognized Shape Pen shapes".
Help, quick reference, the command palette, and the board, font, and color pickers are drawn on the overlay surface, so the separately stacked GTK and layer-shell toolbars covered them. A derived suppression now unmaps both frontends while one of these modals is open, alongside the existing capture-picker suppression. Toolbar preferences are never written, so position, pin, minimize, and open popovers come back when the modal closes. The inline built-in bars now paint before popups so menus and panels draw above them.
The first-run card is painted over the canvas but every press on it fell through to the active tool: clicking the card drew a dot, enabled Undo, and ticked off the "Draw a stroke" step. The renderer now returns the card's rectangle, and pointer, touch, and stylus presses inside it are owned by the card through the chrome-press path. Other buttons are swallowed with their release, idle hover over the card no longer reaches the canvas, and the cursor shows the default arrow.
The first-run tour opened on a keyboard-only background-service prompt before the user had drawn anything. It now runs value first: draw and undo, the toolbar and how to leave (Esc), color and thickness, quick access, and finding commands, with background mode offered last. The rebind jargon left the find step; the replayable tour still covers it. Card actions are real buttons (Got it, Set up, Not now, Skip tour) with their keys shown beside them; Enter, Y/N, and Shift+Esc still work and no longer fire mid-gesture or with Ctrl/Alt held. Buttons run when press and release land on the same button and highlight on hover. onboarding.toml moves to v7: a pre-v7 tour still waiting on the old first step restarts at draw and undo, the retired wait-draw step maps forward, and completed or skipped profiles are never re-enrolled.
With nothing typed the palette listed the registry in order, so Exit opened highlighted at the top and Ctrl+K, Enter closed the overlay, with Clear Canvas right below. The empty query now lists recent commands, then everyday ones (Undo, Redo, pen, marker, eraser, text, select, region capture, whiteboard, help), then the rest by category, and a trailing "Clear, delete & exit" group. Exit and destructive commands go there even when recently used, so none is preselected. The edit/unbind/reset icons now draw only on the selected or hovered row, where the trash icon no longer reads as "delete" beside Clear Canvas. Their space stays reserved so hovering never shifts text, and Ctrl+E, Ctrl+Delete, and Ctrl+R still act on the selected row.
The canvas menu put Clear All second, right under Paste, offered Paste with nothing to paste, had no Undo, Redo, capture, or way out, and drew its key hints in a tiny strip below the box. It now reads in groups: Undo and Redo (dimmed without history), Paste and Capture Region..., view and boards, the other surfaces, Clear All, and Exit last (Hide Overlay when the daemon owns the overlay). Paste is enabled once shapes were copied or a capture went to the clipboard; the system clipboard cannot be probed without blocking, so that is all it knows. The highlight row uses the action's own short label, Highlight, as the toolbar does. The shape menu gains the same dividers and closing Exit row. Dividers sit on row edges, so rows keep one pitch for hit-testing and keyboard focus. The key-hint footer is inside the box at 12px, and the box is sized for every hint it can show.
Rows now read "action label ... key chips": labels form the left
column and keys start one gap past the widest label, joined by a
subtle dotted leader, so the eye can follow each row.
Actions without a binding are hidden by default. A new footer pill,
"Show Unbound" (or Tab, since printable keys type into search),
brings them back with a muted "Not bound" marker instead of a key
chip. Search still finds unbound actions. The choice lasts for the
process.
Body text now renders at the configured help font size instead of
80% of it, footer pills use the body size, and secondary text has a
12px floor (11px in key chips). Sizes are logical pixels, so they
follow the output scale. Page 1 still fits a 1080p output.
Also fix the search-mode nav line, whose segments advanced by ink
width and overlapped ("Esc: Close" / "Backspace: Remove").
Add shared readable-floor tokens to the overlay theme: 12px for labels, hints, and status lines, 11px for keycap labels, and 13px for control labels and hint strips. Overlay chrome is laid out in logical pixels, so the floors follow the output scale. Apply them where text was too small: - capture action panel: labels 11 -> 13px, key chips 8.5 -> 11px, toggle 10.5 -> 13px, status 10.5 -> 12px; the rows and controls grow so nothing clips - region-capture hint strip: 12 -> 13px, drawn through the same text layout that measures it, in a taller strip - radial menu: shortcut and thickness keycaps 9 -> 11px, sub-ring labels 11 -> 12px - help overlay: its floors now come from the shared tokens The context-menu footer is left to the context-menu rework.
The capture toast printed the full absolute path and a "(Ctrl+Alt+O opens folder)" note across the whole screen. It now reads, for example, "Saved screenshot_2026-09-25_213231.png · Copied to clipboard". A path without a usable file name falls back to a ~-relative path. A saved file adds two buttons, reusing the toast's action chips: "Open folder · Ctrl+Alt+O" runs the existing Open Capture Folder action (its label shows the live binding), and "Copy path" copies the full path through the text clipboard pipeline and confirms with "Path copied". The toast stays up for 8 s so the buttons are reachable. Copy-only captures say "Copied to clipboard", and a saved clipboard-fallback file uses the same toast. Error toasts keep their full detail.
With a single output, "Output: WAYLAND-1" told the user nothing, and the permanent "About v0.9.26" chip took space in every frame. The output item now shows only while two or more outputs are connected. The backend reports the connected-output count along with the label (excluding an output that is being destroyed). An explicit active_output_badge = false still hides it, and the new ui.active_output_badge_always keeps it with a single output. A plain active_output_badge = true, which the example config has always carried, is not treated as a request to show a single output's name. The About/version chip is now off by default (show_status_about = false). It stays available as an opt-in item, and About remains in the toolbar and the help overlay. Update config.example.toml, docs/CONFIG.md, README, and the configurator's status-bar contents page and search terms.
Relaunching restored earlier ink onto the transparent overlay board, where it sits over whatever is on screen now, with no notice. After the launch-time session load, when the active board is transparent and its page has at least one shape, show "Restored 7 annotations from last session" with a Clear button. Clear runs Clear Canvas, so it is undoable. The toast uses the existing action-chip support and stays up for 8 s. Stay quiet for overlays the daemon reopens on a toggle (they carry the overlay-child generation variable and show what the user just had on screen), for loads without board data, for solid boards, and for later output or named-session switches.
With a GTK toolbar popover open, the toolbar surface keeps keyboard focus on its own Wayland connection, so Escape did not close the popover and tool shortcuts typed meanwhile were dropped. The GTK window and each popover now relay key presses to the backend, which routes them through the same press path as the overlay's own keyboard (with GTK's modifier state for that press only). Modifier presses, Tab, and keys owned by a focused slider or entry stay in GTK. Key routing now closes any open top-strip menu (shapes, overflow, Canvas/Session/Settings) on a key press: Escape only closes it, any other key closes it and still reaches its binding. The built-in frontend shares the same routing.
The chrome island's layout button cycled Simple -> Regular -> Advanced. Each preset gives the strip a different width, so the button moved out from under the pointer and a second click landed on the canvas. Regular and Advanced also rendered the same strip. The button now opens a small menu (a new LayoutMenu top-strip flyout) listing the three presets with a one-line description each and the current one marked; choosing a row applies the preset and closes the menu. Both frontends render the rows from one shared model, and the menu closes like the other flyouts (Escape, click-away, tool picks). Advanced now puts Rectangle, Ellipse, Blur, and Spotlight on the strip and keeps only the polygons in the Shapes picker; under width pressure those tools move to the overflow menu before Arrow and Line. Layout changes still persist through runtime-ui.toml as before.
A mouse-only user had no way to close the overlay: the chrome island offered Layout, About, Pin, and Minimize, and Minimize turned red on hover, which read as "close". A new Exit entry (item id top.chrome.exit) ends the chrome island in both frontends. It runs the Exit action as an explicit exit request, and its tooltip names the configured Exit keys. When the daemon spawned the overlay it reads "Hide overlay", since the daemon keeps running. It is a typed toolbar item, so customization can hide it. Minimize keeps its round chrome button but now lifts to a neutral tone on hover; red stays reserved for destructive actions.
Capture was reachable only through shortcuts and the command palette: the toolbar screenshot item shipped in the default hidden list. The default hidden list is now empty, so the capture button shows by default. It moves from the annotation utilities into the history island, after Undo/Redo and before the overflow toggle, and it starts the interactive region capture (select, then Copy / Save / Both / Board) instead of the selection-with-defaults capture. It stays a typed, hideable item and still yields to the overflow menu first under width pressure. Configs that author their own hidden list keep it; the example config, docs, and README now describe the new default.
After Minimize the strip collapsed to a 64x24, low-contrast tab that sat exactly over the restored strip's Select button, so a double-click restored the strip and then selected a tool. The tab is now 104x32 in both frontends (one shared size), with a raised, outlined body, the restore glyph, and a "Tools" caption; its tooltip and accessible label still say "Show toolbar". Restoring from the tab or the micro chip arms a short guard (about a double-click interval) in the input toolbar state, and the shared toolbar dispatch drops clicks while it runs, so the second click cannot reach the controls that reappear under the pointer.
The Settings popover greeted every visit with "Runtime preferences are saved separately" and the raw runtime-ui.toml path. Healthy runtime persistence now reads "Toolbar changes are saved automatically". The storage path moved into a collapsed "Details" disclosure: settings notices carry a detail flag, the shared settings model offers a Details / Hide details toggle, and a new SetSettingsDetailsOpen event keeps its state in the input toolbar state, so both frontends render the same rows. Warnings, recovery files, and the reset action stay in view; the reset button now reads "Reset toolbar changes".
Add a Pen feel panel with named levels and a live smoothing preview, plus inline meter and stepper options in both toolbar frontends. Expose the presentation setting in the configurator and config guide. Prefer visible controls over inflated neighboring hit targets. Accumulate fractional wheel travel per meter, clear it on pointer departure, and apply GTK wheel deltas to live application state so delayed snapshots cannot lose rapid or coalesced notches. Cover hit routing and GTK scroll callbacks.
A bare "90%" beside the marker opacity slider did not say which way was see-through, and the tooltip described it backwards. The track now fades from clear to solid in the current color, and the readout is a swatch of a marker stroke over sample text at the current opacity. Both use the alpha a marker stroke actually gets, so a translucent color previews as translucent as it draws. The percentage moves into the tooltip, which now reads "Marker opacity: 90% solid".
The arrow style button cycled Standard, Pointy, Curved, and Double on each click, hiding three styles behind names that do not say what an arrow looks like. The style pill now carries a chip showing the next arrow's style drawn and named, and clicking it opens a menu listing every style as a preview drawn from the arrow outline the canvas fills. Choosing a style sets it and closes the menu; the keyboard action still cycles. The menu is a top-strip menu like the layout menu in both frontends: it closes the other menus, hangs from the chip, and joins the built-in surface's input region.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Make drawing controls easier to understand, reduce overlay clutter, and guide first-time users through drawing before offering background setup.