Expand description
keru_text is an experimental high level text library, with the goal to allow any winit/wgpu program to have full-featured text and text editing with minimal integration effort.
§Usage
// Create the Text struct and the Text renderer:
let mut text = Text::new();
let text_renderer = TextRenderer::new(&device, &queue, surface_config.format);
// Text manages collections of text boxes and styles.
// TextRenderer holds the state needed to render the text on the gpu.
// Add text boxes and get handles:
let handle = text.add_text_box("Hello", (10.0, 10.0), (200.0, 50.0), 0.0);
let edit_handle = text.add_text_edit("Type here".to_string(), (10.0, 70.0), (200.0, 30.0), 0.0);
// Use handles to access and modify the boxes:
text.get_text_edit_mut(&edit_handle).raw_text_mut().push_str("... World");
// Manually remove text boxes when needed:
text.remove_text_box(handle);
// In winit's window_event callback, pass the event to Text:
text.handle_event(&event, &window);
// Do shaping, layout, rasterization, etc. to prepare all the text to be rendered:
text.prepare_all(&mut text_renderer);
// Load the data on the gpu:
text_renderer.load_to_gpu(&device, &queue);
// Render the text as part of a wgpu render pass:
text_renderer.render(&mut render_pass);See the minimal.rs or full.rs examples in the repository to see a more complete example, including the winit and wgpu boilerplate.
§Handles
The library is imperative with a handle-based system.
Creating a text box returns a handle that can be used to access it afterwards.
Handles can’t be Cloned or constructed manually, and removing a text box with Text::remove_text_box() consumes the handle. So a handle is a unique reference that can never be “dangling”.
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.
Text uses slotmaps internally, so get_text_box_mut() and all similar functions are basically as fast as an array lookup. There is no hashing involved.
§Advanced Usage
§Accessibility
This library supports accessibility, but integrating it requires a bit more coordination with winit and with the GUI code outside of this library. In particular, keru_text doesn’t have any concept of a tree. See the accessibility.rs example in the repository for a basic example.
§Interaction
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.
As great as this sounds, in some cases text boxes can be 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:
- Run
Text::find_topmost_text_box()to find out which text box would have received the event, if it wasn’t for other objects. - Run some custom code to find out which other object would have received the event, if it wasn’t for text boxes.
- Compare the depth of the two candidates. For the text box, use
Text::get_text_box_depth(). - If the text box is on top, call
Text::handle_event_with_topmost()withtopmost_text_box = Some(topmost_text_box), which will handle the event normally (but skip looking for the topmost box again). - If the text box, is occluded, call
Text::handle_event_with_topmost()withtopmost_text_box = None.
For any winit::WindowEvent other than a winit::WindowEvent::MouseInput or a winit::WindowEvent::MouseWheel, this process can be skipped, and you can just call Text::handle_event() normallyw.
The occlusion.rs example shows how this works.
§Declarative Visibility
There is an optional declarative interface for hiding text boxes:
// Each frame, advance an internal frame counter,
// and implicitly mark all text boxes as "outdated"
text.advance_frame_and_hide_boxes();
// "Refresh" only the nodes that should remain visible
for node in current_nodes {
text.refresh_text_box(&node.text_box_handle);
}
// Text boxes that were not refreshed will be remain hidden,
// and they will be skipped when rendering or handling events.This library was written for use in Keru, which 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.
§Advanced rendering
When using [TextRenderer::render()], all text boxes are rendered in a single draw call.
The TextRenderer supports using a depth buffer, so this is a perfectly good solution in many cases. However, it’s not enough to get correct results when many semitransparent elements overlap.
As far as I know, there’s no simple solution to this problem, but there are at least three complicated ones:
- Use a shader that can render both text glyphs and any other elements (rectangles, shapes, bezier paths, etc.), and render everything in one draw call. Then, the GPU will draw the elements in the order they appear in the buffer with perfect blending, all automatically.
- Use “batching”: do separate draw calls for the first contiguous range of text glyphs, then switch the pipeline and do another draw call for a range of other elements, etc.
The megashader.rs example shows how this library can be used as part of a render pipeline implementing the first strategy.
I tried my hardest to do this in an “extensible” way while keeping all text-related code as a separate module, but the results were limited due to the heavy limitations of the wgsl shading language. I will give it another try if I can ever get slang shaders to work.
“Regular” batching is currently not implemented.
§Open Issues
There is an open issue in the design of the library: the math for scrolling and smooth scrolling animations in overflowing text edit boxes is hardcoded in the library. This means that a GUI library using keru_text might have inconsistent scrolling behavior between the keru_text text edit boxes and the GUI library’s generic scrollable containers.
Re-exports§
pub use parley;pub use euclid;
Structs§
- Bounding
Box - A bounding box.
- BoxGpu
- Per-text-box data stored in a separate buffer and referenced by index.
- Cloned
Text BoxHandle - Cloneable handle for a text box.
- Cloned
Text Edit Handle - Cloneable handle for a text edit box.
- Color
Brush - RGBA color value for text rendering.
- Glyph
Quad - The struct corresponding to the gpu-side representation of a text glyph.
- Parley
Text Style - Unresolved styles.
- Render
Stats - Statistics about work done during a render cycle.
- Split
String - A string that may be split into two parts (used for IME composition).
- Style
Handle - Handle for a text style. Use with Text methods to apply styles to text.
- Text
- Centralized struct that holds collections of
TextBoxes,TextEdits,TextStyle2s. - TextBox
- A text box stored inside a
Textstruct. - Text
BoxHandle - Handle for a text box.
- Text
Edit - A text edit box.
- Text
Edit Handle - Handle for a text edit box.
- Text
Edit Style - Style configuration for text edit boxes.
- Text
Renderer Params - Configuration parameters for the text renderer.
- Transform2D
- Simple 2D transform with translation, rotation, and scale.
Enums§
- AnyBox
- A non-owning reference to either a
TextBoxor aTextEditBox. - Atlas
Page Size - Determines the size of texture atlas pages for glyph storage.
- Newline
Mode - Defines how newlines are entered in a text edit box.
Functions§
- with_
clipboard - Runs the given closure with mutable access to the thread-local
Clipboard.
Type Aliases§
- Text
Style2 - Text style.