Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/codebase-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,16 @@ Freeze capture waits for the overlay-suppression frame, then selects `wlr-screen
- `render.rs` exposes provisional shape previews for live feedback.

4. **Rendering to the overlay**
- `WaylandState::render` uses Cairo + SHM buffers.
- Draw order: board background → finalized shapes → provisional shape → text cursor preview → status bar (if enabled) → help overlay (if toggled).
- `WaylandState::render` acquires an SHM buffer, prepares mutable animation/damage state,
builds an owned `FramePlan`, paints with Cairo, and submits the buffer. A busy buffer pool
defers the frame before preparation. `state/render/{prepare,plan,paint,submit}.rs` keep
these boundaries explicit; the planner derives canvas policy and screen/world/buffer damage.
- `CanvasRenderCtx` borrows frame parameters and the local Cairo target. The canvas layer
cache, reusable profile baseline, and effect damage history remain in `RenderRuntime`.
- Draw order: board background → finalized shapes → spotlight effects → provisional shape
→ text cursor preview → UI. Color-profile passes surround UI painting according to their
selected targets. Main-surface submission precedes toolbar rendering and marking capture
preflight rendered.
- `ui` module encapsulates status/help overlays, while `draw` handles actual vector geometry routines.

The result is a predictable pipeline: Wayland → handlers → `InputState` →
Expand Down
2 changes: 1 addition & 1 deletion src/backend/wayland/state/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- This subtree supports live overlay runtime state: buffers, damage, boards, capture routing, clipboard paste, color picker, onboarding, PDF export, render helpers, toolbar plumbing, zoom, and core accessors.
- Runtime owners extracted from `WaylandState` live beside it: `focus.rs` (activation, focus, and startup acquisition), `protocol_globals.rs` (bound globals and toolkit handler state), `pointer_runtime.rs` (pointer position, board-pan and chrome gestures, cursor, pointer-lock, and touch lifecycles), `region_capture/runtime.rs` (region generations, active/review/window-snap state, and query/preview workers), `acquisition.rs` (screen acquisition, zoom waiters, and eyedropper source correlation), `input_hud.rs` (system-reader lifecycle and reconciliation), `spotlight_runtime.rs` (render memory, warning latches, and wheel timing), `clipboard_runtime.rs` (single-flight workers and queue policy), `preference_stores.rs` (persistence stores and workers), `ui_animation.rs` (animation scheduling), `font_catalog.rs` (font-catalog prewarm), `text_input.rs` (text-input-v3 lifecycle and commit serials), `tablet_runtime.rs` (tablet-input-v2 objects and stylus contact), `key_repeat.rs` (manual key-repeat timing), and `helper_launch.rs` (About/configurator launches requested by input).
- `core/overlay.rs` owns suppression policy and capture-barrier state; `../surface.rs` owns output/fullscreen/layer placement and frozen-fullscreen transitions.
- `render/` owns overlay render phases plus `RenderRuntime` cache, baseline, and per-effect damage history; `toolbar/` owns `ToolbarChrome` (placement, inline interaction, and fade state), `ToolbarDrag` (built-in and GTK drag lifecycles), and runtime toolbar effects; `clipboard/` owns session paste helpers.
- `render/` separates mutable preparation, pure `FramePlan` decisions, Cairo painting, and buffer submission. `CanvasRenderCtx` borrows frame parameters; `RenderRuntime` owns cache, profile baseline, and per-effect damage history. `toolbar/` owns `ToolbarChrome` (placement, inline interaction, and fade state), `ToolbarDrag` (built-in and GTK drag lifecycles), and runtime toolbar effects; `clipboard/` owns session paste helpers.

## Invariants
- Preserve snapshot boundaries for export and session actions.
Expand Down
14 changes: 14 additions & 0 deletions src/backend/wayland/state/render/canvas/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::time::Instant;

use super::super::plan::{CanvasFrame, FrameGeometry};
use crate::util::Rect;

/// Parameters borrowed from the owned frame plan and its local Cairo target.
/// Backdrop replay resources and mutable runtime caches stay in the painter.
pub(in crate::backend::wayland::state::render) struct CanvasRenderCtx<'a> {
pub(in crate::backend::wayland::state::render) cairo: &'a cairo::Context,
pub(in crate::backend::wayland::state::render) geometry: &'a FrameGeometry,
pub(in crate::backend::wayland::state::render) canvas: &'a CanvasFrame,
pub(in crate::backend::wayland::state::render) damage_world: &'a [Rect],
pub(in crate::backend::wayland::state::render) now: Instant,
}
87 changes: 30 additions & 57 deletions src/backend/wayland/state/render/canvas/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod background;
mod context;
mod overlays;
mod text;

use super::super::*;
pub(super) use context::CanvasRenderCtx;

const SPOTLIGHT_MAGNIFIER_TOAST_SOURCE: &str = "spotlight-magnifier";

Expand Down Expand Up @@ -71,36 +73,30 @@ impl WaylandState {
);
}

#[allow(clippy::too_many_arguments)]
pub(super) fn render_canvas_layer(
&mut self,
ctx: &cairo::Context,
width: u32,
height: u32,
scale: i32,
phys_width: u32,
phys_height: u32,
now: Instant,
damage_world: &[crate::util::Rect],
render_transients: bool,
canvas: &CanvasRenderCtx<'_>,
mut perf: Option<&mut PerfRenderBreakdown>,
) -> Result<()> {
let capture_picker_active = self.capture_picker_chrome_suppressed();
let capture_picker_draws_committed = capture_picker_draws_committed(
capture_picker_active,
self.region_picker_include_drawings(),
);
let render_transients = render_transients && !capture_picker_active;
let canvas_transform_active = self.canvas_transform_active();
let (canvas_origin_x, canvas_origin_y) = self.canvas_view_origin();
let ctx = canvas.cairo;
let width = canvas.geometry.width;
let height = canvas.geometry.height;
let scale = canvas.geometry.scale;
let phys_width = canvas.geometry.physical_width;
let phys_height = canvas.geometry.physical_height;
let now = canvas.now;
let damage_world = canvas.damage_world;
let render_transients = canvas.canvas.render_transients;
let canvas_transform_active = canvas.canvas.transform_active;
let (canvas_origin_x, canvas_origin_y) = canvas.canvas.origin;
let shapes_total = self.input_state.boards.active_frame().shapes.len();
let text_halo_enabled = self.config.drawing.text_halo_enabled;
let text_halo_enabled = canvas.canvas.text_halo_enabled;

// For pure pan transforms, serve the board background and committed
// shapes from the baked layer cache: pan frames force full damage, so
// this turns an O(shapes) Cairo replay into a single aligned blit.
let layer_cache_start = perf.as_ref().map(|_| Instant::now());
let layer_cache_ready = if !capture_picker_active && self.canvas_layer_cache_usable() {
let layer_cache_ready = if canvas.canvas.layer_cache_eligible {
self.ensure_canvas_layer_cache(width, height, scale)
} else {
self.render.canvas_layer_cache_mut().clear();
Expand Down Expand Up @@ -129,7 +125,7 @@ impl WaylandState {
// frozen capture. Turning the Review toggle off keeps only the raw
// backdrop. Transient handles, provisional strokes, text previews,
// hover effects, and click highlights remain suppressed in both cases.
if !capture_picker_draws_committed {
if !canvas.canvas.draw_committed {
self.spotlight.note_frame(false);
if let Some(perf) = perf.as_mut() {
perf.shapes_total = shapes_total;
Expand All @@ -148,8 +144,8 @@ impl WaylandState {

if canvas_transform_active {
let _ = ctx.save();
if self.zoom.active {
ctx.scale(self.zoom.scale, self.zoom.scale);
if let Some(zoom_scale) = canvas.canvas.zoom_scale {
ctx.scale(zoom_scale, zoom_scale);
}
ctx.translate(-canvas_origin_x, -canvas_origin_y);
}
Expand All @@ -158,14 +154,9 @@ impl WaylandState {

let completed_shapes_start = perf.as_ref().map(|_| Instant::now());
self.render_committed_canvas_shapes(
ctx,
width,
height,
damage_world,
canvas_transform_active,
canvas,
layer_cache_ready,
&replay_ctx,
text_halo_enabled,
perf.as_deref_mut(),
);
if let (Some(perf), Some(completed_shapes_start)) = (perf.as_mut(), completed_shapes_start)
Expand Down Expand Up @@ -317,7 +308,7 @@ impl WaylandState {
}

// Render text cursor/buffer if in text mode
self.render_text_input_preview(ctx);
self.render_text_input_preview(canvas);

self.input_state.render_highlight_tool_ring(ctx, mx, my);

Expand All @@ -333,19 +324,19 @@ impl WaylandState {
Ok(())
}

#[allow(clippy::too_many_arguments)]
fn render_committed_canvas_shapes(
&mut self,
ctx: &cairo::Context,
width: u32,
height: u32,
damage_world: &[crate::util::Rect],
canvas_transform_active: bool,
&self,
canvas: &CanvasRenderCtx<'_>,
layer_cache_ready: bool,
replay_ctx: &crate::draw::EraserReplayContext<'_>,
text_halo_enabled: bool,
mut perf: Option<&mut PerfRenderBreakdown>,
) {
let ctx = canvas.cairo;
let width = canvas.geometry.width;
let height = canvas.geometry.height;
let damage_world = canvas.damage_world;
let canvas_transform_active = canvas.canvas.transform_active;
let text_halo_enabled = canvas.canvas.text_halo_enabled;
let shapes = &self.input_state.boards.active_frame().shapes;
if layer_cache_ready && self.render.canvas_layer_cache().blit(ctx) {
debug!("Rendered committed shapes from layer cache");
Expand Down Expand Up @@ -464,19 +455,9 @@ fn provisional_point_count(stroke: &crate::input::tool::ProvisionalToolStroke<'_
}
}

const fn capture_picker_draws_committed(
capture_picker_active: bool,
include_drawings: bool,
) -> bool {
!capture_picker_active || include_drawings
}

#[cfg(test)]
mod tests {
use super::{
capture_picker_draws_committed, rects_intersect, safe_shape_damage_bounds,
union_damage_bounds,
};
use super::{rects_intersect, safe_shape_damage_bounds, union_damage_bounds};
use crate::util::Rect;

#[test]
Expand Down Expand Up @@ -509,12 +490,4 @@ mod tests {
assert!(!rects_intersect(Rect::new(0, 10, 10, 5).unwrap(), damage));
assert!(rects_intersect(Rect::new(9, 10, 2, 5).unwrap(), damage));
}

#[test]
fn picker_preview_follows_the_annotated_export_choice() {
assert!(capture_picker_draws_committed(false, false));
assert!(capture_picker_draws_committed(false, true));
assert!(capture_picker_draws_committed(true, true));
assert!(!capture_picker_draws_committed(true, false));
}
}
Loading
Loading