use cosmic_text::{Family, Metrics}; use crate::prelude::*; pub struct Text { pub content: String, pub attrs: TextAttrs, /// inner alignment of text region (within where its drawn) pub align: Align, buf: TextBuffer, cursor: Cursor, size: Vec2, } impl Text { pub fn font_size(mut self, size: impl UiNum) -> Self { self.attrs.font_size = size.to_f32(); self.attrs.line_height = self.attrs.font_size * 1.1; self } pub fn color(mut self, color: UiColor) -> Self { self.attrs.color = color; self } pub fn family(mut self, family: Family<'static>) -> Self { self.attrs.family = family; self } pub fn line_height(mut self, height: f32) -> Self { self.attrs.line_height = height; self } pub fn new(content: impl Into) -> Self { let attrs = TextAttrs::default(); Self { content: content.into(), buf: TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height)), attrs, align: Align::Center, cursor: Cursor::None, size: Vec2::ZERO, } } pub fn select(&mut self, pos: Vec2, size: Vec2) { let pos = pos - self.region().top_left.to_size(size); let Some(cursor) = self.buf.hit(pos.x, pos.y) else { return; }; self.cursor = Cursor::Select { line: cursor.line as isize, col: cursor.index as isize, }; } pub fn deselect(&mut self) { self.cursor = Cursor::None; } pub fn insert(&mut self, text: &str) { let i = self.update_cursor(); self.content.insert_str(i, text); match &mut self.cursor { Cursor::None => (), Cursor::Select { line, col } => { *col += 1; } } } pub fn backspace(&mut self) { if let Some(i) = self.update_cursor().checked_sub(1) { self.content.remove(i); match &mut self.cursor { Cursor::None => (), Cursor::Select { line, col } => { *col -= 1; } } } } pub fn delete(&mut self) { let i = self.update_cursor(); if i != self.content.len() { self.content.remove(i); } } pub fn move_cursor(&mut self, dir: Dir) { if let Cursor::Select { line, col } = &mut self.cursor { match dir { Dir::LEFT => *col -= 1, Dir::RIGHT => *col += 1, Dir::UP => *line -= 1, Dir::DOWN => *line += 1, } } } pub fn update_cursor(&mut self) -> usize { match &mut self.cursor { Cursor::None => 0, Cursor::Select { line, col } => { if *col < 0 { *line -= 1; } if *line < 0 { *line = 0; *col = 0; } let mut idx = self.content.len(); let mut l = 0; let mut c = 0; let mut cur_len = 0; for (i, ch) in self.content.chars().enumerate() { if ch == '\n' { l += 1; c = 0; } else { if l == *line { cur_len = i as isize + 1; if c == *col { idx = i; } } c += 1; } } if *col < 0 { *col = cur_len; } if *col > cur_len { *col = 0; *line += 1; } if *line > l { *line = l; *col = cur_len; } idx } } } pub fn region(&self) -> UiRegion { UiRegion::from_size_align(self.size, self.align) } } impl Widget for Text { fn draw(&mut self, painter: &mut Painter) { self.update_cursor(); let (handle, offset) = painter.render_text(&mut self.buf, &self.content, &self.attrs, &self.cursor); let dims = handle.size(); self.size = offset.size(&handle); let mut region = self.region(); region.top_left.offset += offset.top_left; region.bot_right.offset = region.top_left.offset + dims; painter.draw_texture_within(&handle, region); } fn get_size(&mut self, ctx: &mut SizeCtx) -> Vec2 { let (handle, offset) = ctx.draw_text(&mut self.buf, &self.content, &self.attrs, &self.cursor); offset.size(&handle) } } pub fn text(content: impl Into) -> Text { Text::new(content) }