mod build; mod edit; pub use build::*; pub use edit::*; use iris_core::util::MutDetect; use crate::prelude::*; use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping}; use std::ops::{Deref, DerefMut}; pub const SHAPING: Shaping = Shaping::Advanced; pub struct Text { pub content: MutDetect, view: TextView, } pub struct TextView { pub attrs: MutDetect, pub buf: MutDetect, // cache tex: Option, width: Option, pub hint: Option, } impl TextView { pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option) -> 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 tex_region(&self, tex: &RenderedText) -> UiRegion { let region = tex.size.align(self.align); let dims = tex.handle.size(); let mut region = region.offset(tex.top_left_offset); region.x.end = region.x.start + UiScalar::abs(dims.x); region.y.end = region.y.start + UiScalar::abs(dims.y); region } 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 && let Some(tex) = &self.tex && !self.attrs.changed && !self.buf.changed { return tex.clone(); } self.width = width; let font_system = &mut ctx.text.font_system; self.attrs.apply(font_system, &mut self.buf, width); self.buf.shape_until_scroll(font_system, false); let tex = ctx.draw_text(&mut self.buf, &self.attrs); self.tex = Some(tex.clone()); self.attrs.changed = false; self.buf.changed = false; tex } pub fn tex(&self) -> Option<&RenderedText> { self.tex.as_ref() } pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len { if let Some(hint) = &self.hint && let [line] = &self.buf.lines[..] && line.text().is_empty() { ctx.width(hint) } else { Len::abs(self.render(ctx).size.x) } } pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len { if let Some(hint) = &self.hint && let [line] = &self.buf.lines[..] && line.text().is_empty() { ctx.height(hint) } else { Len::abs(self.render(ctx).size.y) } } pub fn draw(&mut self, painter: &mut Painter) -> UiRegion { let tex = self.render(&mut painter.size_ctx()); let region = self.tex_region(&tex); if let Some(hint) = &self.hint && let [line] = &self.buf.lines[..] && line.text().is_empty() { painter.widget(hint); } else { painter.texture_within(&tex.handle, region); } region } pub fn content(&self) -> String { self.buf .lines .iter() .map(|l| l.text()) .collect::>() .join("\n") } } impl Text { pub fn new(content: impl Into) -> Self { let attrs = TextAttrs::default(); let buf = TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height)); Self { content: content.into().into(), view: TextView::new(buf, attrs, None), } } fn update_buf(&mut self, ctx: &mut SizeCtx) { if self.content.changed { self.content.changed = false; self.view.buf.set_text( &mut ctx.text.font_system, &self.content, &Attrs::new().family(self.view.attrs.family), SHAPING, None, ); } } } 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) } } pub fn sort_cursors(a: Cursor, b: Cursor) -> (Cursor, Cursor) { let start = a.min(b); let end = a.max(b); (start, end) } pub fn edit_line(line: &mut BufferLine, text: String) { line.set_text(text, line.ending(), line.attrs_list().clone()); } 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 } }