use std::ops::{Deref, DerefMut}; use crate::prelude::*; use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion}; use unicode_segmentation::UnicodeSegmentation; use winit::{ event::KeyEvent, keyboard::{Key, NamedKey}, }; pub struct TextEdit { pub(super) view: TextView, pub(super) cursor: Option, } impl TextEdit { pub fn region(&self) -> UiRegion { UiRegion::from_size_align( self.tex().map(|t| t.size()).unwrap_or(Vec2::ZERO), self.align, ) } pub fn content(&self) -> String { self.buf .lines .iter() .map(|l| l.text()) // why is this needed?? what?? .collect::>() .join("\n") } } impl Widget for TextEdit { fn draw(&mut self, painter: &mut Painter) { let tex = self.view.draw(&mut painter.size_ctx()); let region = text_region(&tex, self.align); painter.texture_within(&tex.handle, region); if let Some(cursor) = &self.cursor && let Some(offset) = cursor_pos(cursor, &self.buf) { let size = vec2(1, self.attrs.line_height); painter.primitive_within( RectPrimitive::color(Color::WHITE), UiRegion::from_size_align(size, Align::TopLeft) .offset(offset) .within(®ion), ); } } fn desired_size(&mut self, ctx: &mut SizeCtx) -> Size { Size::abs(self.view.draw(ctx).size()) } } /// copied & modified from fn found in Editor in cosmic_text fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> { let mut prev = None; for run in buf .layout_runs() .skip_while(|r| r.line_i < cursor.line) .take_while(|r| r.line_i == cursor.line) { prev = Some((run.line_w, run.line_top)); for glyph in run.glyphs.iter() { if cursor.index == glyph.start { return Some((glyph.x, run.line_top)); } else if cursor.index > glyph.start && cursor.index < glyph.end { // Guess x offset based on characters let mut before = 0; let mut total = 0; let cluster = &run.text[glyph.start..glyph.end]; for (i, _) in cluster.grapheme_indices(true) { if glyph.start + i < cursor.index { before += 1; } total += 1; } let offset = glyph.w * (before as f32) / (total as f32); return Some((glyph.x + offset, run.line_top)); } } } prev } pub struct TextEditCtx<'a> { pub text: &'a mut TextEdit, pub font_system: &'a mut FontSystem, } impl<'a> TextEditCtx<'a> { pub fn take(&mut self) -> String { let text = self .text .buf .lines .drain(..) .map(|l| l.into_text()) .collect::>() .join("\n"); self.text .buf .set_text(self.font_system, "", &Attrs::new(), SHAPING, None); if let Some(cursor) = &mut self.text.cursor { cursor.line = 0; cursor.index = 0; cursor.affinity = Affinity::default(); } text } pub fn motion(&mut self, motion: Motion) { if let Some(cursor) = self.text.cursor && let Some((cursor, _)) = self.text .buf .cursor_motion(self.font_system, cursor, None, motion) { self.text.cursor = Some(cursor); } } pub fn replace(&mut self, len: usize, text: &str) { for _ in 0..len { self.delete(); } self.insert_inner(text, false); } pub fn insert(&mut self, text: &str) { let mut lines = text.split('\n'); let Some(first) = lines.next() else { return; }; self.insert_inner(first, true); for line in lines { self.newline(); self.insert_inner(line, true); } } fn insert_inner(&mut self, text: &str, mov: bool) { if let Some(cursor) = &mut self.text.cursor { let line = &mut self.text.view.buf.lines[cursor.line]; let mut line_text = line.text().to_string(); line_text.insert_str(cursor.index, text); line.set_text(line_text, line.ending(), line.attrs_list().clone()); if mov { for _ in 0..text.chars().count() { self.motion(Motion::Right); } } } } pub fn newline(&mut self) { if let Some(cursor) = &mut self.text.cursor { let lines = &mut self.text.view.buf.lines; let line = &mut lines[cursor.line]; let new = line.split_off(cursor.index); cursor.line += 1; lines.insert(cursor.line, new); cursor.index = 0; } } pub fn backspace(&mut self) { if let Some(cursor) = &mut self.text.cursor && (cursor.index != 0 || cursor.line != 0) { self.motion(Motion::Left); self.delete(); } } pub fn delete(&mut self) { if let Some(cursor) = &mut self.text.cursor { let lines = &mut self.text.view.buf.lines; let line = &mut lines[cursor.line]; if cursor.index == line.text().len() { if cursor.line == lines.len() - 1 { return; } let add = lines.remove(cursor.line + 1).into_text(); let line = &mut lines[cursor.line]; let mut cur = line.text().to_string(); cur.push_str(&add); line.set_text(cur, line.ending(), line.attrs_list().clone()); } else { let mut text = line.text().to_string(); text.remove(cursor.index); line.set_text(text, line.ending(), line.attrs_list().clone()); } } } pub fn select(&mut self, pos: Vec2, size: Vec2) { let pos = pos - self.text.region().top_left.to_abs(size); self.text.cursor = self.text.buf.hit(pos.x, pos.y); } pub fn deselect(&mut self) { self.text.cursor = None; } pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { match &event.logical_key { Key::Named(named) => match named { NamedKey::Backspace => self.backspace(), NamedKey::Delete => self.delete(), NamedKey::Space => self.insert(" "), NamedKey::Enter => { if modifiers.shift { self.newline(); } else { return TextInputResult::Submit; } } NamedKey::ArrowRight => self.motion(Motion::Right), NamedKey::ArrowLeft => self.motion(Motion::Left), NamedKey::ArrowUp => self.motion(Motion::Up), NamedKey::ArrowDown => self.motion(Motion::Down), NamedKey::Escape => { self.deselect(); return TextInputResult::Unfocus; } _ => return TextInputResult::Unused, }, Key::Character(text) => { if modifiers.control && text == "v" { return TextInputResult::Paste; } else { self.insert(text) } } _ => return TextInputResult::Unused, } TextInputResult::Used } } #[derive(Default)] pub struct Modifiers { pub shift: bool, pub control: bool, } impl Modifiers { pub fn clear(&mut self) { self.shift = false; self.control = false; } } pub enum TextInputResult { Used, Unused, Unfocus, Submit, Paste, } impl TextInputResult { pub fn unfocus(&self) -> bool { matches!(self, TextInputResult::Unfocus) } } impl Deref for TextEdit { type Target = TextView; fn deref(&self) -> &Self::Target { &self.view } } impl DerefMut for TextEdit { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.view } }