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:
+7
-61
@@ -30,7 +30,7 @@ use crate::render::Canvas;
|
||||
use crate::types::{ Color, Rect };
|
||||
use crate::widget::Element;
|
||||
|
||||
use super::{ DrawCtx, layout_and_draw };
|
||||
use super::{ build_draw_ctx, commit_draw_ctx, layout_and_draw };
|
||||
use super::chrome::{ apply_input_region, draw_fallback_banner, draw_titlebar };
|
||||
|
||||
/// GPU full-redraw path. Mirrors [`super::software::draw_surface_full`]
|
||||
@@ -88,23 +88,7 @@ pub( crate ) fn draw_surface_full_gpu<Msg: Clone>(
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
accessible_extras: Vec::new(),
|
||||
live_depth: 0,
|
||||
};
|
||||
let mut ctx = build_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, debug_layout );
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if ctx.debug_layout
|
||||
@@ -126,19 +110,8 @@ pub( crate ) fn draw_surface_full_gpu<Msg: Clone>(
|
||||
|
||||
canvas.present();
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.accessible_extras = ctx.accessible_extras;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
commit_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, ctx );
|
||||
ss.content_dirty = false;
|
||||
|
||||
let wl_surface = ss.surface.wl_surface();
|
||||
apply_input_region( wl_surface, compositor, input_region, scale );
|
||||
@@ -195,23 +168,7 @@ pub( crate ) fn draw_surface_partial_gpu<Msg: Clone>(
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout: false,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
accessible_extras: Vec::new(),
|
||||
live_depth: 0,
|
||||
};
|
||||
let mut ctx = build_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, false );
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if let Some( ref menu ) = ss.context_menu
|
||||
@@ -226,19 +183,8 @@ pub( crate ) fn draw_surface_partial_gpu<Msg: Clone>(
|
||||
canvas.clear_clip();
|
||||
canvas.present();
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.accessible_extras = ctx.accessible_extras;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
commit_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, ctx );
|
||||
ss.content_dirty = false;
|
||||
|
||||
let wl_surface = ss.surface.wl_surface();
|
||||
apply_input_region( wl_surface, compositor, input_region, scale );
|
||||
|
||||
@@ -90,6 +90,65 @@ pub( crate ) struct DrawCtx<Msg: Clone>
|
||||
pub live_depth: u32,
|
||||
}
|
||||
|
||||
/// Build the per-frame [`DrawCtx`] from the surface's [`FrameState`], moving the
|
||||
/// carried maps (cursor / selection / scroll) out of `frame` into the context.
|
||||
/// The interaction indices are passed by value (they live on `SurfaceState`, not
|
||||
/// `frame`). `debug_layout` is `false` on the partial paths. Shared by all four
|
||||
/// frame paths; [`commit_draw_ctx`] writes the produced state back.
|
||||
pub( crate ) fn build_draw_ctx<Msg: Clone>(
|
||||
frame: &mut crate::event_loop::FrameState<Msg>,
|
||||
focused_idx: Option<usize>,
|
||||
hovered_idx: Option<usize>,
|
||||
pressed_idx: Option<usize>,
|
||||
debug_layout: bool,
|
||||
) -> DrawCtx<Msg>
|
||||
{
|
||||
DrawCtx
|
||||
{
|
||||
focused_idx,
|
||||
hovered_idx,
|
||||
pressed_idx,
|
||||
cursor_state: std::mem::take( &mut frame.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut frame.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout,
|
||||
scroll_offsets: std::mem::take( &mut frame.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut frame.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut frame.scroll_navigable_items ),
|
||||
previous_widget_rects: frame.widget_rects.clone(),
|
||||
accessible_extras: Vec::new(),
|
||||
live_depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the finished frame's [`DrawCtx`] back into the [`FrameState`]: snapshot
|
||||
/// the interaction indices into `prev_*` and move the produced rect lists and
|
||||
/// carried maps back. Shared by all four frame paths; the caller must have
|
||||
/// already consumed anything it needs from `ctx` (e.g. `compute_damage`) before
|
||||
/// calling this, and resets `content_dirty` itself (it lives on `SurfaceState`).
|
||||
pub( crate ) fn commit_draw_ctx<Msg: Clone>(
|
||||
frame: &mut crate::event_loop::FrameState<Msg>,
|
||||
focused_idx: Option<usize>,
|
||||
hovered_idx: Option<usize>,
|
||||
pressed_idx: Option<usize>,
|
||||
mut ctx: DrawCtx<Msg>,
|
||||
)
|
||||
{
|
||||
frame.prev_focused = focused_idx;
|
||||
frame.prev_hovered = hovered_idx;
|
||||
frame.prev_pressed = pressed_idx;
|
||||
|
||||
frame.widget_rects = ctx.widget_rects;
|
||||
frame.scroll_rects = ctx.scroll_rects;
|
||||
frame.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
frame.cursor_state = ctx.cursor_state;
|
||||
frame.selection_anchor = ctx.selection_anchor;
|
||||
frame.scroll_offsets = ctx.scroll_offsets;
|
||||
frame.accessible_extras = ctx.accessible_extras;
|
||||
frame.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
}
|
||||
|
||||
/// Paint the built-in Copy / Cut / Paste context menu on top of the
|
||||
/// finished surface content. Called from the software and GLES draw
|
||||
/// paths right before `present()` so the menu sits above everything
|
||||
|
||||
+11
-65
@@ -29,7 +29,7 @@ use crate::render::Canvas;
|
||||
use crate::types::{ Color, Rect };
|
||||
use crate::widget::Element;
|
||||
|
||||
use super::{ DrawCtx, compute_damage, layout_and_draw };
|
||||
use super::{ build_draw_ctx, commit_draw_ctx, compute_damage, layout_and_draw };
|
||||
use super::chrome::{ apply_input_region, draw_fallback_banner, draw_titlebar };
|
||||
|
||||
/// Full redraw path: clear the canvas, run layout+draw for every widget, then
|
||||
@@ -85,23 +85,7 @@ pub( crate ) fn draw_surface_full<Msg: Clone>(
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
accessible_extras: Vec::new(),
|
||||
live_depth: 0,
|
||||
};
|
||||
let mut ctx = build_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, debug_layout );
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if ctx.debug_layout
|
||||
@@ -126,11 +110,11 @@ pub( crate ) fn draw_surface_full<Msg: Clone>(
|
||||
Vec::new()
|
||||
} else {
|
||||
compute_damage(
|
||||
&ss.widget_rects,
|
||||
&ss.frame.widget_rects,
|
||||
&ctx.widget_rects,
|
||||
ss.prev_focused,
|
||||
ss.prev_hovered,
|
||||
ss.prev_pressed,
|
||||
ss.frame.prev_focused,
|
||||
ss.frame.prev_hovered,
|
||||
ss.frame.prev_pressed,
|
||||
ss.focused_idx,
|
||||
ss.hovered_idx,
|
||||
ss.gesture.pressed_idx,
|
||||
@@ -138,19 +122,8 @@ pub( crate ) fn draw_surface_full<Msg: Clone>(
|
||||
)
|
||||
};
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.accessible_extras = ctx.accessible_extras;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
commit_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, ctx );
|
||||
ss.content_dirty = false;
|
||||
|
||||
canvas.write_to_wayland_buf( canvas_buf, swap_rb );
|
||||
|
||||
@@ -225,23 +198,7 @@ pub( crate ) fn draw_surface_partial<Msg: Clone>(
|
||||
|
||||
ss.titlebar_close_rect = draw_titlebar( canvas, &ss.titlebar_title, pw, tb_h, sf );
|
||||
|
||||
let mut ctx: DrawCtx<Msg> = DrawCtx
|
||||
{
|
||||
focused_idx: ss.focused_idx,
|
||||
hovered_idx: ss.hovered_idx,
|
||||
pressed_idx: ss.gesture.pressed_idx,
|
||||
cursor_state: std::mem::take( &mut ss.cursor_state ),
|
||||
selection_anchor: std::mem::take( &mut ss.selection_anchor ),
|
||||
widget_rects: Vec::new(),
|
||||
debug_layout: false,
|
||||
scroll_offsets: std::mem::take( &mut ss.scroll_offsets ),
|
||||
scroll_rects: Vec::new(),
|
||||
scroll_canvases: std::mem::take( &mut ss.scroll_canvases ),
|
||||
scroll_navigable_items: std::mem::take( &mut ss.scroll_navigable_items ),
|
||||
previous_widget_rects: ss.widget_rects.clone(),
|
||||
accessible_extras: Vec::new(),
|
||||
live_depth: 0,
|
||||
};
|
||||
let mut ctx = build_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, false );
|
||||
layout_and_draw::<Msg>( view, canvas, screen_rect, &mut ctx, 0 );
|
||||
|
||||
if let Some( ref menu ) = ss.context_menu
|
||||
@@ -258,19 +215,8 @@ pub( crate ) fn draw_surface_partial<Msg: Clone>(
|
||||
|
||||
canvas.clear_clip();
|
||||
|
||||
ss.prev_focused = ss.focused_idx;
|
||||
ss.prev_hovered = ss.hovered_idx;
|
||||
ss.prev_pressed = ss.gesture.pressed_idx;
|
||||
|
||||
ss.widget_rects = ctx.widget_rects;
|
||||
ss.scroll_rects = ctx.scroll_rects;
|
||||
ss.scroll_canvases = std::mem::take( &mut ctx.scroll_canvases );
|
||||
ss.cursor_state = ctx.cursor_state;
|
||||
ss.selection_anchor = ctx.selection_anchor;
|
||||
ss.scroll_offsets = ctx.scroll_offsets;
|
||||
ss.accessible_extras = ctx.accessible_extras;
|
||||
ss.scroll_navigable_items = ctx.scroll_navigable_items;
|
||||
ss.content_dirty = false;
|
||||
commit_draw_ctx( &mut ss.frame, ss.focused_idx, ss.hovered_idx, ss.gesture.pressed_idx, ctx );
|
||||
ss.content_dirty = false;
|
||||
|
||||
canvas.write_to_wayland_buf( canvas_buf, swap_rb );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user