Files
iris/src/widget/text/mod.rs
T
2026-09-14 02:48:02 -04:00

167 lines
4.3 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, painter: &mut Painter) -> &RenderedText {
let width = if self.attrs.wrap {
Some(painter.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(painter.render_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()
}
/// Draws the text, and says where the glyphs went and what they use.
pub fn draw(&mut self, painter: &mut Painter) -> (UiRegion, Size) {
let align = self.align;
if self.is_empty() && self.hint.is_some() {
let region = self.render(painter).size.align(align);
let size = match &self.hint {
Some(hint) => painter.widget(hint).size(),
None => Size::ZERO,
};
return (region, size);
}
let tex = self.render(painter);
let region = tex.size.align(align);
let size = Size::abs(tex.size);
let within = region.within(&painter.region());
painter.glyphs(tex, within);
(region, size)
}
/// Wrapping reads the width it is offered, so a wider box reshapes it and
/// a taller one does not. Alignment matters too, and separately: glyphs
/// anchored to the start of an axis stay put when that extent changes,
/// but centred or end-aligned ones move even though the shaping stands.
pub fn on_resize(&self, axis: Axis) -> OnResize {
let reshapes = axis == Axis::X && self.attrs.wrap;
let anchored = match axis {
Axis::X => self.align.x,
Axis::Y => self.align.y,
} == AxisAlign::Neg;
match reshapes || !anchored {
true => OnResize::Redraw,
false => OnResize::Translate,
}
}
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) {
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) -> Size {
self.update_buf();
self.view.draw(painter).1
}
fn on_resize(&self, axis: Axis) -> OnResize {
self.view.on_resize(axis)
}
}
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
}
}