Replace the cosmic-text path with Parley layout and Swash rasterization, backed by shared glyph-atlas pages. Shaping, editing, rasterization, and glyph rendering move together because they share the text buffer and rendered-glyph types; splitting them further would require a temporary renderer that is immediately removed. This is reconstructed rather than replayed from the extraction history. It also fixes issues found during review: - texture binding changes remain set when an atlas patch follows a new page - pressing an empty field places a caret and accepts input - selection motion delegates collapse behavior to Parley - character deletion follows logical clusters rather than visual neighbors - the unused root-level Swash dependency is omitted Four public-behavior integration tests live in `tests/text_edit.rs`: empty-field input, multibyte IME preedit replacement, UTF-8-safe backspace, and selection replacement. The old twelve-test inline block and implementation-restating cases are omitted. Every added source comment was manually reviewed. Comments that narrated implementation or history were removed; retained comments document cache/rasterization keys, GPU upload constraints, focus representation, bidi geometry, or IME semantics. Known limitation: atlas pages currently grow without eviction. Each page is 4 MiB on CPU and GPU. An arbitrary cap would leave cached rendered-text UVs pointing at reused glyph slots, so bounding this safely needs a later generation/invalidation change. This changes public text types and signatures. GPU glyph rendering is covered by compilation rather than a live-surface test. Verified with: - `cargo fmt --all --check` - `cargo clippy --workspace --all-targets -- -D warnings` - `cargo test --workspace` (four integration tests pass) Cargo still reports inherited future-incompatibility notices for existing wgpu/winit dependencies; there are no current clippy warnings. --------- Co-authored-by: iris <2+iris@noreply.localhost> Reviewed-on: iris/iris#10 Reviewed-by: iris <2+iris@noreply.localhost> Co-authored-by: AIris <4+iris-ai@noreply.localhost>
172 lines
4.2 KiB
Rust
172 lines
4.2 KiB
Rust
mod build;
|
|
mod edit;
|
|
|
|
pub use build::*;
|
|
pub use edit::*;
|
|
use iris_core::util::MutDetect;
|
|
|
|
use crate::prelude::*;
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
pub struct Text {
|
|
pub content: MutDetect<String>,
|
|
view: TextView,
|
|
}
|
|
|
|
pub struct TextView {
|
|
pub attrs: MutDetect<TextAttrs>,
|
|
pub buf: MutDetect<TextBuffer>,
|
|
// cache
|
|
tex: Option<RenderedText>,
|
|
width: Option<f32>,
|
|
pub hint: Option<StrongWidget>,
|
|
}
|
|
|
|
impl TextView {
|
|
fn is_empty(&self) -> bool {
|
|
self.buf.is_empty()
|
|
}
|
|
|
|
pub fn wrap_width(&self) -> Option<f32> {
|
|
self.width
|
|
}
|
|
}
|
|
|
|
impl TextView {
|
|
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<StrongWidget>) -> Self {
|
|
Self {
|
|
attrs: attrs.into(),
|
|
buf: buf.into(),
|
|
tex: None,
|
|
width: None,
|
|
hint,
|
|
}
|
|
}
|
|
|
|
/// region where the text should be draw
|
|
/// does not include extra height or width from weird unicode
|
|
pub fn region(&self) -> UiRegion {
|
|
self.tex()
|
|
.map(|t| t.size)
|
|
.unwrap_or(Vec2::ZERO)
|
|
.align(self.align)
|
|
}
|
|
|
|
fn render(&mut self, ctx: &mut SizeCtx) -> &RenderedText {
|
|
let width = if self.attrs.wrap {
|
|
Some(ctx.px_size().x)
|
|
} else {
|
|
None
|
|
};
|
|
if width != self.width || self.tex.is_none() || self.attrs.changed || self.buf.changed {
|
|
self.width = width;
|
|
self.tex = Some(ctx.draw_text(&mut self.buf, &self.attrs, width));
|
|
self.attrs.changed = false;
|
|
self.buf.changed = false;
|
|
}
|
|
self.tex.as_ref().unwrap()
|
|
}
|
|
pub fn tex(&self) -> Option<&RenderedText> {
|
|
self.tex.as_ref()
|
|
}
|
|
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
if self.is_empty()
|
|
&& let Some(hint) = &self.hint
|
|
{
|
|
ctx.width(hint)
|
|
} else {
|
|
Len::abs(self.render(ctx).size.x)
|
|
}
|
|
}
|
|
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
if self.is_empty()
|
|
&& let Some(hint) = &self.hint
|
|
{
|
|
ctx.height(hint)
|
|
} else {
|
|
Len::abs(self.render(ctx).size.y)
|
|
}
|
|
}
|
|
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
|
let align = self.align;
|
|
if self.is_empty() && self.hint.is_some() {
|
|
let region = self.render(&mut painter.size_ctx()).size.align(align);
|
|
if let Some(hint) = &self.hint {
|
|
painter.widget(hint);
|
|
}
|
|
return region;
|
|
}
|
|
|
|
let tex = self.render(&mut painter.size_ctx());
|
|
let region = tex.size.align(align);
|
|
let within = region.within(&painter.region());
|
|
painter.glyphs(tex, within);
|
|
region
|
|
}
|
|
|
|
pub fn content(&self) -> String {
|
|
self.buf.text().to_string()
|
|
}
|
|
}
|
|
|
|
impl Text {
|
|
pub fn new(content: impl Into<String>) -> Self {
|
|
let content: String = content.into();
|
|
Self {
|
|
view: TextView::new(TextBuffer::new(&content), TextAttrs::default(), None),
|
|
content: content.into(),
|
|
}
|
|
}
|
|
fn update_buf(&mut self, _ctx: &mut SizeCtx) {
|
|
if self.content.changed {
|
|
self.content.changed = false;
|
|
self.view.buf.set_text(self.content.as_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Widget for Text {
|
|
fn draw(&mut self, painter: &mut Painter) {
|
|
self.update_buf(&mut painter.size_ctx());
|
|
self.view.draw(painter);
|
|
}
|
|
|
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
self.update_buf(ctx);
|
|
self.view.desired_width(ctx)
|
|
}
|
|
|
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
|
self.update_buf(ctx);
|
|
self.view.desired_height(ctx)
|
|
}
|
|
}
|
|
|
|
impl Deref for Text {
|
|
type Target = TextAttrs;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.view
|
|
}
|
|
}
|
|
|
|
impl DerefMut for Text {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.view
|
|
}
|
|
}
|
|
|
|
impl Deref for TextView {
|
|
type Target = TextAttrs;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.attrs
|
|
}
|
|
}
|
|
|
|
impl DerefMut for TextView {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.attrs
|
|
}
|
|
}
|