truce_gui_types/lib.rs
1//! Lightweight GUI types for truce. No rasterization, no windowing.
2//!
3//! `truce-gui-types` carries the trait + data surface that GUI
4//! backends (the built-in `truce-gui::BuiltinEditor`, plus
5//! `truce-egui`, `truce-iced`, `truce-slint`) build on. Crates that
6//! only need to *describe* layouts and react to platform-translated
7//! input events depend on this crate; the heavy machinery
8//! (tiny-skia, baseview, truce-font, fontdue) stays in `truce-gui`.
9//!
10//! The split exists so `truce-plugin` (the user-facing
11//! `PluginLogic` trait crate) can name `GridLayout` /
12//! `RenderBackend` / `WidgetRegion` without pulling in a software
13//! rasterizer + windowing toolkit. Plugin authors who supply a
14//! custom editor (egui, iced, slint, raw window handle) end up
15//! transitively depending only on `truce-gui-types` instead of the
16//! full `truce-gui`.
17
18// Widget-drawing helpers, `RenderBackend` trait methods, and interaction
19// dispatch all take many independent geometry / state / theme arguments.
20// The long signatures are intentional; bundling them into builder
21// structs would obscure call sites without simplifying any single
22// call.
23#![allow(clippy::too_many_arguments)]
24
25pub mod interaction;
26pub mod layout;
27#[macro_use]
28pub mod macros;
29pub mod render;
30pub mod snapshot;
31pub mod theme;
32pub mod widgets;
33
34#[cfg(target_os = "ios")]
35pub mod ios;
36
37pub use render::{ImageId, RenderBackend};
38pub use snapshot::ParamSnapshot;
39pub use theme::Theme;
40
41/// Convert a logical extent (in points) to physical pixels.
42///
43/// Standardised rounding policy across every truce GUI backend:
44/// round to nearest, then clamp the result to `1` so a degenerate
45/// `0 × scale` doesn't collapse a wgpu surface (`width: 0` is a
46/// validation error). The `logical.max(1)` guard handles the
47/// converse - a zero-logical caller can't multiply through to `0`
48/// before the round.
49// Logical pixel sizes are bounded by `u32::MAX / scale`; in practice
50// no editor exceeds 16384 logical pixels.
51#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
52#[inline]
53#[must_use]
54pub fn to_physical_px(logical: u32, scale: f64) -> u32 {
55 (f64::from(logical.max(1)) * scale).round().max(1.0) as u32
56}