Crate keru_text

Crate keru_text 

Source
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() with topmost_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() with topmost_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:

  1. 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.
  2. 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§

BoundingBox
A bounding box.
BoxGpu
Per-text-box data stored in a separate buffer and referenced by index.
ClonedTextBoxHandle
Cloneable handle for a text box.
ClonedTextEditHandle
Cloneable handle for a text edit box.
ColorBrush
RGBA color value for text rendering.
GlyphQuad
The struct corresponding to the gpu-side representation of a text glyph.
ParleyTextStyle
Unresolved styles.
RenderStats
Statistics about work done during a render cycle.
SplitString
A string that may be split into two parts (used for IME composition).
StyleHandle
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 Text struct.
TextBoxHandle
Handle for a text box.
TextEdit
A text edit box.
TextEditHandle
Handle for a text edit box.
TextEditStyle
Style configuration for text edit boxes.
TextRendererParams
Configuration parameters for the text renderer.
Transform2D
Simple 2D transform with translation, rotation, and scale.

Enums§

AnyBox
A non-owning reference to either a TextBox or a TextEditBox.
AtlasPageSize
Determines the size of texture atlas pages for glyph storage.
NewlineMode
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§

TextStyle2
Text style.