Skip to main content

agg_gui/widgets/rich_text/
mod.rs

1//! `rich_text` — a styled rich-text document model, command engine, layout
2//! engine, and the widgets ([`RichTextView`], [`RichTextEdit`]) that render and
3//! edit it.  This is the toolkit for **embedding a real text editor in your
4//! agg-gui app**, not a fixed demo widget: you own the document, supply the
5//! fonts, and drive formatting through commands.
6//!
7//! # Module layout
8//!
9//! * [`model`] — the document types ([`RichDoc`], [`Block`], [`TextRun`],
10//!   [`InlineStyle`], [`ListKind`]), positions/ranges ([`DocPos`], [`DocRange`]),
11//!   and the structural edit primitives (insert / remove / split / merge /
12//!   normalize).
13//! * [`commands`] — [`RichCommand`] plus [`apply_command`], with toolbar-state
14//!   helpers ([`style_at`], [`range_common_style`], [`CommonStyle`]).
15//! * [`layout`] — width-constrained, per-run-font layout producing paint-ready
16//!   line/fragment geometry ([`DocLayout`]).  Catalog-agnostic: it asks a
17//!   *resolver* ([`SharedResolver`]) to map each run's style to a
18//!   [`Font`](crate::text::Font).
19//! * [`view`] — [`RichTextView`], a read-only display widget, plus the
20//!   [`single_font_resolver`] helper.
21//! * [`editor`] — [`RichTextEdit`], the interactive editor, and the cheap
22//!   [`RichEditHandle`] a formatting toolbar drives it through.
23//!
24//! # Embedding an editor
25//!
26//! Everything a functional editor needs is public here.  A resolver maps run
27//! styles to concrete fonts (see [`single_font_resolver`] for the full
28//! contract); [`RichTextEdit::handle`] hands a toolbar a cheap, cloneable driver
29//! that shares the very editor being rendered.
30//!
31//! ```
32//! use std::sync::Arc;
33//! use agg_gui::Font;
34//! use agg_gui::widgets::rich_text::{
35//!     single_font_resolver, Block, InlineStyle, RichCommand, RichDoc, RichTextEdit, TextRun,
36//! };
37//!
38//! // 1. Load a font. Any `Arc<Font>` works; here we read the crate's bundled
39//! //    face so this example is a real, runnable doc-test.
40//! let bytes = std::fs::read(concat!(
41//!     env!("CARGO_MANIFEST_DIR"),
42//!     "/assets/fonts/NotoSans-Regular.ttf",
43//! ))
44//! .expect("bundled font readable");
45//! let font = Arc::new(Font::from_slice(&bytes).expect("valid font"));
46//!
47//! // 2. Build a document from styled runs (or `RichDoc::new()` for a blank one).
48//! let bold = InlineStyle { bold: true, ..Default::default() };
49//! let doc = RichDoc::from_blocks(vec![Block {
50//!     runs: vec![
51//!         TextRun::new("Hello, ", InlineStyle::default()),
52//!         TextRun::new("world", bold),
53//!     ],
54//!     ..Block::new()
55//! }]);
56//!
57//! // 3. One font -> a working resolver -> a functional editor in one line each.
58//! let resolver = single_font_resolver(font);
59//! let mut editor = RichTextEdit::new(doc, resolver).with_font_size(18.0);
60//!
61//! // 4. A cheap handle drives the same editor from a toolbar (clone freely).
62//! let toolbar = editor.handle();
63//!
64//! // 5. Apply formatting commands (a real selection comes from mouse/keyboard
65//! //    input dispatched to `editor.on_event`; a collapsed caret arms the format
66//! //    for the next typed run).
67//! toolbar.exec(&RichCommand::ToggleItalic);
68//!
69//! // 6. Read toolbar state: what styles the current selection shares.
70//! let style = editor.common_style_of_selection();
71//! let _ = style.bold; // Option<bool>: Some(v) = all agree, None = mixed.
72//!
73//! // 7. Undo / redo flow through the shared core (also on the handle).
74//! if editor.can_undo() {
75//!     editor.undo();
76//! }
77//! toolbar.redo();
78//!
79//! assert!(editor.plain_text().starts_with("Hello"));
80//! ```
81//!
82//! To *display* a document without editing, use [`RichTextView`] the same way.
83//! For a multi-font resolver with real bold/italic faces backed by a system-font
84//! catalog, see `make_resolver` in `demo-ui/src/windows/rich_text_demo`.
85//!
86//! # Asynchronous font loading
87//!
88//! The editor caches its layout against `(width, document revision)` and does
89//! **not** observe an app's font catalog.  If your app loads faces
90//! asynchronously, call [`RichTextEdit::invalidate_layout`] when a newly-arrived
91//! font should be picked up (e.g. on each advance of your font-cache epoch), so
92//! the doc reshapes and the cached backbuffer re-rasters.
93
94pub mod commands;
95pub mod editor;
96pub mod layout;
97pub mod model;
98pub mod rich_clipboard;
99pub mod toolbar;
100pub mod view;
101
102pub use commands::{apply_command, range_common_style, style_at, CommonStyle, RichCommand, MAX_INDENT};
103pub use editor::{RichEditCore, RichEditHandle, RichTextEdit};
104pub use layout::{
105    layout_doc, BlockLayout, DocLayout, FontResolver, LineFragment, LineLayout, INDENT_PX,
106    LINE_SPACING, LIST_GUTTER_PX, MARKER_GAP_PX,
107};
108pub use model::{
109    insert_text, merge_block_with_prev, remove_range, split_block, Block, DocPos, DocRange,
110    InlineStyle, ListKind, RichDoc, TextRun,
111};
112pub use toolbar::{RichTextToolbar, Variant};
113pub use view::{single_font_resolver, uniform_resolver, RichTextView, SharedResolver};