textslabs/lib.rs
1//! Textslabs is an experimental high level text library based on Parley.
2//!
3//! The goal is to allow any winit/wgpu program to have full-featured text and text editing with minimal integration effort.
4//!
5//! Most GUI programs or toolkits, games, etc. don't have any advanced requirements for text: they just want basic text boxes that "work the same way as everywhere else" (browsers, native operating system GUIs, etc.).
6//!
7//! If all that is available is a relatively low level library such as Parley, all these projects will have to do a large amount of repeated work, needlessly raising the barrier of entry to GUI programming.
8//!
9//! # Limitations
10//!
11//! This library is not ready for use. It currently does not reach its own goal of "full featured text".
12//!
13//! - Accessibility is supported in Parley itself but not in Textslabs, because of my personal lack of familiarity with the subject.
14//!
15//! - Textslabs currently uses the built-in Swash CPU rasterizer and a basic homemade atlas renderer to actually show the text on screen. The performance is acceptable but it is not as good as it could be. This might eventually be fixed by switching to the new "Vello Hybrid" renderer, or by solving the performance problems in Swash.
16//!
17//! - Parley itself has some limitations, but they will be probably fixed soon.
18//!
19//!
20//! # Usage
21//!
22//! See the `basic.rs` example in the repository to see how the library can be used.
23//!
24//! The two main structs are:
25//! - [`Text`] - manages collections of text boxes and styles
26//! - [`TextRenderer`] - renders the text on the GPU
27//!
28//! ## Imperative Mode
29//!
30//! The main way to use the library is imperative with a handle-based system:
31//!
32//! ```rust,no_run
33//! use textslabs::*;
34//! let mut text = Text::new_without_blink_wakeup();
35//!
36//! // Add text boxes and get handles
37//! let handle = text.add_text_box("Hello", (10.0, 10.0), (200.0, 50.0), 0.0);
38//! let edit_handle = text.add_text_edit("Type here".to_string(), (10.0, 70.0), (200.0, 30.0), 0.0);
39//!
40//! // Use handles to access and modify the boxes
41//! // text.get_text_box_mut(&handle).set_style(&my_style);
42//! text.get_text_box_mut(&handle).text_mut().push_str("... World");
43//!
44//! // Manually remove text boxes
45//! text.remove_text_box(handle);
46//! text.remove_text_edit(edit_handle);
47//! ```
48//!
49//! This interface is ideal for retained-mode GUI libraries, but declarative GUI libraries that diff their node trees can still use the imperative interface, calling the `Text::remove_*` functions when the nodes holding the handles are removed.
50//!
51//! Handles can't be `Clone`d or constructed manually, so they are unique references that can never be "dangling".
52//!
53//! [`Text`] uses slabs internally, so `get_text_box_mut()` and all similar functions are basically as fast as an array lookup. There is no hashing involved.
54//!
55//! ## Declarative Mode
56//!
57//! There is an optional declarative interface for hiding and removing text boxes. For hiding text boxes declaratively:
58//!
59//! ```ignore
60//! // Each frame, advance an internal frame counter,
61//! // and implicitly mark all text boxes as "outdated"
62//! text.advance_frame_and_hide_boxes();
63//!
64//! // "Refresh" only the nodes that should remain visible
65//! for node in current_nodes {
66//! text.refresh_text_box(&node.text_box_handle);
67//! }
68//!
69//! // Text boxes that were not refreshed will be remain hidden,
70//! // and they will be skipped when rendering or handling events.
71//! ```
72//!
73//! There's also an experimental function for removing text boxes declaratively: [`Text::remove_old_nodes()`].
74//!
75//! This library was written for use in Keru. Keru is a declarative library that diffs node trees, so it uses imperative-mode calls to remove widgets. However, it uses the declarative interface for hiding text boxes that need to be kept hidden in the background.
76//!
77//! ## Interaction
78//!
79//! Text boxes and text edit boxes are fully interactive. In simple situations, this requires a single function call: [`Text::handle_event()`]. This function takes a `winit::WindowEvent` and updates all the text boxes accordingly.
80//!
81//! As great as this sounds, sometimes text boxes are occluded by other objects, such as an opaque panel. In this case, handling a mouse click event requires information that the [`Text`] struct doesn't have, so the integration needs to be a bit more complex. The process is this:
82//!
83//! - Run `let topmost_text_box = `[`Text::find_topmost_text_box()`] to find out which text box *would* have received the event, if it wasn't for other objects.
84//! - Run some custom code to find out which other object *would* have received the event, if it wasn't for text boxes.
85//! - Compare the depth of the two candidates. For the text box, use [`Text::get_text_box_depth()`].
86//! - If the text box is on top, run [`Text::handle_event_with_topmost()`]`(Some(topmost_text_box))`, which will handle the event normally, but avoid looking for the topmost box again.
87//! - If the text box, is occluded, run [`Text::handle_event_with_topmost()`]`(None)`.
88//!
89//! For any `winit::WindowEvent` other than a `winit::WindowEvent::MouseInput`, this process can be skipped, and you can just call [`Text::handle_event()`].
90//!
91//! The `occlusion.rs` example shows how this works.
92
93
94mod setup;
95pub use setup::*;
96
97mod text_renderer;
98pub use text_renderer::*;
99
100mod text;
101pub use text::*;
102
103mod text_box;
104pub use text_box::*;
105
106mod text_edit;
107pub use text_edit::*;
108
109mod accessibility;
110pub use accessibility::*;
111
112pub use parley::TextStyle as ParleyTextStyle;
113
114/// Text style.
115///
116/// To use it, first add a `TextStyle2` into a [`Text`] with [`Text::add_style()`], and get a [`StyleHandle`] back. Then, use [`TextBox::set_style()`] to make a text box use the style.
117pub type TextStyle2 = ParleyTextStyle<'static, ColorBrush>;
118
119/// Style configuration for text edit boxes.
120///
121/// Contains color settings that are specific to text edit behavior (disabled/placeholder states).
122#[derive(Clone, Debug, PartialEq)]
123pub struct TextEditStyle {
124 /// Color to use when text is disabled
125 pub disabled_text_color: ColorBrush,
126 /// Color to use for placeholder text
127 pub placeholder_text_color: ColorBrush,
128}
129
130impl Default for TextEditStyle {
131 fn default() -> Self {
132 Self {
133 disabled_text_color: ColorBrush([128, 128, 128, 255]), // Gray
134 placeholder_text_color: ColorBrush([160, 160, 160, 255]), // Lighter gray
135 }
136 }
137}
138
139use bytemuck::{Pod, Zeroable};
140use etagere::euclid::{Size2D, UnknownUnit};
141use etagere::{size2, Allocation, BucketedAtlasAllocator};
142use lru::LruCache;
143use rustc_hash::FxHasher;
144use swash::zeno::{Format, Vector};
145
146use wgpu::*;
147
148use image::{GrayImage, Luma, Rgba, RgbaImage};
149use parley::{
150 Glyph, GlyphRun,
151 Layout, PositionedLayoutItem,
152};
153use std::borrow::Cow;
154use std::hash::BuildHasherDefault;
155use std::mem;
156use std::num::NonZeroU64;
157use swash::scale::image::{Content, Image};
158use swash::scale::{Render, ScaleContext, Scaler, Source, StrikeWith};
159use swash::{FontRef, GlyphId};
160use wgpu::{
161 MultisampleState, Texture, TextureFormat,
162 TextureUsages, TextureViewDescriptor,
163};
164use swash::zeno::Placement;
165
166pub use parley::{FontWeight, FontStyle, LineHeight, FontStack, Alignment, AlignmentOptions, OverflowWrap};