use embedded_graphics::{
Drawable,
draw_target::{DrawTarget, DrawTargetExt},
pixelcolor::Rgb565,
prelude::{Point, Primitive, Size},
primitives::{PrimitiveStyleBuilder, Rectangle, RoundedRectangle},
};
use super::layout::{draw_line, layout_lines};
use super::{TextAlignment, TextSpan, TextVerticalAlignment, TextViewStyle};
pub struct TextView<'a> {
pub frame: Rectangle,
pub text: &'a str,
pub style: super::TextRunStyle,
pub view_style: TextViewStyle,
}
impl<'a> TextView<'a> {
pub const fn new(frame: Rectangle, text: &'a str, style: super::TextRunStyle) -> Self {
Self {
frame,
text,
style,
view_style: TextViewStyle::new(),
}
}
pub fn with_view_style(mut self, view_style: TextViewStyle) -> Self {
self.view_style = view_style;
self
}
pub fn draw<D>(&self, display: &mut D)
where
D: DrawTarget<Color = Rgb565>,
{
let spans = [TextSpan::new(self.text, self.style)];
draw_text_view(display, self.frame, &spans, &self.view_style);
}
}
pub struct RichTextView<'a> {
pub frame: Rectangle,
pub spans: &'a [TextSpan<'a>],
pub view_style: TextViewStyle,
}
impl<'a> RichTextView<'a> {
pub const fn new(frame: Rectangle, spans: &'a [TextSpan<'a>]) -> Self {
Self {
frame,
spans,
view_style: TextViewStyle::new(),
}
}
pub fn with_view_style(mut self, view_style: TextViewStyle) -> Self {
self.view_style = view_style;
self
}
pub fn draw<D>(&self, display: &mut D)
where
D: DrawTarget<Color = Rgb565>,
{
draw_text_view(display, self.frame, self.spans, &self.view_style);
}
}
pub fn draw_text_view<D>(
display: &mut D,
frame: Rectangle,
spans: &[TextSpan<'_>],
view_style: &TextViewStyle,
) where
D: DrawTarget<Color = Rgb565>,
{
draw_shell(display, frame, view_style);
let content = view_style.insets.inset_rect(frame);
if content.size.width == 0 || content.size.height == 0 || spans.is_empty() {
return;
}
let lines = layout_lines(spans, view_style, content.size.width as i32);
if lines.is_empty() {
return;
}
let spacing = view_style.line_spacing as i32;
let total_height = lines.iter().map(|line| line.height).sum::<i32>()
+ spacing * (lines.len().saturating_sub(1) as i32);
let available_height = content.size.height as i32;
let offset_y = match view_style.vertical_alignment {
TextVerticalAlignment::Top => 0,
TextVerticalAlignment::Center => ((available_height - total_height).max(0)) / 2,
TextVerticalAlignment::Bottom => (available_height - total_height).max(0),
};
let mut clipped = display.clipped(&content);
let mut cursor_y = content.top_left.y + offset_y;
for line in lines {
let offset_x = match view_style.alignment {
TextAlignment::Leading => 0,
TextAlignment::Center => ((content.size.width as i32 - line.width).max(0)) / 2,
TextAlignment::Trailing => (content.size.width as i32 - line.width).max(0),
};
draw_line(
&mut clipped,
spans,
line,
Point::new(content.top_left.x + offset_x, cursor_y),
);
cursor_y += line.height + spacing;
}
}
fn draw_shell<D>(display: &mut D, frame: Rectangle, style: &TextViewStyle)
where
D: DrawTarget<Color = Rgb565>,
{
if style.background.is_none() && style.border.is_none() {
return;
}
let mut primitive = PrimitiveStyleBuilder::new();
if let Some(background) = style.background {
primitive = primitive.fill_color(background);
}
if let Some(border) = style.border {
primitive = primitive
.stroke_color(border)
.stroke_width(style.border_width.max(1));
}
RoundedRectangle::with_equal_corners(
frame,
Size::new(style.corner_radius, style.corner_radius),
)
.into_styled(primitive.build())
.draw(display)
.ok();
}