Bump to 0.2.0: SW/GLES paint parity, shared font resolution, FrameState refactor, docs and packaging fixes
Rendering parity (software ↔ GLES). The software backend now rounds glyph pen positions and image destinations to the nearest integer pixel, matching what the GLES backend already did; previously it truncated, so text and 1:1 images could land up to half a pixel off between the two backends and the bilinear sample read ~1 px softer than the source. Gradients and shadows are deliberately left unimplemented on the software backend, and the GLES multi-rect `glScissor` clip is left coarse on purpose: making it exact would need stencil bits the EGL config does not carry, or routing the partial-redraw path through the offscreen clip layer, which would break its `fill` / `clear_rects_transparent` scissor semantics. Adds software-backend pixel tests covering the snapping. Font resolution unification. The system-font candidate chain, `find_font_opt` and `load_default_font_bytes` lived in two copies (`render/helpers` and `gles_render/helpers`) that had already diverged — one resolved through `find_font_opt`, the other inlined the candidate loop — and now live once in `system_fonts`. The two per-backend `OnceLock` default-font caches and `primary_handle` collapse into a single `system_fonts::default_handle`. Module docs and the `font_registry` caller are updated accordingly. Shared image validation and rect inflation. `draw_image_data`'s dimension check and its one-line warning were byte-duplicated across both backends and are now `render::helpers::validate_rgba_dims`. The six manual symmetric `Rect`-inflate literals in the GLES primitives reuse the existing `Rect::expand`. FrameState and DrawCtx de-duplication. The eleven `SurfaceState` fields the draw pass owns and threads through `DrawCtx` — `widget_rects`, the cursor / selection maps, the scroll state, `accessible_extras`, `prev_focused` / `prev_hovered` / `prev_pressed` — move into a `FrameState` sub-struct. The per-frame `build_draw_ctx` / `commit_draw_ctx` helpers can then borrow `&mut ss.frame`, disjoint from `ss.canvas` and `ss.pool`, so the four frame paths (software / GLES × full / partial) replace their duplicated `DrawCtx` construction and write-back with a single helper call each. A whole-`SurfaceState` borrow could not express this (partial borrows do not cross function boundaries), which is why the helpers take the sub-struct. `content_dirty` stays on `SurfaceState` — it is an invalidation flag, not frame state — and is reset at the call site. rich_text tests. Adds the previously-missing `tests.rs` for the `RichText` widget: one hit rect per visual line a link spans (the widget's core invariant), the single-line and no-link cases, preferred-size growth with hard line breaks, and `map_msg` range preservation — all headless against a software `Canvas`, with line counts forced by `\n` so they do not depend on any system font's measured width. Documentation. Fills the rustdoc gaps on the embedder-facing surface: `core::UiSurface` accessors, the `egl_context` public API, the `GlesCanvas` methods, `theme::typography` and `theme::error`, and the `RichText` / `Text` builders; adds a crate-level "Rendering backends" overview. `CHANGELOG.md` is added (0.2.0 / 0.1.0), and `docs/widgets.md` / `docs/cookbook.md` gain `rich_text`, `external`, and the CPU-draw / path-clip / externally-laid-out-tree recipes. `debian/changelog` gets the 0.2.0-1 entry. Private intra-doc links to `system_fonts` are demoted to code spans so `cargo doc` is warning-free. Packaging. The `libltk-dev` registry crate shipped a `Cargo.toml` declaring the `lookup` bench while the `Makefile` install copied only `src/`, so Cargo refused to parse the manifest over a missing `benches/lookup.rs`; the install now ships `benches/` as well (the file alone satisfies the parse — criterion is a dev-dependency and is not resolved when the crate is consumed as a library). `Cargo.toml` is bumped to 0.2.0 to match the package version and the `ltk-0.2.0` registry directory.
This commit is contained in:
@@ -51,11 +51,17 @@ impl GlesCanvas
|
||||
r_x1 <= clip.x || c_x1 <= r_x0 || r_y1 <= clip.y || c_y1 <= r_y0
|
||||
}
|
||||
|
||||
/// Fill an arbitrary vector path (commands in surface coordinates) with a
|
||||
/// solid colour. CPU fallback: rasterised with tiny-skia into a bbox-sized
|
||||
/// pixmap and blitted as a transient texture — there is no GPU path shader.
|
||||
pub fn fill_path( &mut self, cmds: &[ PathCmd ], color: Color )
|
||||
{
|
||||
self.rasterise_path( cmds, color, None );
|
||||
}
|
||||
|
||||
/// Stroke an arbitrary vector path (commands in surface coordinates) with a
|
||||
/// centered stroke of `width` px. Same tiny-skia-into-texture CPU fallback as
|
||||
/// [`Self::fill_path`].
|
||||
pub fn stroke_path( &mut self, cmds: &[ PathCmd ], color: Color, width: f32 )
|
||||
{
|
||||
self.rasterise_path( cmds, color, Some( width ) );
|
||||
@@ -114,6 +120,10 @@ impl GlesCanvas
|
||||
unsafe { self.gl.delete_texture( tex ); }
|
||||
}
|
||||
|
||||
/// Fill `rect` with a solid colour, with per-corner rounding from `corners`.
|
||||
/// Coverage (including the rounded corners) comes from an SDF in the rect
|
||||
/// shader; `color.a` is multiplied by `global_alpha`. Culled early when the
|
||||
/// rect falls entirely outside the active scissor.
|
||||
pub fn fill_rect( &mut self, rect: Rect, color: Color, corners: Corners )
|
||||
{
|
||||
if self.rect_culled( rect, 1.0 ) { return; }
|
||||
@@ -124,13 +134,7 @@ impl GlesCanvas
|
||||
// stay anchored to the original rect — `u_pad` lets the shader
|
||||
// remap `v_uv` from the larger quad back into rect-local space.
|
||||
let pad = 1.0_f32;
|
||||
let expanded = Rect
|
||||
{
|
||||
x: rect.x - pad,
|
||||
y: rect.y - pad,
|
||||
width: rect.width + 2.0 * pad,
|
||||
height: rect.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = rect.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
let alpha = color.a * self.global_alpha;
|
||||
// SAFETY: see module doc. `u_rect_stroke = 0.0` triggers the fill
|
||||
@@ -221,13 +225,7 @@ impl GlesCanvas
|
||||
self.activate_target();
|
||||
// See fill_rect for the rationale on the 1 px quad pad.
|
||||
let pad = 1.0_f32;
|
||||
let expanded = Rect
|
||||
{
|
||||
x: rect.x - pad,
|
||||
y: rect.y - pad,
|
||||
width: rect.width + 2.0 * pad,
|
||||
height: rect.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = rect.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
// SAFETY: see module doc. `tex` is the cached LUT for `g.stops`
|
||||
// produced by `ensure_lut_texture` above (RGBA8, sampler unit 0);
|
||||
@@ -269,13 +267,7 @@ impl GlesCanvas
|
||||
self.activate_target();
|
||||
// See fill_rect for the rationale on the 1 px quad pad.
|
||||
let pad = 1.0_f32;
|
||||
let expanded = Rect
|
||||
{
|
||||
x: rect.x - pad,
|
||||
y: rect.y - pad,
|
||||
width: rect.width + 2.0 * pad,
|
||||
height: rect.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = rect.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
// SAFETY: see module doc. Same LUT contract as the linear path
|
||||
// above. `g.center` and `g.radius` are finite fractional values
|
||||
@@ -422,22 +414,10 @@ impl GlesCanvas
|
||||
// active scissor is culled before the shader runs, so the
|
||||
// snapshot only needs to cover the intersection.
|
||||
let pad = 1.0_f32;
|
||||
let snap_rect = Rect
|
||||
{
|
||||
x: target.x - pad,
|
||||
y: target.y - pad,
|
||||
width: target.width + 2.0 * pad,
|
||||
height: target.height + 2.0 * pad,
|
||||
};
|
||||
let snap_rect = target.expand( pad );
|
||||
self.snapshot_fbo_region_tight( snap_rect );
|
||||
self.activate_target();
|
||||
let expanded = Rect
|
||||
{
|
||||
x: target.x - pad,
|
||||
y: target.y - pad,
|
||||
width: target.width + 2.0 * pad,
|
||||
height: target.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = target.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
let aux_tex = self.aux_a.expect( "snapshotted" ).1;
|
||||
// SAFETY: see module doc. `aux_tex` was just populated by
|
||||
@@ -475,13 +455,7 @@ impl GlesCanvas
|
||||
// of terminating at the surface rect. Same rationale as
|
||||
// fill_rect. `u_size` / `u_radii` stay anchored to `target`.
|
||||
let pad = 1.0_f32;
|
||||
let expanded = Rect
|
||||
{
|
||||
x: target.x - pad,
|
||||
y: target.y - pad,
|
||||
width: target.width + 2.0 * pad,
|
||||
height: target.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = target.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
// SAFETY: see module doc. We swap the global blend state for the
|
||||
// duration of one draw and restore the canvas-wide default
|
||||
@@ -550,13 +524,7 @@ impl GlesCanvas
|
||||
// would shift the zero-line outward and, in the circle case
|
||||
// (radius = size/2), turn the result into a rounded square.
|
||||
let pad = half + 1.0;
|
||||
let expanded = Rect
|
||||
{
|
||||
x: rect.x - pad,
|
||||
y: rect.y - pad,
|
||||
width: rect.width + 2.0 * pad,
|
||||
height: rect.height + 2.0 * pad,
|
||||
};
|
||||
let expanded = rect.expand( pad );
|
||||
let mvp = ortho_rect( self.width, self.height, expanded );
|
||||
let alpha = color.a * self.global_alpha;
|
||||
// SAFETY: see module doc. `u_rect_stroke = width > 0.0` triggers
|
||||
|
||||
Reference in New Issue
Block a user