Skip to main content

truce_gui/
lib.rs

1//! Built-in GUI for truce plugins.
2//!
3//! Orchestrates the two `truce_gui_types::RenderBackend` impls into
4//! editor types: with the default `cpu` feature, `BuiltinEditor`
5//! rasterises widgets to a `truce_cpu::CpuBackend` (tiny-skia)
6//! pixmap and blits it to a wgpu surface; with the `gpu` feature,
7//! `GpuEditor` renders directly through `truce_gpu::WgpuBackend`.
8//! The non-runtime data types (layout, widget regions, interaction
9//! state, theme, render trait) live in `truce-gui-types` and the
10//! plugin traits in `truce-plugin`; this crate re-exports them so
11//! existing `truce_gui::...` paths keep working.
12
13// Widget-drawing helpers, `RenderBackend` trait methods, and interaction
14// dispatch all take many independent geometry / state / theme arguments.
15// The long signatures are intentional; bundling them into builder
16// structs would obscure call sites without simplifying any single
17// call.
18#![allow(clippy::too_many_arguments)]
19
20// `blit` (CPU pixmap → wgpu surface upload) is only needed for the
21// cpu path. Gated together with `truce-cpu`.
22#[cfg(feature = "cpu")]
23pub mod blit;
24// baseview-bound editor is macOS / Windows / Linux only. iOS
25// embeds the editor in a UIView managed by the AUv3 view
26// controller - see [`editor_ios`]. `BuiltinEditor` itself stays
27// available regardless of the `cpu` feature so the wgpu-only
28// `GpuEditor` can wrap it; the cpu-specific fields and Editor
29// trait impl inside this module are individually gated.
30#[cfg(not(target_os = "ios"))]
31pub mod editor;
32#[cfg(target_os = "ios")]
33pub mod editor_ios;
34#[cfg(target_os = "ios")]
35pub use editor_ios as editor;
36// The wgpu-backed editor wraps `BuiltinEditor` to render through
37// `truce_gpu::WgpuBackend`. Lives here so the user-facing renderer
38// crate (truce-gui) is a one-stop dep for plugin authors; the wgpu
39// primitives stay an implementation detail in truce-gpu.
40#[cfg(all(feature = "gpu", not(target_os = "ios")))]
41pub mod gpu_editor;
42pub mod interaction;
43pub mod platform;
44mod render_core;
45
46// `CpuBackend` (tiny-skia `RenderBackend` impl) + `font` (fontdue
47// glyph cache) live in the sibling `truce-cpu` crate so the CPU
48// rasterizer is a peer of `truce-gpu`'s `WgpuBackend` in the crate
49// graph. Re-exported under their historical `truce_gui::*` paths
50// so existing call sites keep working. Available whenever the `cpu`
51// feature is on *or* we're building for iOS — iOS always rasterizes
52// through `CpuBackend` (see `editor_ios`), independent of features,
53// so `truce-cpu` is a hard dep there.
54#[cfg(any(feature = "cpu", target_os = "ios"))]
55pub use truce_cpu::ColorExt;
56#[cfg(any(feature = "cpu", target_os = "ios"))]
57pub use truce_cpu::CpuBackend;
58#[cfg(any(feature = "cpu", target_os = "ios"))]
59pub use truce_cpu::font;
60// Internal sub-module path that `backend_cpu` used to occupy.
61#[cfg(any(feature = "cpu", target_os = "ios"))]
62#[doc(hidden)]
63pub mod backend_cpu {
64    pub use truce_cpu::CpuBackend;
65}
66
67// Re-export the lightweight data + trait surface from `truce-gui-types`
68// so old `truce_gui::layout::*` / `truce_gui::widgets::*` /
69// `truce_gui::theme::*` paths continue to resolve. New code can import
70// directly from `truce_gui_types`.
71#[cfg(target_os = "ios")]
72pub use truce_gui_types::ios;
73pub use truce_gui_types::{ImageId, ParamSnapshot, RenderBackend, Theme};
74pub use truce_gui_types::{layout, render, snapshot, theme, widgets};
75
76// Re-export plugin-logic traits from `truce-plugin` for the same
77// backward-compat reason.
78pub use truce_plugin::{PluginLogic, PluginLogic64, PluginLogicCore, default_hit_test};
79
80#[doc(hidden)]
81pub use truce_plugin::__plugin_logic_deps;
82
83pub use editor::BuiltinEditor;
84#[cfg(all(feature = "gpu", not(target_os = "ios")))]
85pub use gpu_editor::GpuEditor;
86pub use platform::{EditorScale, to_physical_px};
87
88/// Construct truce's default editor for a plugin's `editor()` impl.
89///
90/// Picks the renderer based on which feature is enabled:
91///
92/// - `gpu` (opt-in): wraps a [`BuiltinEditor`] in a `GpuEditor`
93///   that renders directly through `truce_gpu::WgpuBackend`.
94/// - `cpu` (default): returns a [`BuiltinEditor`] whose `Editor`
95///   impl rasterises to a tiny-skia pixmap and blits it to a wgpu
96///   surface.
97/// - iOS: always returns the iOS `BuiltinEditor` (UIView-hosted
98///   `CAMetalLayer`); the `gpu` feature has no effect on iOS.
99///
100/// Most layout-only plugins implement [`truce_plugin::PluginLogic::editor`] as:
101///
102/// ```ignore
103/// fn editor(&self) -> Box<dyn truce_core::Editor> {
104///     truce_gui::default_editor(
105///         self.params.clone(),
106///         GridLayout::build(vec![ /* widgets */ ]),
107///     )
108/// }
109/// ```
110///
111/// Only compiled when a renderer feature (`cpu` or `gpu`) is on.
112/// Crates that depend on `truce-gui` purely for its types / platform
113/// helpers (e.g. `truce-egui`, `truce-iced`, `truce-slint`) can build
114/// it with neither feature and simply not call this function.
115#[cfg(any(feature = "cpu", feature = "gpu", target_os = "ios"))]
116#[must_use]
117pub fn default_editor<P: truce_params::Params + 'static>(
118    params: std::sync::Arc<P>,
119    layout: truce_gui_types::layout::GridLayout,
120) -> Box<dyn truce_core::editor::Editor> {
121    let builtin = BuiltinEditor::new_grid(params, layout);
122    #[cfg(target_os = "ios")]
123    {
124        Box::new(builtin)
125    }
126    #[cfg(all(feature = "gpu", not(target_os = "ios")))]
127    {
128        Box::new(GpuEditor::new(builtin))
129    }
130    #[cfg(all(feature = "cpu", not(feature = "gpu"), not(target_os = "ios")))]
131    {
132        Box::new(builtin)
133    }
134}
135
136/// Fluent shorthand for [`default_editor`]. Build a `GridLayout`,
137/// then close the `editor()` impl with `.into_editor(&self.params)`:
138///
139/// ```ignore
140/// fn editor(&self) -> Box<dyn truce_core::Editor> {
141///     GridLayout::build(vec![ /* widgets */ ])
142///         .with_title("GAIN")
143///         .into_editor(&self.params)
144/// }
145/// ```
146///
147/// Equivalent to `default_editor(self.params.clone(), layout)` - the
148/// `&Arc<P>` is cloned internally so the call site stays free of an
149/// explicit `.clone()`. Bring it into scope with
150/// `use truce_gui::IntoLayoutEditor;` (it can't ride along on
151/// `truce::prelude`, which deliberately doesn't depend on this crate).
152///
153/// The method name mirrors [`truce_core::IntoEditor::into_editor`] (the
154/// blanket "box a concrete editor" helper used by egui / iced / slint),
155/// so every `editor()` impl ends the same way - layout plugins just
156/// pass their params.
157///
158/// Same feature gating as [`default_editor`]: only compiled when a
159/// renderer feature (`cpu` / `gpu`) is on, or on iOS.
160#[cfg(any(feature = "cpu", feature = "gpu", target_os = "ios"))]
161pub trait IntoLayoutEditor {
162    /// Wrap this layout in truce's default editor, picking the
163    /// renderer from the active `truce-gui` feature. See
164    /// [`default_editor`].
165    fn into_editor<P: truce_params::Params + 'static>(
166        self,
167        params: &std::sync::Arc<P>,
168    ) -> Box<dyn truce_core::editor::Editor>;
169}
170
171#[cfg(any(feature = "cpu", feature = "gpu", target_os = "ios"))]
172impl IntoLayoutEditor for truce_gui_types::layout::GridLayout {
173    fn into_editor<P: truce_params::Params + 'static>(
174        self,
175        params: &std::sync::Arc<P>,
176    ) -> Box<dyn truce_core::editor::Editor> {
177        default_editor(params.clone(), self)
178    }
179}
180
181/// Get the display scale factor used to size the next editor.
182///
183/// Screenshot rendering pins this to a deterministic value via
184/// [`truce_core::screenshot::override_scale`] (default 2.0) so a
185/// reference PNG baked on one host renders at the same physical
186/// dimensions on any other. Outside screenshot rendering the
187/// override is unset and we return the platform's main-screen DPI
188/// query (Retina = 2.0, normal = 1.0).
189#[must_use]
190pub fn backing_scale() -> f64 {
191    if let Some(s) = truce_core::screenshot::override_scale() {
192        return s;
193    }
194    platform::main_screen_scale()
195}