GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
deps: depend on cosmic-text directly, dropping glyphon and wgpu
glyphon is a wgpu text renderer. The wgpu path was retired in favour of raw
Vulkan (src/vk/), so none of glyphon's own API was reachable any more — the
five TextRenderer/TextArea mentions left in the tree are doc comments
describing what the Vulkan path mirrors. Every `glyphon::` item actually used
here (FontSystem, Buffer, Attrs, Metrics, Family, Weight, Style, Shaping,
Color, SwashCache, cosmic_text::Align, fontdb::Database) was a cosmic-text
re-export, so the dependency is now cosmic-text directly.
The one exception was `glyphon::TextBounds`, glyphon's own type, used solely
as an intermediate on the way to `TextSpan::bounds`; it is now a plain
four-i32 struct in backend/window_runner.rs.
cosmic-text is pinned to =0.12.1, the exact version glyphon 0.8 resolved to,
so shaping cannot drift. Verified unchanged three ways:
- the resolved feature set of the whole shaping stack (cosmic-text, fontdb,
rustybuzz, swash, ttf-parser) is byte-identical either way;
- shaping the same strings via glyphon's re-export and via cosmic-text
directly gives identical glyph ids, subpixel advances and line widths
over 288 runs (4 sizes x 4 families x 3 weight/style combos);
- cce-files rendered pixel-identical before and after over 4.26M pixels.
Also re-exports cosmic_text from the crate root, so clients need no text
dependency of their own and cannot drift onto a second FontSystem type.
Drops glyphon, wgpu, wgpu-core, wgpu-hal, wgpu-types, metal, gpu-alloc,
gpu-descriptor, khronos-egl and 10 more from the build (19 crates). The rest
of wgpu's tree stays only because naga/ash are direct dependencies here.
Clean build of this crate's gpu+text dependency set: 24.9s -> 18.0s wall,
5m05s -> 3m26s CPU.
Co-Authored-By: Claude <[email protected]>
CLAUDE.md | 12 ++++---
Cargo.toml | 11 +++++--
src/backend/window_runner.rs | 67 +++++++++++++++++++++++-----------------
src/lib.rs | 23 +++++++++-----
src/main.rs | 2 +-
src/scene/layout.rs | 2 +-
src/scene/paint.rs | 8 ++---
src/vk/mod.rs | 4 +--
src/vk/renderer.rs | 6 ++--
src/vk/text.rs | 22 ++++++-------
src/widget/container/menu.rs | 2 +-
src/widget/container/treelist.rs | 2 +-
src/widget/display/label.rs | 12 +++----
src/widget/display/list_item.rs | 10 +++---
src/widget/display/status_bar.rs | 8 ++---
src/widget/display/text_label.rs | 2 +-
src/widget/input/text_box.rs | 2 +-
src/widget/mod.rs | 2 +-
src/widget/model.rs | 4 +--
19 files changed, 114 insertions(+), 87 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index dcf3563..8c17b46 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -17,8 +17,11 @@ set outright.
- **Transport**: raw `wayland-client` 0.31 + `smithay-client-toolkit` 0.19, driven by a `calloop`
event loop. Clients are real Wayland surfaces (xdg toplevels, xdg popups, and `wlr-layer-shell`
layer surfaces), not toolkit-owned windows.
-- **Rendering**: **wgpu 24** for all geometry through a single `src/shader.wgsl` pipeline, and
- **glyphon** for text. There is no HTML/DOM — the UI is GPU primitives (quads, rounded rects with
+- **Rendering**: raw Vulkan via **ash** (`src/vk/`, `VkRenderer`) for all geometry, and
+ **cosmic-text** + swash for text (shaped into a self-managed glyph atlas by `src/vk/text.rs`).
+ The wgpu path is retired; cosmic-text used to be reached through **glyphon**, which is gone
+ too — every `glyphon::` item used here was a cosmic-text re-export, and dropping it takes
+ wgpu out of the build. There is no HTML/DOM — the UI is GPU primitives (quads, rounded rects with
per-corner radii, vectors with caps, arcs, circles, bevels). Tessellators live in
`backend/window_runner.rs` and are re-exported through `src/engine.rs`.
- It is **both a library and a binary.** `src/lib.rs` is the toolkit; `src/main.rs` is
@@ -125,7 +128,7 @@ cce-system-interface) to confirm behavior, not just the test suite.
## Module map (where things live)
- `layout.rs` (largest file, ~5.7k lines) — fonts + sizing; many `*_font_parsed()` getters and
- the `read_preferred_fonts` / font-family resolution used by the glyphon text path.
+ the `read_preferred_fonts` / font-family resolution used by the cosmic-text path.
- `color.rs` — color model and named colors (`colors` re-export module in `lib.rs`).
- `config.rs` — KDL loading and `kdl_to_json` conversion (see workspace `CLAUDE.md` for paths).
- `context.rs` — `UiContext`: the retained widget tree, event routing, spatial grid, dirty
@@ -143,7 +146,8 @@ cce-system-interface) to confirm behavior, not just the test suite.
## Fonts & assets
-`lib.rs` builds the glyphon `FontSystem`. Bundled fonts load from `$CCE_FONTS_DIR` (else
+`lib.rs` builds the cosmic-text `FontSystem` (re-exported as `cce_ui::cosmic_text` so clients
+need no text dependency of their own). Bundled fonts load from `$CCE_FONTS_DIR` (else
`~/Dropbox/Fonts`); bundled icons from `$CCE_ICONS_DIR` (else `~/Dropbox/cce/cce-icons/svg`).
System fonts are loaded only when `$CCE_LOAD_SYSTEM_FONTS` is set (or via
`create_font_system_with_system_fonts()`, used by the font picker). Configured custom font
diff --git a/Cargo.toml b/Cargo.toml
index b81e0c6..3c81270 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,8 +11,15 @@ path = "src/lib.rs"
raw-window-handle = "0.6"
glam = "0.29"
bytemuck = { version = "1", features = ["derive"] }
-glyphon = "0.8"
-# Raw-Vulkan backend (versions match what wgpu 24 already pulls in).
+# Text shaping. This was reached through glyphon until the wgpu path was retired;
+# every `glyphon::` item cce-ui ever used was a cosmic-text re-export (the sole
+# exception, `TextBounds`, is now defined in backend/window_runner.rs), so depending
+# on cosmic-text directly drops glyphon, wgpu and 17 more crates from the build —
+# the rest of wgpu's tree stays only because naga/ash below already pull it.
+# Pinned to the exact version glyphon 0.8 resolved to, so shaping does not change:
+# verified by diffing shaped glyph ids/advances both ways over 288 runs.
+cosmic-text = "=0.12.1"
+# Raw-Vulkan backend (versions match what wgpu 24 already pulled in).
ash = "0.38"
gpu-allocator = { version = "0.27", default-features = false, features = ["vulkan"] }
naga = { version = "24", features = ["wgsl-in", "spv-out"] }
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 0fcb0f0..d60695b 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -35,10 +35,7 @@ pub use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_to
pub use smithay_client_toolkit::seat::pointer::CursorIcon as PointerCursorIcon;
use calloop::EventLoop;
use calloop_wayland_source::WaylandSource;
-use glyphon::{
- FontSystem,
- TextBounds, Buffer, Attrs, Metrics,
-};
+use cosmic_text::{FontSystem, Buffer, Attrs, Metrics};
use crate::widget::{WidgetHost, TextItem, MouseButton, ElementState, MouseScrollDelta, KeyEvent, Key, NamedKey, Position};
use crate::wayland::detect_scale_factor;
use crate::vk::{Batch2D, Frame2D, TextSpan, VkRenderer};
@@ -153,57 +150,57 @@ pub fn get_text_buffer_attrs(
"monospace" => {
if !mono_fallback.is_empty() {
if let Some(ref cased) = resolved_storage {
- glyphon::Family::Name(cased)
+ cosmic_text::Family::Name(cased)
} else {
- glyphon::Family::Name(crate::layout::get_system_monospace_font())
+ cosmic_text::Family::Name(crate::layout::get_system_monospace_font())
}
} else {
- glyphon::Family::Name(crate::layout::get_system_monospace_font())
+ cosmic_text::Family::Name(crate::layout::get_system_monospace_font())
}
}
"sans-serif" => {
if !sans_fallback.is_empty() {
if let Some(ref cased) = resolved_storage {
- glyphon::Family::Name(cased)
+ cosmic_text::Family::Name(cased)
} else {
- glyphon::Family::SansSerif
+ cosmic_text::Family::SansSerif
}
} else {
- glyphon::Family::SansSerif
+ cosmic_text::Family::SansSerif
}
}
"serif" => {
if !serif_fallback.is_empty() {
if let Some(ref cased) = resolved_storage {
- glyphon::Family::Name(cased)
+ cosmic_text::Family::Name(cased)
} else {
- glyphon::Family::Serif
+ cosmic_text::Family::Serif
}
} else {
- glyphon::Family::Serif
+ cosmic_text::Family::Serif
}
}
- name => glyphon::Family::Name(name),
+ name => cosmic_text::Family::Name(name),
}
} else {
if !sans_fallback.is_empty() {
if let Some(ref cased) = resolved_sans {
- glyphon::Family::Name(cased)
+ cosmic_text::Family::Name(cased)
} else {
- glyphon::Family::SansSerif
+ cosmic_text::Family::SansSerif
}
} else {
- glyphon::Family::SansSerif
+ cosmic_text::Family::SansSerif
}
};
attrs = attrs.family(family);
if text_attrs.italic {
- attrs = attrs.style(glyphon::Style::Italic);
+ attrs = attrs.style(cosmic_text::Style::Italic);
}
if let Some(w) = text_attrs.weight {
- attrs = attrs.weight(glyphon::Weight(w));
+ attrs = attrs.weight(cosmic_text::Weight(w));
}
- buf.set_text(fs, text, attrs, glyphon::Shaping::Advanced);
+ buf.set_text(fs, text, attrs, cosmic_text::Shaping::Advanced);
buf.shape_until_scroll(fs, true);
BUFFER_CACHE.with(|cache| {
@@ -260,9 +257,9 @@ pub fn get_text_buffer_laid_out(
buf.set_size(fs, layout.wrap_width.map(|w| w * scale), Some(layout.box_height * scale));
let align = match layout.align_h {
- AlignH::Left => glyphon::cosmic_text::Align::Left,
- AlignH::Center => glyphon::cosmic_text::Align::Center,
- AlignH::Right => glyphon::cosmic_text::Align::Right,
+ AlignH::Left => cosmic_text::Align::Left,
+ AlignH::Center => cosmic_text::Align::Center,
+ AlignH::Right => cosmic_text::Align::Right,
};
for line in &mut buf.lines {
line.set_align(Some(align));
@@ -280,6 +277,18 @@ pub fn get_text_buffer_laid_out(
(buf, voff)
}
+/// A text item's clip rect in physical pixels. This was `glyphon::TextBounds` — the one
+/// glyphon-owned type cce-ui ever used, everything else being a cosmic-text re-export — so
+/// it is defined here now that the dependency is cosmic-text directly. Same plain
+/// four-`i32` layout; it is only an intermediate on the way to `TextSpan::bounds`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct TextBounds {
+ pub left: i32,
+ pub top: i32,
+ pub right: i32,
+ pub bottom: i32,
+}
+
/// The popover-occlusion clamp shared by the default [`Application::text_areas`] mapping and
/// the display-list text path: clip a text item's bounds so it does not bleed through an open
/// popover's plate. A text item whose own bounds coincide with a popover rect IS that popover's
@@ -2576,7 +2585,7 @@ pub trait Application: Sized + 'static {
None
}
- /// Opt in to render the display list's `Prim::Text` items through the glyphon pass
+ /// Opt in to render the display list's `Prim::Text` items through the glyph pass
/// (shaped via the shared buffer cache, clipped to the item clip ∩ the prim bounds). An
/// app's ENTIRE frame — geometry and text — is then one
/// [`display_list`](Application::display_list). Default `false` draws no text (an app that
@@ -2723,7 +2732,7 @@ pub struct EngineState<A: Application> {
pub renderer: Option<VkRenderer>,
pub font_system: Option<FontSystem>,
- pub swash_cache: glyphon::SwashCache,
+ pub swash_cache: cosmic_text::SwashCache,
pub scale_factor: f64,
/// The buffer scale last sent to the surface. Updated in [`Self::render`],
@@ -2797,7 +2806,7 @@ pub struct EngineState<A: Application> {
/// runs the off-screen hover-clear (which would otherwise corrupt the
/// drag: a ramp key snapped to the graph corner).
pub buttons_down: u32,
- /// This frame's display-list text, shaped and held here so the glyphon `TextArea`s built
+ /// This frame's display-list text, shaped and held here so the `TextSpan`s built
/// in the render pass can borrow the buffers (Phase 6 —
/// [`Application::display_list_text`]).
pub dl_text_items: Vec<TextItem>,
@@ -2985,7 +2994,7 @@ impl<A: Application> EngineState<A> {
.unwrap_or_else(|| crate::scene::paint::PaintCtx::new().finish());
// 1a. Phase 6 display-list text: shape the list's Text prims through the shared buffer
- // cache and hold them for the glyphon pass (the TextAreas built below borrow these).
+ // cache and hold them for the glyph pass (the TextSpans built below borrow these).
// Clip = the paint walk's item clip ∩ the prim's own bounds, in logical space.
self.dl_text_items.clear();
if self.inner.as_ref().unwrap().display_list_text() {
@@ -3008,7 +3017,7 @@ impl<A: Application> EngineState<A> {
buffer,
x: *x,
y: *y + y_off,
- color: glyphon::Color::rgba(
+ color: cosmic_text::Color::rgba(
color[0],
color[1],
color[2],
@@ -4312,7 +4321,7 @@ fn run_session<'l, A: Application>(
inner: None,
renderer: None,
font_system: None,
- swash_cache: glyphon::SwashCache::new(),
+ swash_cache: cosmic_text::SwashCache::new(),
scale_factor: 1.0,
committed_buffer_scale: 1,
entered_outputs: Vec::new(),
diff --git a/src/lib.rs b/src/lib.rs
index 914dd6e..f9e6b54 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,6 +20,13 @@ pub mod colors {
pub use crate::color::*;
}
+/// The text-shaping library, re-exported so clients need no text dependency of
+/// their own: `cce_ui::cosmic_text::FontSystem` rather than a per-crate
+/// `cosmic-text` (previously `glyphon`) entry in every client's Cargo.toml.
+/// Re-exporting also keeps every client on the one version cce-ui shapes with —
+/// a `FontSystem` handed across the boundary must be the same type.
+pub use cosmic_text;
+
pub static IS_VERTICAL: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub static BAR_THICKNESS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(24);
@@ -109,17 +116,17 @@ pub fn rasterize_svg(data: &[u8], px: u32) -> Option<(Vec<u8>, u32, u32)> {
Some((rgba, w, h))
}
-/// Build a glyphon `FontSystem` loaded with the bundled CCE fonts (house style).
+/// Build a cosmic-text `FontSystem` loaded with the bundled CCE fonts (house style).
/// System fonts are loaded only if `$CCE_LOAD_SYSTEM_FONTS` is set. Configured
/// custom fonts are validated with a warning if missing.
-pub fn create_font_system() -> glyphon::FontSystem {
+pub fn create_font_system() -> cosmic_text::FontSystem {
build_font_system(false)
}
/// Like [`create_font_system`] but always also loads installed system fonts, for
/// apps that must see every font on the system (e.g. the font picker) or want
/// them as fallbacks. Additive — bundled CCE fonts are still loaded.
-pub fn create_font_system_with_system_fonts() -> glyphon::FontSystem {
+pub fn create_font_system_with_system_fonts() -> cosmic_text::FontSystem {
build_font_system(true)
}
@@ -129,7 +136,7 @@ pub fn create_font_system_with_system_fonts() -> glyphon::FontSystem {
/// list of well-known files keeps startup cheap while giving cosmic-text's
/// unix script fallback (family names "Noto Sans CJK *", "Noto Color Emoji")
/// real faces to land on. `$CCE_NO_FALLBACK_FONTS` opts out.
-fn load_fallback_fonts(db: &mut glyphon::cosmic_text::fontdb::Database) {
+fn load_fallback_fonts(db: &mut cosmic_text::fontdb::Database) {
if std::env::var("CCE_NO_FALLBACK_FONTS").is_ok() {
return;
}
@@ -152,8 +159,8 @@ fn load_fallback_fonts(db: &mut glyphon::cosmic_text::fontdb::Database) {
}
}
-fn build_font_system(load_system_fonts: bool) -> glyphon::FontSystem {
- let mut db = glyphon::cosmic_text::fontdb::Database::new();
+fn build_font_system(load_system_fonts: bool) -> cosmic_text::FontSystem {
+ let mut db = cosmic_text::fontdb::Database::new();
db.load_fonts_dir(fonts_dir());
load_fallback_fonts(&mut db);
if load_system_fonts || std::env::var("CCE_LOAD_SYSTEM_FONTS").is_ok() {
@@ -174,7 +181,7 @@ fn build_font_system(load_system_fonts: bool) -> glyphon::FontSystem {
// high (cosmic-text common_fallback) and hijacked spaces and digits with
// emoji metrics. Berkeley Mono is the house mono; Noto Sans CJK SC (the
// targeted fallback face above) doubles as a full Latin sans.
- fn has_family(db: &glyphon::cosmic_text::fontdb::Database, fam: &str) -> bool {
+ fn has_family(db: &cosmic_text::fontdb::Database, fam: &str) -> bool {
db.faces()
.any(|f| f.families.iter().any(|(n, _)| n == fam))
}
@@ -222,7 +229,7 @@ fn build_font_system(load_system_fonts: bool) -> glyphon::FontSystem {
}
}
- glyphon::FontSystem::new_with_locale_and_db("en-US".to_string(), db)
+ cosmic_text::FontSystem::new_with_locale_and_db("en-US".to_string(), db)
}
diff --git a/src/main.rs b/src/main.rs
index f0fd643..949fdc2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,7 +6,7 @@
//! 1. **One paint path.** The whole frame — geometry AND text — is built in
//! [`Application::display_list`] as prims on a [`PaintCtx`], with
//! [`Application::display_list_text`] returning `true`. There is no `view*`/`text_items`
-//! pair, no app-side `FontSystem`, no glyphon buffers: text is a `Prim::Text` shaped by
+//! pair, no app-side `FontSystem`, no cosmic-text buffers: text is a `Prim::Text` shaped by
//! the engine's shared cache.
//! 2. **Layout via the scene solver.** The frame is a plain `Arena<LayoutBox>` tree the
//! app builds and solves with [`compute_layout`]; widgets get their rects from the
diff --git a/src/scene/layout.rs b/src/scene/layout.rs
index d496d9d..84e1136 100644
--- a/src/scene/layout.rs
+++ b/src/scene/layout.rs
@@ -20,7 +20,7 @@
//! Layout modes ([`LayoutMode`]): **Flex** (row/column with grow/shrink, gap, padding, main/cross
//! alignment incl. stretch), **Stack** (Z-overlay with per-axis alignment), and **Grid** (fixed
//! column count with uniform column width and per-row heights). Deferred to later increments:
-//! wrapping, percentage lengths, width-dependent adaptive grids, and the glyphon text-measure
+//! wrapping, percentage lengths, width-dependent adaptive grids, and the cosmic-text text-measure
//! hook for real leaf widgets (that lands with Phase 2b integration).
use crate::scene::arena::{Arena, NodeId};
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index c814b0e..079ff50 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -114,7 +114,7 @@ pub enum Prim {
ConcaveFillet { cx: f32, cy: f32, radius: f32, depth: f32, start: f32, raised: bool },
/// Text in sRGB u8 (the `TextLabel` convention). `font` is a font string for
/// `get_text_buffer` (family, or "family:size"); `bounds` is a logical `[l, t, r, b]` clip
- /// for the glyph pass (Phase 6: the backend renders these through glyphon when the app
+ /// for the glyph pass (Phase 6: the backend renders these through the glyph pass when the app
/// opts in via `Application::display_list_text`; the paint walk's clip additionally
/// applies through the item's `clip`). `attrs` carries the optional shaping attributes
/// beyond family+size (the font picker's italic/weight preview variants). `layout`, when
@@ -130,7 +130,7 @@ pub enum Prim {
}
/// Horizontal alignment of laid-out (boxed) text — the toolkit-plain mirror of
-/// `glyphon::cosmic_text::Align`, mapped at shape time.
+/// `cosmic_text::Align`, mapped at shape time.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum AlignH {
#[default]
@@ -162,8 +162,8 @@ pub struct TextLayout {
/// Optional shaping attributes for a [`Prim::Text`] — the subset a widget can request beyond
/// family + size. `weight` is the OpenType weight (400 regular, 700 bold); `None` leaves the
-/// family default. Kept toolkit-plain (no glyphon types) like the rest of the scene layer;
-/// the backend maps them onto `glyphon::Style`/`Weight` at shape time.
+/// family default. Kept toolkit-plain (no cosmic-text types) like the rest of the scene layer;
+/// the backend maps them onto `cosmic_text::Style`/`Weight` at shape time.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct TextAttrs {
pub italic: bool,
diff --git a/src/vk/mod.rs b/src/vk/mod.rs
index 698a1ee..a40a82f 100644
--- a/src/vk/mod.rs
+++ b/src/vk/mod.rs
@@ -11,8 +11,8 @@
//! quads with circle clipping, the wavy-blob sentinel, window-corner
//! rounding (radius 0 disables), and blur-behind plates (negative alpha)
//! sampling the renderer-managed backdrop.
-//! - **Text** (`glyph.wgsl` + [`TextSpan`]): cosmic-text shaping (via glyphon's
-//! re-export) + swash rasterization into a self-managed glyph atlas, with
+//! - **Text** (`glyph.wgsl` + [`TextSpan`]): cosmic-text shaping (a direct dependency since the
+//! wgpu path retired) + swash rasterization into a self-managed glyph atlas, with
//! per-span bounds clipping, rotation, and circle clipping.
//! - **3D** (`scene3d.wgsl` + [`SceneDraw`]): handle-based [`Vertex3D`] meshes
//! with a depth buffer, rendered into the full-size backdrop image
diff --git a/src/vk/renderer.rs b/src/vk/renderer.rs
index a7a9423..5c5c9b6 100644
--- a/src/vk/renderer.rs
+++ b/src/vk/renderer.rs
@@ -1422,11 +1422,11 @@ impl VkRenderer {
/// Stage text for the next `draw_frame`: shape-cache misses are rasterized
/// into the glyph atlas and vertices are built against the current extent.
- /// Mirrors `glyphon::TextRenderer::prepare`.
+ /// Mirrors what was `glyphon::TextRenderer::prepare`.
pub fn prepare_text(
&mut self,
- font_system: &mut glyphon::FontSystem,
- swash_cache: &mut glyphon::SwashCache,
+ font_system: &mut cosmic_text::FontSystem,
+ swash_cache: &mut cosmic_text::SwashCache,
spans: &[TextSpan<'_>],
) {
// Against the extent this frame will actually be drawn at: `resize` is
diff --git a/src/vk/text.rs b/src/vk/text.rs
index 24af1ca..2210cb9 100644
--- a/src/vk/text.rs
+++ b/src/vk/text.rs
@@ -1,11 +1,11 @@
-//! Text on ash: cosmic-text shaping (reached through glyphon's re-export, so the
-//! shaping behavior and fonts are byte-identical to the wgpu path) + swash
-//! rasterization into a self-managed RGBA glyph atlas, drawn by the glyph.wgsl
-//! pipeline inside the renderer's render pass.
+//! Text on ash: cosmic-text shaping + swash rasterization into a self-managed
+//! RGBA glyph atlas, drawn by the glyph.wgsl pipeline inside the renderer's
+//! render pass. (cosmic-text used to be reached through glyphon's re-export;
+//! the dependency is direct now that the wgpu path is gone, pinned to the same
+//! version, so shaping behavior and fonts are unchanged.)
//!
-//! `TextSpan` mirrors `glyphon::TextArea` (buffer + position + scale + bounds +
-//! default color) so the eventual cutover from `text_renderer.prepare(...)` is
-//! mechanical.
+//! `TextSpan` mirrors what was `glyphon::TextArea` (buffer + position + scale +
+//! bounds + default color) — the shape the wgpu-era cutover was written against.
//!
//! Atlas strategy: shelf packing into a 1024² RGBA8 image with a CPU mirror.
//! When new glyphs land, the whole mirror is re-uploaded before the next render
@@ -22,8 +22,8 @@ use gpu_allocator::vulkan::{
};
use gpu_allocator::MemoryLocation;
-use glyphon::cosmic_text::{Buffer as TextBuffer, CacheKey, SwashContent};
-use glyphon::{FontSystem, SwashCache};
+use cosmic_text::{Buffer as TextBuffer, CacheKey, SwashContent};
+use cosmic_text::{FontSystem, SwashCache};
use super::renderer::{create_cpu_buffer, destroy_cpu_buffer, AllocatedBuffer};
@@ -32,7 +32,7 @@ const ATLAS_PAD: u32 = 1;
/// One shaped text run to draw. `left`/`top` are physical pixels and `scale`
/// multiplies the shaped (logical) glyph positions — the same contract as
-/// glyphon::TextArea, where callers pass `label.x * scale`.
+/// the old glyphon::TextArea, where callers pass `label.x * scale`.
pub struct TextSpan<'a> {
pub buffer: &'a TextBuffer,
pub left: f32,
@@ -543,7 +543,7 @@ impl TextStage {
continue;
}
- // glyphon's placement formula, physical pixels.
+ // glyphon's placement formula (kept verbatim), physical pixels.
let mut x0 = (physical.x + entry.left) as f32;
let mut y0 = (line_y + physical.y - entry.top) as f32;
let mut x1 = x0 + entry.w as f32;
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 15c4390..3154a1b 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -12,7 +12,7 @@
//! zero constructors workspace-wide (dead code).
//!
//! Dropped with the migration: the `title_buf`/`curved_title_char_bufs`/`context_item_bufs`
-//! glyphon caches — `get_text_items` always returned empty, so `prepare_text` built buffers
+//! cosmic-text caches — `get_text_items` always returned empty, so `prepare_text` built buffers
//! nothing ever read (an abandoned optimization). Also gone: the legacy `rect()` override's
//! vertical-mode dynamic height (`with_vertical` has no callers workspace-wide; the vertical
//! label/title geometry is kept for the strip's rotated mode, but the widget rect is the
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index d87eb3a..3462103 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -716,7 +716,7 @@ impl Paint for TreeList {
true
}
- fn prepare_text(&mut self, fs: &mut glyphon::FontSystem, _rect: Rect) {
+ fn prepare_text(&mut self, fs: &mut cosmic_text::FontSystem, _rect: Rect) {
self.search_box.prepare_text(fs);
self.add_key_btn.prepare_text(fs);
if self.add_key_popover_open {
diff --git a/src/widget/display/label.rs b/src/widget/display/label.rs
index 50ee8ae..80a64f9 100644
--- a/src/widget/display/label.rs
+++ b/src/widget/display/label.rs
@@ -117,10 +117,10 @@ mod tests {
// Styled label builder with optional strikethrough
#[derive(Debug)]
pub struct StyledLabel {
- pub buffer: glyphon::Buffer,
+ pub buffer: cosmic_text::Buffer,
pub w: f32,
pub color: [f32; 4],
- pub g_color: glyphon::Color,
+ pub g_color: cosmic_text::Color,
pub strikethrough: bool,
pub strikethrough_color: Option<[f32; 4]>,
// Source retained so the label can be re-emitted as a display-list Text prim (Phase 6ak):
@@ -146,11 +146,11 @@ pub struct LabelPrim {
}
impl StyledLabel {
- pub fn new(fs: &mut glyphon::FontSystem, text: &str, size: f32, color: [f32; 4]) -> Self {
+ pub fn new(fs: &mut cosmic_text::FontSystem, text: &str, size: f32, color: [f32; 4]) -> Self {
Self::new_with_family(fs, text, size, color, "sans-serif")
}
- pub fn new_with_family(fs: &mut glyphon::FontSystem, text: &str, size: f32, color: [f32; 4], family: &str) -> Self {
+ pub fn new_with_family(fs: &mut cosmic_text::FontSystem, text: &str, size: f32, color: [f32; 4], family: &str) -> Self {
let scale = crate::scale::scale_factor();
let mut final_text = text.to_string();
let is_vert = crate::IS_VERTICAL.load(std::sync::atomic::Ordering::Relaxed);
@@ -162,7 +162,7 @@ impl StyledLabel {
let bar_thickness = crate::BAR_THICKNESS.load(std::sync::atomic::Ordering::Relaxed) as f32;
buffer.set_size(fs, Some(bar_thickness * scale as f32), None);
for line in &mut buffer.lines {
- line.set_align(Some(glyphon::cosmic_text::Align::Center));
+ line.set_align(Some(cosmic_text::Align::Center));
}
buffer.shape_until_scroll(fs, true);
}
@@ -171,7 +171,7 @@ impl StyledLabel {
let num_lines = buffer.layout_runs().count();
w = num_lines as f32 * size * 1.05;
}
- let g_color = glyphon::Color::rgb(
+ let g_color = cosmic_text::Color::rgb(
(color[0] * 255.0) as u8,
(color[1] * 255.0) as u8,
(color[2] * 255.0) as u8,
diff --git a/src/widget/display/list_item.rs b/src/widget/display/list_item.rs
index 4164287..8ae0ba6 100644
--- a/src/widget/display/list_item.rs
+++ b/src/widget/display/list_item.rs
@@ -1,4 +1,4 @@
-//! `TextItem` (retained glyphon buffer holder, unchanged) and the narrow-trait
+//! `TextItem` (retained cosmic-text buffer holder, unchanged) and the narrow-trait
//! `InteractiveListItem` (Phase 5j): Button-style press/release with themed
//! selected/hover/press overlays and title/subtitle text.
@@ -9,10 +9,10 @@ use crate::widget::{Adapted, ElementState, Event, EventCtx, Input, Layout, Mouse
#[derive(Debug, Clone)]
pub struct TextItem {
- pub buffer: glyphon::Buffer,
+ pub buffer: cosmic_text::Buffer,
pub x: f32,
pub y: f32,
- pub color: glyphon::Color,
+ pub color: cosmic_text::Color,
pub bounds: Option<[f32; 4]>,
/// Optional circular clip `[cx, cy, r]` in logical pixels (a display-list item's
/// `clip_circle` carried through to the glyph pass). `None` for ordinary labels.
@@ -24,12 +24,12 @@ pub struct TextItem {
impl TextItem {
pub fn new(
- fs: &mut glyphon::FontSystem,
+ fs: &mut cosmic_text::FontSystem,
text: &str,
size: f32,
x: f32,
y: f32,
- color: glyphon::Color,
+ color: cosmic_text::Color,
font: Option<&str>,
bounds: Option<[f32; 4]>,
) -> Self {
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index 7b7f43b..bb86254 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -3,7 +3,7 @@
//! statusbar color/text-color/blur and derives its rounded corners from where it sits against
//! the parent's edges ([`Paint::corner_style`] + the corners walk). Two text paths: the
//! [`Paint::paint`] prim (carrying the configured statusbar font — the legacy default-font
-//! behavior on this path dropped the family and rendered sans), and pre-shaped glyphon
+//! behavior on this path dropped the family and rendered sans), and pre-shaped cosmic-text
//! buffers through [`Paint::text_items`] (new with this migration) for manual hosts —
//! cce-status-interface calls `prepare_text` then `get_text_items` into its own paint.
@@ -16,7 +16,7 @@ use crate::widget::{Adapted, Input, Layout, Paint};
pub struct StatusBar {
rect: Rect,
pub text: String,
- pub text_buf: Option<glyphon::Buffer>,
+ pub text_buf: Option<cosmic_text::Buffer>,
pub text_offset_x: Option<f32>,
pub text_color: Option<[f32; 4]>,
pub bg_color: Option<[f32; 4]>,
@@ -178,7 +178,7 @@ impl Paint for StatusBar {
Some(crate::layout::statusbar_font())
}
- fn prepare_text(&mut self, fs: &mut glyphon::FontSystem, _rect: Rect) {
+ fn prepare_text(&mut self, fs: &mut cosmic_text::FontSystem, _rect: Rect) {
if !self.text.is_empty() && self.text_buf.is_none() {
let (font_fam, font_size) = crate::layout::statusbar_font_parsed();
let size = if font_size > 0.0 { font_size } else { 12.0 };
@@ -205,7 +205,7 @@ mod tests {
/// deleted; the bar's rendered text is the `Paint::paint` prim.)
#[test]
fn manual_host_text_pipeline() {
- let mut fs = glyphon::FontSystem::new();
+ let mut fs = cosmic_text::FontSystem::new();
// Pin the flat style: the bg-quad bridge under test is skipped by the
// config-default recessed band.
let mut bar = StatusBar::new().with_text("hello").with_text_offset_x(15.0).with_recess(false);
diff --git a/src/widget/display/text_label.rs b/src/widget/display/text_label.rs
index 8d60474..245c18d 100644
--- a/src/widget/display/text_label.rs
+++ b/src/widget/display/text_label.rs
@@ -74,6 +74,6 @@ impl TextLabel {
}
}
-pub(crate) fn make_widget_text_buffer(fs: &mut glyphon::FontSystem, text: &str, size: f32, font_family: &str) -> glyphon::Buffer {
+pub(crate) fn make_widget_text_buffer(fs: &mut cosmic_text::FontSystem, text: &str, size: f32, font_family: &str) -> cosmic_text::Buffer {
crate::backend::window_runner::get_text_buffer(fs, text, size, Some(font_family))
}
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 65f5e25..1de0af0 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1160,7 +1160,7 @@ impl Paint for TextBox {
/// The legacy `prepare_text`: sync font family/size with the live config defaults, then
/// shape the display text and record per-glyph advances (`map_x_to_idx` reads them).
- fn prepare_text(&mut self, fs: &mut glyphon::FontSystem, _rect: Rect) {
+ fn prepare_text(&mut self, fs: &mut cosmic_text::FontSystem, _rect: Rect) {
let (style_family, style_size) = crate::layout::control_label_font_detached_parsed();
if self.font_size == self.default_font_size {
self.font_size = style_size;
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index be32ca0..aa5a99c 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -478,7 +478,7 @@ pub trait WidgetHost {
fn focused(&self, ctx: &UiContext) -> bool {
ctx.is_focused_id(self.base().id())
}
- fn prepare_text(&mut self, _fs: &mut glyphon::FontSystem) {}
+ fn prepare_text(&mut self, _fs: &mut cosmic_text::FontSystem) {}
fn set_visible(&mut self, _visible: bool) {}
fn visible(&self) -> bool { true }
diff --git a/src/widget/model.rs b/src/widget/model.rs
index e057284..3cdf1fb 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -252,7 +252,7 @@ pub trait Paint {
/// overrides). TextBox measures its glyph advances here — load-bearing for cursor↔pixel
/// mapping, not just a render cache. Receives the laid-out content rect. Default: nothing
/// to shape.
- fn prepare_text(&mut self, _fs: &mut glyphon::FontSystem, _rect: Rect) {}
+ fn prepare_text(&mut self, _fs: &mut cosmic_text::FontSystem, _rect: Rect) {}
/// Whether [`paint`](Paint::paint) emits the widget's ENTIRE subtree, so the paint walk
/// must not also descend into its (ctx-linked) children — the legacy
@@ -1101,7 +1101,7 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
Layout::register_embedded_children(&mut self.inner, host_id, ctx);
}
- fn prepare_text(&mut self, fs: &mut glyphon::FontSystem) {
+ fn prepare_text(&mut self, fs: &mut cosmic_text::FontSystem) {
if self.visible() {
let rect = self.content_rect();
Paint::prepare_text(&mut self.inner, fs, rect);