#![cfg_attr(not(test), no_std)]
#![deny(clippy::missing_inline_in_public_items)]
#![deny(clippy::cargo)]
#![deny(missing_docs)]
#![warn(clippy::all)]
#![allow(clippy::needless_doctest_main)]
pub mod alignment;
mod parser;
pub mod plugin;
mod rendering;
pub mod style;
mod utils;
use crate::{
alignment::{HorizontalAlignment, VerticalAlignment},
plugin::{NoPlugin, PluginMarker as Plugin, PluginWrapper},
style::{HeightMode, TabSize, TextBoxStyle},
};
use embedded_graphics::{
geometry::{Dimensions, Point},
primitives::Rectangle,
text::{
renderer::{CharacterStyle, TextRenderer},
LineHeight,
},
transform::Transform,
};
use object_chain::{Chain, ChainElement, Link};
#[cfg(feature = "plugin")]
pub use crate::{
parser::{ChangeTextStyle, Token},
rendering::{cursor::Cursor, TextBoxProperties},
};
#[derive(Clone, Debug, Hash)]
#[must_use]
pub struct TextBox<'a, S, M = NoPlugin<<S as TextRenderer>::Color>>
where
S: TextRenderer,
{
pub text: &'a str,
pub bounds: Rectangle,
pub character_style: S,
pub style: TextBoxStyle,
pub vertical_offset: i32,
plugin: PluginWrapper<'a, M, S::Color>,
}
impl<'a, S> TextBox<'a, S, NoPlugin<<S as TextRenderer>::Color>>
where
S: TextRenderer + CharacterStyle,
{
#[inline]
pub fn new(text: &'a str, bounds: Rectangle, character_style: S) -> Self {
TextBox::with_textbox_style(text, bounds, character_style, TextBoxStyle::default())
}
#[inline]
pub fn with_textbox_style(
text: &'a str,
bounds: Rectangle,
character_style: S,
textbox_style: TextBoxStyle,
) -> Self {
let mut styled = TextBox {
text,
bounds,
character_style,
style: textbox_style,
vertical_offset: 0,
plugin: PluginWrapper::new(NoPlugin::new()),
};
styled.style.height_mode.apply(&mut styled);
styled
}
#[inline]
pub fn with_alignment(
text: &'a str,
bounds: Rectangle,
character_style: S,
alignment: HorizontalAlignment,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_alignment(alignment),
)
}
#[inline]
pub fn with_vertical_alignment(
text: &'a str,
bounds: Rectangle,
character_style: S,
vertical_alignment: VerticalAlignment,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_vertical_alignment(vertical_alignment),
)
}
#[inline]
pub fn with_height_mode(
text: &'a str,
bounds: Rectangle,
character_style: S,
mode: HeightMode,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_height_mode(mode),
)
}
#[inline]
pub fn with_line_height(
text: &'a str,
bounds: Rectangle,
character_style: S,
line_height: LineHeight,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_line_height(line_height),
)
}
#[inline]
pub fn with_paragraph_spacing(
text: &'a str,
bounds: Rectangle,
character_style: S,
spacing: u32,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_paragraph_spacing(spacing),
)
}
#[inline]
pub fn with_tab_size(
text: &'a str,
bounds: Rectangle,
character_style: S,
tab_size: TabSize,
) -> Self {
TextBox::with_textbox_style(
text,
bounds,
character_style,
TextBoxStyle::with_tab_size(tab_size),
)
}
#[inline]
pub fn set_vertical_offset(&mut self, offset: i32) -> &mut Self {
self.vertical_offset = offset;
self
}
#[inline]
pub fn add_plugin<M>(self, plugin: M) -> TextBox<'a, S, Chain<M>>
where
M: Plugin<'a, <S as TextRenderer>::Color>,
{
let mut styled = TextBox {
text: self.text,
bounds: self.bounds,
character_style: self.character_style,
style: self.style,
vertical_offset: self.vertical_offset,
plugin: PluginWrapper::new(Chain::new(plugin)),
};
styled.style.height_mode.apply(&mut styled);
styled
}
}
impl<'a, S, P> TextBox<'a, S, P>
where
S: TextRenderer + CharacterStyle,
P: Plugin<'a, <S as TextRenderer>::Color> + ChainElement,
{
#[inline]
pub fn add_plugin<M>(self, plugin: M) -> TextBox<'a, S, Link<M, P>>
where
M: Plugin<'a, <S as TextRenderer>::Color>,
{
let parent = self.plugin.into_inner();
let mut styled = TextBox {
text: self.text,
bounds: self.bounds,
character_style: self.character_style,
style: self.style,
vertical_offset: self.vertical_offset,
plugin: PluginWrapper::new(parent.append(plugin)),
};
styled.style.height_mode.apply(&mut styled);
styled
}
#[inline]
pub fn take_plugins(self) -> P {
self.plugin.into_inner()
}
}
impl<'a, S, M> Transform for TextBox<'a, S, M>
where
S: TextRenderer + Clone,
M: Plugin<'a, S::Color>,
{
#[inline]
fn translate(&self, by: Point) -> Self {
Self {
bounds: self.bounds.translate(by),
..self.clone()
}
}
#[inline]
fn translate_mut(&mut self, by: Point) -> &mut Self {
self.bounds.translate_mut(by);
self
}
}
impl<'a, S, M> Dimensions for TextBox<'a, S, M>
where
S: TextRenderer,
M: Plugin<'a, S::Color>,
{
#[inline]
fn bounding_box(&self) -> Rectangle {
self.bounds
}
}
impl<'a, S, M> TextBox<'a, S, M>
where
S: TextRenderer,
M: Plugin<'a, S::Color>,
{
#[inline]
fn fit_height(&mut self) -> &mut Self {
self.fit_height_limited(u32::MAX)
}
#[inline]
fn fit_height_limited(&mut self, max_height: u32) -> &mut Self {
let text_height = self
.style
.measure_text_height_impl(
self.plugin.clone(),
&self.character_style,
self.text,
self.bounding_box().size.width,
)
.min(max_height)
.min(i32::MAX as u32);
self.bounds.size.height = text_height;
self
}
}