use cast::CastFloat;
use super::TextClass;
#[allow(unused)] use super::{DrawCx, SizeCx};
use crate::Layout;
use crate::cast::Cast;
#[allow(unused)] use crate::event::ConfigCx;
use crate::geom::{Rect, Vec2};
use crate::layout::{AlignHints, AxisInfo, SizeRules, Stretch};
use crate::text::fonts::FontSelector;
use crate::text::format::FormattableText;
use crate::text::*;
use std::num::NonZeroUsize;
#[derive(Clone, Debug)]
pub struct Text<T: FormattableText> {
rect: Rect,
font: FontSelector,
dpem: f32,
class: TextClass,
wrap: bool,
align: (Align, Align),
direction: Direction,
status: Status,
display: TextDisplay,
text: T,
}
impl<T: FormattableText> Layout for Text<T> {
fn rect(&self) -> Rect {
self.rect
}
fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
let rules = if axis.is_horizontal() {
if self.wrap {
let (min, ideal) = cx.wrapped_line_len(self.class, self.dpem);
let bound: i32 = self.measure_width(ideal.cast()).cast_ceil();
SizeRules::new(bound.min(min), bound.min(ideal), Stretch::Filler)
} else {
let bound: i32 = self.measure_width(f32::INFINITY).cast_ceil();
SizeRules::new(bound, bound, Stretch::Filler)
}
} else {
let wrap_width = self
.wrap
.then(|| axis.other().map(|w| w.cast()))
.flatten()
.unwrap_or(f32::INFINITY);
let bound: i32 = self.measure_height(wrap_width, None).cast_ceil();
SizeRules::new(bound, bound, Stretch::Filler)
};
rules.with_margins(cx.text_margins().extract(axis))
}
fn set_rect(&mut self, _: &mut SizeCx, rect: Rect, hints: AlignHints) {
self.set_align(hints.complete_default().into());
if rect.size != self.rect.size {
if rect.size.0 != self.rect.size.0 {
self.set_max_status(Status::LevelRuns);
} else {
self.set_max_status(Status::Wrapped);
}
}
self.rect = rect;
self.rewrap();
}
fn draw(&self, mut draw: DrawCx) {
draw.text(self.rect, self);
}
}
impl<T: FormattableText> Text<T> {
#[inline]
pub fn new(text: T, class: TextClass, wrap: bool) -> Self {
Text {
rect: Rect::default(),
font: FontSelector::default(),
dpem: 16.0,
class,
wrap,
align: Default::default(),
direction: Direction::default(),
status: Status::New,
text,
display: Default::default(),
}
}
#[inline]
pub fn with_class(mut self, class: TextClass) -> Self {
self.class = class;
self
}
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
}
#[inline]
pub fn text_mut(&mut self) -> &mut T {
&mut self.text
}
pub fn configure(&mut self, cx: &mut SizeCx) {
let font = cx.font(self.class);
let dpem = cx.dpem(self.class);
if font != self.font {
self.font = font;
self.dpem = dpem;
self.set_max_status(Status::New);
} else if dpem != self.dpem {
self.dpem = dpem;
self.set_max_status(Status::ResizeLevelRuns);
}
}
#[inline]
pub fn require_reprepare(&mut self) {
self.set_max_status(Status::New);
}
pub fn set_text(&mut self, text: T) {
if self.text == text {
return; }
self.text = text;
self.set_max_status(Status::New);
}
#[inline]
pub fn str_len(&self) -> usize {
self.as_str().len()
}
#[inline]
pub fn as_str(&self) -> &str {
self.text.as_str()
}
#[inline]
pub fn clone_string(&self) -> String {
self.text.as_str().to_string()
}
#[inline]
pub fn class(&self) -> TextClass {
self.class
}
#[inline]
pub fn set_class(&mut self, class: TextClass) {
self.class = class;
}
#[inline]
pub fn wrap(&self) -> bool {
self.wrap
}
#[inline]
pub fn set_wrap(&mut self, wrap: bool) {
self.wrap = wrap;
}
#[inline]
pub fn font(&self) -> FontSelector {
self.font
}
#[inline]
pub fn set_font(&mut self, font: FontSelector) {
if font != self.font {
self.font = font;
self.set_max_status(Status::New);
}
}
#[inline]
pub fn font_size(&self) -> f32 {
self.dpem
}
#[inline]
pub fn set_font_size(&mut self, dpem: f32) {
if dpem != self.dpem {
self.dpem = dpem;
self.set_max_status(Status::ResizeLevelRuns);
}
}
#[inline]
pub fn set_font_size_pt(&mut self, pt_size: f32, scale_factor: f32) {
self.set_font_size(pt_size * scale_factor * (96.0 / 72.0));
}
#[inline]
pub fn direction(&self) -> Direction {
self.direction
}
#[inline]
pub fn set_direction(&mut self, direction: Direction) {
if direction != self.direction {
self.direction = direction;
self.set_max_status(Status::New);
}
}
#[inline]
pub fn align(&self) -> (Align, Align) {
self.align
}
#[inline]
pub fn set_align(&mut self, align: (Align, Align)) {
if align != self.align {
if align.0 == self.align.0 {
self.set_max_status(Status::Wrapped);
} else {
self.set_max_status(Status::LevelRuns);
}
self.align = align;
}
}
pub fn text_is_rtl(&self) -> bool {
let cached_is_rtl = match self.line_is_rtl(0) {
Ok(None) => Some(self.direction == Direction::Rtl),
Ok(Some(is_rtl)) => Some(is_rtl),
Err(NotReady) => None,
};
#[cfg(not(debug_assertions))]
if let Some(cached) = cached_is_rtl {
return cached;
}
let is_rtl = self.display.text_is_rtl(self.as_str(), self.direction);
if let Some(cached) = cached_is_rtl {
debug_assert_eq!(cached, is_rtl);
}
is_rtl
}
#[inline]
pub fn effect_tokens(&self) -> &[Effect] {
self.text.effect_tokens()
}
}
impl<T: FormattableText> Text<T> {
#[inline]
pub fn check_status(&self, status: Status) -> Result<(), NotReady> {
if self.status >= status { Ok(()) } else { Err(NotReady) }
}
#[inline]
pub fn is_prepared(&self) -> bool {
self.status == Status::Ready
}
#[inline]
fn set_max_status(&mut self, status: Status) {
self.status = self.status.min(status);
}
#[inline]
pub fn unchecked_display(&self) -> &TextDisplay {
&self.display
}
#[inline]
pub fn display(&self) -> Result<&TextDisplay, NotReady> {
self.check_status(Status::Ready)?;
Ok(self.unchecked_display())
}
#[inline]
pub fn wrapped_display(&self) -> Result<&TextDisplay, NotReady> {
self.check_status(Status::Wrapped)?;
Ok(self.unchecked_display())
}
fn prepare_runs(&mut self) {
match self.status {
Status::New => self
.display
.prepare_runs(&self.text, self.direction, self.font, self.dpem)
.expect("no suitable font found"),
Status::ResizeLevelRuns => self.display.resize_runs(&self.text, self.dpem),
_ => return,
}
self.status = Status::LevelRuns;
}
pub fn measure_width(&mut self, max_width: f32) -> f32 {
self.prepare_runs();
self.display.measure_width(max_width)
}
pub fn measure_height(&mut self, wrap_width: f32, max_lines: Option<NonZeroUsize>) -> f32 {
self.prepare_runs();
self.display.measure_height(wrap_width, max_lines)
}
pub fn prepare(&mut self) -> bool {
if self.is_prepared() {
return false;
}
self.prepare_runs();
debug_assert!(self.status >= Status::LevelRuns);
self.rewrap();
true
}
fn rewrap(&mut self) {
if self.status < Status::LevelRuns {
return;
}
if self.status == Status::LevelRuns {
let align_width = self.rect.size.0.cast();
let wrap_width = if !self.wrap { f32::INFINITY } else { align_width };
self.display
.prepare_lines(wrap_width, align_width, self.align.0);
}
if self.status <= Status::Wrapped {
self.display
.vertically_align(self.rect.size.1.cast(), self.align.1);
}
self.status = Status::Ready;
}
pub fn reprepare_action(&mut self, cx: &mut ConfigCx) {
if self.prepare() {
let (tl, br) = self.display.bounding_box();
let bounds: Vec2 = self.rect.size.cast();
if tl.0 < 0.0 || tl.1 < 0.0 || br.0 > bounds.0 || br.1 > bounds.1 {
cx.resize();
}
}
cx.redraw();
}
pub fn ensure_no_left_overhang(&mut self) {
if let Ok((tl, _)) = self.bounding_box()
&& tl.0 < 0.0
{
self.display.apply_offset(kas_text::Vec2(-tl.0, 0.0));
}
}
#[inline]
pub fn bounding_box(&self) -> Result<(Vec2, Vec2), NotReady> {
let (tl, br) = self.wrapped_display()?.bounding_box();
Ok((tl.into(), br.into()))
}
#[inline]
pub fn num_lines(&self) -> Result<usize, NotReady> {
Ok(self.wrapped_display()?.num_lines())
}
#[inline]
pub fn get_line(&self, index: usize) -> Result<Option<&Line>, NotReady> {
Ok(self.wrapped_display()?.get_line(index))
}
#[inline]
pub fn lines(&self) -> Result<impl Iterator<Item = &Line>, NotReady> {
Ok(self.wrapped_display()?.lines())
}
#[inline]
pub fn find_line(
&self,
index: usize,
) -> Result<Option<(usize, std::ops::Range<usize>)>, NotReady> {
Ok(self.wrapped_display()?.find_line(index))
}
#[inline]
pub fn line_is_rtl(&self, line: usize) -> Result<Option<bool>, NotReady> {
Ok(self.wrapped_display()?.line_is_rtl(line))
}
#[inline]
pub fn text_index_nearest(&self, pos: Vec2) -> Result<usize, NotReady> {
Ok(self.display()?.text_index_nearest(pos.into()))
}
#[inline]
pub fn line_index_nearest(&self, line: usize, x: f32) -> Result<Option<usize>, NotReady> {
Ok(self.wrapped_display()?.line_index_nearest(line, x))
}
pub fn text_glyph_pos(&self, index: usize) -> Result<MarkerPosIter, NotReady> {
Ok(self.display()?.text_glyph_pos(index))
}
}
impl Text<String> {
#[inline]
pub fn insert_char(&mut self, index: usize, c: char) {
self.text.insert(index, c);
self.set_max_status(Status::New);
}
#[inline]
pub fn insert_str(&mut self, index: usize, text: &str) {
self.text.insert_str(index, text);
self.set_max_status(Status::New);
}
#[inline]
pub fn replace_range(&mut self, range: std::ops::Range<usize>, replace_with: &str) {
self.text.replace_range(range, replace_with);
self.set_max_status(Status::New);
}
#[inline]
pub fn set_str(&mut self, text: &str) -> bool {
if self.text.as_str() == text {
return false; }
self.text = text.to_string();
self.set_max_status(Status::New);
true
}
#[inline]
pub fn set_string(&mut self, text: String) -> bool {
if self.text.as_str() == text {
return false; }
self.text = text;
self.set_max_status(Status::New);
true
}
#[inline]
pub fn swap_string(&mut self, string: &mut String) {
std::mem::swap(&mut self.text, string);
self.set_max_status(Status::New);
}
}