use std::convert::{AsMut, AsRef};
use crate::display::{Effect, MarkerPosIter, NotReady, TextDisplay};
use crate::fonts::{self, FaceId, InvalidFontId};
use crate::format::{EditableText, FormattableText};
use crate::{Action, Glyph, Vec2};
use crate::{Align, Direction, Environment};
#[derive(Clone, Debug, Default)]
pub struct Text<T: FormattableText + ?Sized> {
env: Environment,
display: TextDisplay,
text: T,
}
impl<T: FormattableText> Text<T> {
#[inline]
pub fn new(text: T) -> Self {
Text::new_env(Default::default(), text)
}
#[inline]
pub fn new_env(env: Environment, text: T) -> Self {
Text {
env,
text,
display: Default::default(),
}
}
pub fn clone_text(&self) -> T
where
T: Clone,
{
self.text.clone()
}
#[inline]
pub fn take_text(self) -> T {
self.text
}
#[inline]
pub fn text(&self) -> &T {
&self.text
}
pub fn set_text(&mut self, text: T) {
self.text = text;
self.display.action = Action::All;
}
#[inline]
pub fn set_and_try_prepare(&mut self, text: T) -> Result<bool, InvalidFontId> {
self.set_text(text);
self.try_prepare()
}
}
pub trait TextApi {
#[inline]
fn str_len(&self) -> usize {
self.as_str().len()
}
fn as_str(&self) -> &str;
fn clone_string(&self) -> String;
fn env(&self) -> Environment;
fn set_env(&mut self, env: Environment);
fn display(&self) -> &TextDisplay;
fn require_action(&mut self, action: Action);
fn prepare_runs(&mut self) -> Result<(), InvalidFontId>;
fn measure_width(&mut self, limit: f32) -> Result<f32, InvalidFontId>;
fn measure_height(&mut self) -> Result<f32, InvalidFontId>;
fn prepare(&mut self) -> Result<bool, InvalidFontId>;
fn effect_tokens(&self) -> &[Effect<()>];
}
impl<T: FormattableText + ?Sized> TextApi for Text<T> {
#[inline]
fn display(&self) -> &TextDisplay {
&self.display
}
#[inline]
fn as_str(&self) -> &str {
self.text.as_str()
}
#[inline]
fn clone_string(&self) -> String {
self.text.as_str().to_string()
}
#[inline]
fn env(&self) -> Environment {
self.env
}
#[inline]
fn set_env(&mut self, env: Environment) {
let action;
if env.font_id != self.env.font_id || env.direction != self.env.direction {
action = Action::All;
} else if env.dpem != self.env.dpem {
action = Action::Resize;
} else if env.bounds.0 != self.env.bounds.0
|| env.align.0 != self.env.align.0
|| env.wrap != self.env.wrap
{
action = Action::Wrap;
} else if env.bounds.1 != self.env.bounds.1 || env.align.1 != self.env.align.1 {
action = Action::VAlign;
} else {
debug_assert_eq!(env, self.env);
action = Action::None;
}
self.display.require_action(action);
self.env = env;
}
#[inline]
fn require_action(&mut self, action: Action) {
self.display.require_action(action);
}
#[inline]
fn prepare_runs(&mut self) -> Result<(), InvalidFontId> {
self.display.prepare_runs(
&self.text,
self.env.direction,
self.env.font_id,
self.env.dpem,
)
}
fn measure_width(&mut self, limit: f32) -> Result<f32, InvalidFontId> {
if self.display.required_action() > Action::Wrap {
self.prepare_runs()?;
}
Ok(self.display.measure_width(limit).unwrap())
}
fn measure_height(&mut self) -> Result<f32, InvalidFontId> {
let v_align = self.env.align.1;
if v_align != Align::TL {
self.env.align.1 = Align::TL;
self.display.require_action(Action::VAlign);
}
let action = self.display.required_action();
if action > Action::Wrap {
self.prepare_runs()?;
}
let height = if action >= Action::Wrap {
self.display
.prepare_lines(self.env.bounds, self.env.wrap, self.env.align)
.unwrap()
.1
} else if action == Action::VAlign {
self.display
.vertically_align(self.env.bounds.1, self.env.align.1)
.unwrap()
.1
} else {
(self.display.bounding_box().unwrap().1).1
};
if v_align != Align::TL {
self.env.align.1 = v_align;
self.display.require_action(Action::VAlign);
}
Ok(height)
}
#[inline]
fn prepare(&mut self) -> Result<bool, InvalidFontId> {
self.prepare_runs()?;
let action = self.display.required_action();
let bound = if action == Action::Wrap {
self.display
.prepare_lines(self.env.bounds, self.env.wrap, self.env.align)
.unwrap()
} else if action == Action::VAlign {
self.display
.vertically_align(self.env.bounds.1, self.env.align.1)
.unwrap()
} else {
return Ok(false);
};
Ok(!(bound.0 <= self.env.bounds.0 && bound.1 <= self.env.bounds.1))
}
#[inline]
fn effect_tokens(&self) -> &[Effect<()>] {
self.text.effect_tokens()
}
}
pub trait TextApiExt: TextApi {
fn update_env(&mut self, env: Environment) -> Result<bool, InvalidFontId> {
self.set_env(env);
self.prepare()
}
fn try_prepare(&mut self) -> Result<bool, InvalidFontId> {
if !fonts::any_loaded() {
return Err(InvalidFontId);
}
self.prepare()
}
fn bounding_box(&self) -> Result<(Vec2, Vec2), NotReady> {
self.display().bounding_box()
}
#[inline]
fn required_action(&self) -> Action {
self.display().action
}
#[inline]
fn num_lines(&self) -> Result<usize, NotReady> {
self.display().num_lines()
}
#[inline]
fn find_line(&self, index: usize) -> Result<Option<(usize, std::ops::Range<usize>)>, NotReady> {
self.display().find_line(index)
}
#[inline]
fn line_range(&self, line: usize) -> Result<Option<std::ops::Range<usize>>, NotReady> {
self.display().line_range(line)
}
fn text_is_rtl(&self) -> Result<bool, NotReady> {
Ok(match self.display().line_is_rtl(0)? {
None => matches!(self.env().direction, Direction::BidiRtl | Direction::Rtl),
Some(is_rtl) => is_rtl,
})
}
#[inline]
fn line_is_rtl(&self, line: usize) -> Result<Option<bool>, NotReady> {
self.display().line_is_rtl(line)
}
#[inline]
fn text_index_nearest(&self, pos: Vec2) -> Result<usize, NotReady> {
self.display().text_index_nearest(pos)
}
#[inline]
fn line_index_nearest(&self, line: usize, x: f32) -> Result<Option<usize>, NotReady> {
self.display().line_index_nearest(line, x)
}
fn text_glyph_pos(&self, index: usize) -> Result<MarkerPosIter, NotReady> {
self.display().text_glyph_pos(index)
}
#[inline]
#[cfg_attr(doc_cfg, doc(cfg(feature = "num_glyphs")))]
#[cfg(feature = "num_glyphs")]
fn num_glyphs(&self) -> usize {
self.display().num_glyphs()
}
fn glyphs<F: FnMut(FaceId, f32, Glyph)>(&self, f: F) -> Result<(), NotReady> {
self.display().glyphs(f)
}
fn glyphs_with_effects<X, F, G>(
&self,
effects: &[Effect<X>],
default_aux: X,
f: F,
g: G,
) -> Result<(), NotReady>
where
X: Copy,
F: FnMut(FaceId, f32, Glyph, usize, X),
G: FnMut(f32, f32, f32, f32, usize, X),
{
self.display()
.glyphs_with_effects(effects, default_aux, f, g)
}
fn highlight_range<F>(&self, range: std::ops::Range<usize>, mut f: F) -> Result<(), NotReady>
where
F: FnMut(Vec2, Vec2),
{
self.display().highlight_range(range, &mut f)
}
}
impl<T: TextApi + ?Sized> TextApiExt for T {}
pub trait EditableTextApi {
fn insert_char(&mut self, index: usize, c: char);
fn replace_range(&mut self, range: std::ops::Range<usize>, replace_with: &str);
fn set_string(&mut self, string: String);
fn swap_string(&mut self, string: &mut String);
}
impl<T: EditableText + ?Sized> EditableTextApi for Text<T> {
#[inline]
fn insert_char(&mut self, index: usize, c: char) {
self.text.insert_char(index, c);
self.display.action = Action::All;
}
#[inline]
fn replace_range(&mut self, range: std::ops::Range<usize>, replace_with: &str) {
self.text.replace_range(range, replace_with);
self.display.action = Action::All;
}
#[inline]
fn set_string(&mut self, string: String) {
self.text.set_string(string);
self.display.action = Action::All;
}
#[inline]
fn swap_string(&mut self, string: &mut String) {
self.text.swap_string(string);
self.display.action = Action::All;
}
}
impl<T: FormattableText + ?Sized> AsRef<TextDisplay> for Text<T> {
fn as_ref(&self) -> &TextDisplay {
&self.display
}
}
impl<T: FormattableText + ?Sized> AsMut<TextDisplay> for Text<T> {
fn as_mut(&mut self) -> &mut TextDisplay {
&mut self.display
}
}