use std::{
any::Any,
borrow::Cow,
cell::RefCell,
fmt::{
Debug,
Display,
},
rc::Rc,
};
use freya_engine::prelude::{
BlendMode,
Canvas,
FontCollection,
FontStyle,
Paint,
PaintStyle,
ParagraphBuilder,
ParagraphStyle,
PlaceholderAlignment,
PlaceholderStyle,
RectHeightStyle,
RectWidthStyle,
SaveLayerRec,
SkParagraph,
SkRect,
TextBaseline,
TextStyle,
};
use rustc_hash::FxHashMap;
use torin::prelude::{
Area,
Length,
Point2D,
Position,
PostMeasure,
Size2D,
};
use crate::{
data::{
AccessibilityData,
CursorStyleData,
EffectData,
LayoutData,
StyleState,
TextStyleData,
TextStyleState,
},
diff_key::DiffKey,
element::{
Element,
ElementExt,
EventHandlerType,
IntoElement,
LayoutContext,
PostMeasureContext,
RenderContext,
},
elements::rect::rect,
events::name::EventName,
layers::Layer,
node_id::NodeId,
prelude::{
AccessibilityExt,
ChildrenExt,
Color,
ContainerExt,
ContainerPositionExt,
EventHandlersExt,
Fill,
KeyExt,
LayerExt,
LayoutExt,
MaybeExt,
TextAlign,
TextStyleExt,
VerticalAlign,
},
style::cursor::{
CursorMode,
CursorStyle,
},
text_cache::CachedParagraph,
tree::DiffModifies,
};
pub fn paragraph() -> Paragraph {
Paragraph {
key: DiffKey::None,
element: ParagraphElement::default(),
children: Vec::new(),
}
}
pub struct ParagraphHolderInner {
pub paragraph: Rc<SkParagraph>,
pub scale_factor: f64,
}
#[derive(Clone)]
pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
impl PartialEq for ParagraphHolder {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Debug for ParagraphHolder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ParagraphHolder")
}
}
impl Default for ParagraphHolder {
fn default() -> Self {
Self(Rc::new(RefCell::new(None)))
}
}
#[derive(PartialEq, Clone)]
pub enum ParagraphContent {
Span,
Element,
}
#[derive(PartialEq, Clone)]
pub struct ParagraphElement {
pub layout: LayoutData,
pub spans: Vec<Span<'static>>,
pub contents: Vec<ParagraphContent>,
pub accessibility: AccessibilityData,
pub text_style_data: TextStyleData,
pub cursor_style_data: CursorStyleData,
pub event_handlers: FxHashMap<EventName, EventHandlerType>,
pub sk_paragraph: ParagraphHolder,
pub cursor_index: Option<usize>,
pub highlights: Vec<(usize, usize)>,
pub max_lines: Option<usize>,
pub line_height: Option<f32>,
pub relative_layer: Layer,
pub cursor_style: CursorStyle,
pub cursor_mode: CursorMode,
pub vertical_align: VerticalAlign,
}
impl Default for ParagraphElement {
fn default() -> Self {
let mut accessibility = AccessibilityData::default();
accessibility.builder.set_role(accesskit::Role::Paragraph);
Self {
layout: Default::default(),
spans: Default::default(),
contents: Default::default(),
accessibility,
text_style_data: Default::default(),
cursor_style_data: Default::default(),
event_handlers: Default::default(),
sk_paragraph: Default::default(),
cursor_index: Default::default(),
highlights: Default::default(),
max_lines: Default::default(),
line_height: Default::default(),
relative_layer: Default::default(),
cursor_style: CursorStyle::default(),
cursor_mode: CursorMode::default(),
vertical_align: VerticalAlign::default(),
}
}
}
impl Display for ParagraphElement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
&self
.spans
.iter()
.map(|s| s.text.clone())
.collect::<Vec<_>>()
.join("\n"),
)
}
}
impl ElementExt for ParagraphElement {
fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
else {
return false;
};
self != paragraph
}
fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
else {
return DiffModifies::all();
};
let mut diff = DiffModifies::empty();
if self.spans != paragraph.spans || self.contents != paragraph.contents {
diff.insert(DiffModifies::STYLE);
diff.insert(DiffModifies::LAYOUT);
}
if self.accessibility != paragraph.accessibility {
diff.insert(DiffModifies::ACCESSIBILITY);
}
if self.relative_layer != paragraph.relative_layer {
diff.insert(DiffModifies::LAYER);
}
if self.text_style_data != paragraph.text_style_data {
diff.insert(DiffModifies::STYLE);
}
if self.event_handlers != paragraph.event_handlers {
diff.insert(DiffModifies::EVENT_HANDLERS);
}
if self.cursor_index != paragraph.cursor_index
|| self.highlights != paragraph.highlights
|| self.cursor_mode != paragraph.cursor_mode
|| self.cursor_style != paragraph.cursor_style
|| self.cursor_style_data != paragraph.cursor_style_data
|| self.vertical_align != paragraph.vertical_align
{
diff.insert(DiffModifies::STYLE);
}
if self.text_style_data != paragraph.text_style_data
|| self.line_height != paragraph.line_height
|| self.max_lines != paragraph.max_lines
{
diff.insert(DiffModifies::TEXT_STYLE);
diff.insert(DiffModifies::LAYOUT);
}
if self.layout != paragraph.layout {
diff.insert(DiffModifies::STYLE);
diff.insert(DiffModifies::LAYOUT);
}
diff
}
fn layout(&'_ self) -> Cow<'_, LayoutData> {
Cow::Borrowed(&self.layout)
}
fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
None
}
fn style(&'_ self) -> Cow<'_, StyleState> {
Cow::Owned(StyleState::default())
}
fn is_transparent(&self) -> bool {
false
}
fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
Cow::Borrowed(&self.text_style_data)
}
fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
Cow::Borrowed(&self.accessibility)
}
fn layer(&self) -> Layer {
self.relative_layer
}
fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
let cached_paragraph = CachedParagraph {
text_style_state: context.text_style_state,
spans: &self.spans,
max_lines: self.max_lines,
line_height: self.line_height,
width: context.area_size.width,
};
let paragraph = context
.text_cache
.utilize(context.node_id, &cached_paragraph)
.unwrap_or_else(|| {
let width = if self.max_lines == Some(1)
&& context.text_style_state.text_align == TextAlign::default()
&& context
.text_style_state
.text_overflow
.get_ellipsis()
.is_none()
{
f32::MAX
} else {
context.area_size.width + 1.0
};
let paragraph = self.build_paragraph(
context.text_style_state,
context.fallback_fonts,
context.scale_factor,
context.font_collection,
width,
&[],
);
context
.text_cache
.insert(context.node_id, &cached_paragraph, paragraph)
});
let size = Size2D::new(paragraph.longest_line(), paragraph.height()).max(Size2D::zero());
self.sk_paragraph
.0
.borrow_mut()
.replace(ParagraphHolderInner {
paragraph,
scale_factor: context.scale_factor,
});
Some((size, Rc::new(())))
}
fn should_hook_measurement(&self) -> bool {
true
}
fn should_measure_inner_children(&self) -> bool {
self.has_inline_content()
}
fn needs_post_measure(&self) -> bool {
self.has_inline_content()
}
fn post_measure(&self, context: PostMeasureContext) -> PostMeasure<NodeId> {
if context.children.is_empty() {
return PostMeasure::default();
}
let placeholders: Vec<Size2D> = context
.children
.iter()
.map(|child| {
context
.layout
.get(child)
.map(|node| node.area.size)
.unwrap()
})
.collect();
let width = self
.sk_paragraph
.0
.borrow()
.as_ref()
.map(|holder| holder.paragraph.max_width())
.unwrap();
let paragraph = self.build_paragraph(
context.text_style_state,
context.fallback_fonts,
context.scale_factor,
context.font_collection,
width,
&placeholders,
);
let rects = paragraph.get_rects_for_placeholders();
let paragraph_height = paragraph.height();
let content_size = Size2D::new(paragraph.longest_line(), paragraph_height);
self.sk_paragraph
.0
.borrow_mut()
.replace(ParagraphHolderInner {
paragraph: Rc::new(paragraph),
scale_factor: context.scale_factor,
});
let visible_area = context.node_layout.visible_area();
let vertical_offset = match self.vertical_align {
VerticalAlign::Start => 0.0,
VerticalAlign::Center => (visible_area.height() - paragraph_height).max(0.0) / 2.0,
};
let origin = visible_area.origin;
let mut offsets = Vec::new();
let mut hidden_children = Vec::new();
for (index, child_id) in context.children.iter().enumerate() {
let Some(current) = context.layout.get(child_id).map(|node| node.area.origin) else {
continue;
};
match rects.get(index) {
Some(rect) => {
let offset_x = origin.x + rect.rect.left - current.x;
let offset_y = origin.y + vertical_offset + rect.rect.top - current.y;
offsets.push((*child_id, Length::new(offset_x), Length::new(offset_y)));
}
None => hidden_children.push(*child_id),
}
}
PostMeasure {
content_size: Some(content_size),
offsets,
hidden_children,
}
}
fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
Some(Cow::Borrowed(&self.event_handlers))
}
fn render(&self, context: RenderContext) {
let paragraph = self.sk_paragraph.0.borrow();
let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
let visible_area = context.layout_node.visible_area();
let cursor_area = match self.cursor_mode {
CursorMode::Fit => visible_area,
CursorMode::Expanded => context.layout_node.area,
};
let paragraph_height = paragraph.height();
let area_height = visible_area.height();
let vertical_offset = match self.vertical_align {
VerticalAlign::Start => 0.0,
VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
};
let cursor_vertical_offset = match self.cursor_mode {
CursorMode::Fit => vertical_offset,
CursorMode::Expanded => 0.0,
};
let cursor_vertical_size_offset = match self.cursor_mode {
CursorMode::Fit => 0.,
CursorMode::Expanded => vertical_offset * 2.,
};
for (from, to) in self.highlights.iter() {
if from == to {
continue;
}
let (from, to) = { if from < to { (from, to) } else { (to, from) } };
let rects = paragraph.get_rects_for_range(
*from..*to,
RectHeightStyle::Tight,
RectWidthStyle::Tight,
);
let mut highlights_paint = Paint::default();
highlights_paint.set_anti_alias(true);
highlights_paint.set_style(PaintStyle::Fill);
highlights_paint.set_color(self.cursor_style_data.highlight_color);
if rects.is_empty() && *from == 0 {
let avg_line_height =
paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
let rect = SkRect::new(
cursor_area.min_x(),
cursor_area.min_y() + cursor_vertical_offset,
cursor_area.min_x() + 6.,
cursor_area.min_y() + avg_line_height + cursor_vertical_size_offset,
);
context.canvas.draw_rect(rect, &highlights_paint);
}
for rect in rects {
let rect = SkRect::new(
cursor_area.min_x() + rect.rect.left,
cursor_area.min_y() + rect.rect.top + cursor_vertical_offset,
cursor_area.min_x() + rect.rect.right.max(6.),
cursor_area.min_y() + rect.rect.bottom + cursor_vertical_size_offset,
);
context.canvas.draw_rect(rect, &highlights_paint);
}
}
let visible_highlights = self
.highlights
.iter()
.filter(|highlight| highlight.0 != highlight.1)
.count()
> 0;
if let Some(cursor_index) = self.cursor_index
&& self.cursor_style == CursorStyle::Block
&& let Some(cursor_rect) = paragraph
.get_rects_for_range(
cursor_index..cursor_index + 1,
RectHeightStyle::Tight,
RectWidthStyle::Tight,
)
.first()
.map(|text| text.rect)
.or_else(|| {
let text_len = paragraph
.get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
.position as usize;
let last_rects = paragraph.get_rects_for_range(
text_len.saturating_sub(1)..text_len,
RectHeightStyle::Tight,
RectWidthStyle::Tight,
);
if let Some(last_rect) = last_rects.first() {
let mut caret = last_rect.rect;
caret.left = caret.right;
Some(caret)
} else {
let avg_line_height =
paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
Some(SkRect::new(0., 0., 6., avg_line_height))
}
})
{
let width = (cursor_rect.right - cursor_rect.left).max(6.0);
let cursor_rect = SkRect::new(
cursor_area.min_x() + cursor_rect.left,
cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
cursor_area.min_x() + cursor_rect.left + width,
cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
);
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
paint.set_color(self.cursor_style_data.color);
context.canvas.draw_rect(cursor_rect, &paint);
}
paint_paragraph_with_fill(
paragraph,
context.canvas,
Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
&context.text_style_state.color,
);
if let Some(cursor_index) = self.cursor_index
&& !visible_highlights
{
let cursor_rects = paragraph.get_rects_for_range(
cursor_index..cursor_index + 1,
RectHeightStyle::Tight,
RectWidthStyle::Tight,
);
if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
let text_len = paragraph
.get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
.position as usize;
let last_rects = paragraph.get_rects_for_range(
text_len.saturating_sub(1)..text_len,
RectHeightStyle::Tight,
RectWidthStyle::Tight,
);
if let Some(last_rect) = last_rects.first() {
let mut caret = last_rect.rect;
caret.left = caret.right;
Some(caret)
} else {
None
}
}) {
let paint_color = self.cursor_style_data.color;
match self.cursor_style {
CursorStyle::Underline => {
let thickness = 2.0;
let underline_rect = SkRect::new(
cursor_area.min_x() + cursor_rect.left,
cursor_area.min_y() + cursor_rect.bottom - thickness
+ cursor_vertical_offset,
cursor_area.min_x() + cursor_rect.right,
cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
);
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
paint.set_color(paint_color);
context.canvas.draw_rect(underline_rect, &paint);
}
CursorStyle::Line => {
let cursor_rect = SkRect::new(
cursor_area.min_x() + cursor_rect.left,
cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
cursor_area.min_x() + cursor_rect.left + 2.,
cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
);
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
paint.set_color(paint_color);
context.canvas.draw_rect(cursor_rect, &paint);
}
_ => {}
}
}
}
}
}
impl ParagraphElement {
fn has_inline_content(&self) -> bool {
self.contents
.iter()
.any(|content| matches!(content, ParagraphContent::Element))
}
fn build_paragraph(
&self,
text_style_state: &TextStyleState,
fallback_fonts: &[Cow<'static, str>],
scale_factor: f64,
font_collection: &FontCollection,
width: f32,
placeholders: &[Size2D],
) -> SkParagraph {
let mut paragraph_style = ParagraphStyle::default();
if let Some(ellipsis) = text_style_state.text_overflow.get_ellipsis() {
paragraph_style.set_ellipsis(ellipsis);
}
paragraph_style.set_text_style(&base_text_style(
text_style_state,
fallback_fonts,
scale_factor,
self.line_height,
));
paragraph_style.set_max_lines(self.max_lines);
paragraph_style.set_text_align(text_style_state.text_align.into());
let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
let mut spans = self.spans.iter();
let mut placeholders = placeholders.iter();
for content in &self.contents {
match content {
ParagraphContent::Span => {
let Some(span) = spans.next() else { continue };
paragraph_builder.push_style(&span_text_style(
text_style_state,
fallback_fonts,
scale_factor,
span,
self.line_height,
));
paragraph_builder.add_text(&span.text);
}
ParagraphContent::Element => {
let Some(size) = placeholders.next() else {
continue;
};
paragraph_builder.add_placeholder(&PlaceholderStyle::new(
size.width,
size.height,
PlaceholderAlignment::Middle,
TextBaseline::Alphabetic,
0.0,
));
}
}
}
let mut paragraph = paragraph_builder.build();
paragraph.layout(width);
paragraph
}
}
impl From<Paragraph> for Element {
fn from(value: Paragraph) -> Self {
let elements = value
.children
.into_iter()
.map(|child| {
rect()
.position(Position::new_absolute())
.child(child)
.into_element()
})
.collect();
Element::Element {
key: value.key,
element: Rc::new(value.element),
elements,
}
}
}
fn base_text_style(
text_style_state: &TextStyleState,
fallback_fonts: &[Cow<'static, str>],
scale_factor: f64,
line_height: Option<f32>,
) -> TextStyle {
let mut text_style = TextStyle::default();
let mut font_families = text_style_state.font_families.clone();
font_families.extend_from_slice(fallback_fonts);
text_style.set_color(text_style_state.color.as_color().unwrap_or(Color::WHITE));
text_style.set_font_size(f32::from(text_style_state.font_size) * scale_factor as f32);
text_style.set_font_families(&font_families);
text_style.set_font_style(FontStyle::new(
text_style_state.font_weight.into(),
text_style_state.font_width.into(),
text_style_state.font_slant.into(),
));
if text_style_state.text_height.needs_custom_height() {
text_style.set_height_override(true);
text_style.set_half_leading(true);
}
if let Some(line_height) = line_height {
text_style.set_height_override(true);
text_style.set_height(line_height);
}
for text_shadow in text_style_state.text_shadows.iter() {
text_style.add_shadow((*text_shadow).into());
}
text_style
}
fn span_text_style(
text_style_state: &TextStyleState,
fallback_fonts: &[Cow<'static, str>],
scale_factor: f64,
span: &Span,
line_height: Option<f32>,
) -> TextStyle {
let span_style = TextStyleState::from_data(text_style_state, &span.text_style_data);
let mut text_style = TextStyle::new();
let mut font_families = text_style_state.font_families.clone();
font_families.extend_from_slice(fallback_fonts);
for text_shadow in span_style.text_shadows.iter() {
text_style.add_shadow((*text_shadow).into());
}
text_style.set_color(span_style.color.as_color().unwrap_or(Color::WHITE));
text_style.set_font_size(f32::from(span_style.font_size) * scale_factor as f32);
text_style.set_font_families(&font_families);
text_style.set_font_style(FontStyle::new(
span_style.font_weight.into(),
span_style.font_width.into(),
span_style.font_slant.into(),
));
text_style.set_decoration_type(span_style.text_decoration.into());
if let Some(line_height) = line_height {
text_style.set_height_override(true);
text_style.set_height(line_height);
}
text_style
}
pub(crate) fn paint_paragraph_with_fill(
paragraph: &SkParagraph,
canvas: &Canvas,
origin: Point2D,
fill: &Fill,
) {
if matches!(fill, Fill::Color(_)) {
paragraph.paint(canvas, origin.to_tuple());
return;
}
let width = paragraph.longest_line();
let height = paragraph.height();
let area = Area::new(origin, Size2D::new(width, height));
let bounds_rect = SkRect::from_xywh(area.min_x(), area.min_y(), width, height);
let layer = canvas.save_layer(&SaveLayerRec::default().bounds(&bounds_rect));
paragraph.paint(canvas, origin.to_tuple());
let mut paint = Paint::default();
paint.set_anti_alias(true);
paint.set_style(PaintStyle::Fill);
paint.set_blend_mode(BlendMode::SrcIn);
fill.apply_to_paint(&mut paint, area);
canvas.draw_rect(bounds_rect, &paint);
canvas.restore_to_count(layer);
}
impl KeyExt for Paragraph {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl EventHandlersExt for Paragraph {
fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
&mut self.element.event_handlers
}
}
impl MaybeExt for Paragraph {}
impl LayerExt for Paragraph {
fn get_layer(&mut self) -> &mut Layer {
&mut self.element.relative_layer
}
}
pub struct Paragraph {
key: DiffKey,
element: ParagraphElement,
children: Vec<Element>,
}
impl LayoutExt for Paragraph {
fn get_layout(&mut self) -> &mut LayoutData {
&mut self.element.layout
}
}
impl ContainerExt for Paragraph {}
impl ChildrenExt for Paragraph {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.children
}
fn child<C: IntoElement>(mut self, child: C) -> Self {
self.element.contents.push(ParagraphContent::Element);
self.children.push(child.into_element());
self
}
fn children(self, children: impl IntoIterator<Item = Element>) -> Self {
children
.into_iter()
.fold(self, |paragraph, child| paragraph.child(child))
}
fn maybe_child<C: IntoElement>(self, child: Option<C>) -> Self {
match child {
Some(child) => self.child(child),
None => self,
}
}
}
impl AccessibilityExt for Paragraph {
fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
&mut self.element.accessibility
}
}
impl TextStyleExt for Paragraph {
fn get_text_style_data(&mut self) -> &mut TextStyleData {
&mut self.element.text_style_data
}
}
impl Paragraph {
pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
(element as &dyn Any)
.downcast_ref::<ParagraphElement>()
.cloned()
}
pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
for span in spans {
self.push_span(span);
}
self
}
pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
self.push_span(span.into());
self
}
fn push_span(&mut self, span: Span<'static>) {
self.element.contents.push(ParagraphContent::Span);
self.element.spans.push(span);
}
pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
self.element.cursor_style_data.color = cursor_color.into();
self
}
pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
self.element.cursor_style_data.highlight_color = highlight_color.into();
self
}
pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
self.element.cursor_style = cursor_style.into();
self
}
pub fn holder(mut self, holder: ParagraphHolder) -> Self {
self.element.sk_paragraph = holder;
self
}
pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
self.element.cursor_index = cursor_index.into();
self
}
pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
if let Some(highlights) = highlights.into() {
self.element.highlights = highlights;
}
self
}
pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
self.element.max_lines = max_lines.into();
self
}
pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
self.element.line_height = line_height.into();
self
}
pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
self.element.cursor_mode = cursor_mode.into();
self
}
pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
self.element.vertical_align = vertical_align.into();
self
}
}
#[derive(Clone, PartialEq, Hash)]
pub struct Span<'a> {
pub text_style_data: TextStyleData,
pub text: Cow<'a, str>,
}
impl From<&'static str> for Span<'static> {
fn from(text: &'static str) -> Self {
Span {
text_style_data: TextStyleData::default(),
text: text.into(),
}
}
}
impl From<String> for Span<'static> {
fn from(text: String) -> Self {
Span {
text_style_data: TextStyleData::default(),
text: text.into(),
}
}
}
impl<'a> Span<'a> {
pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
Self {
text: text.into(),
text_style_data: TextStyleData::default(),
}
}
}
impl<'a> TextStyleExt for Span<'a> {
fn get_text_style_data(&mut self) -> &mut TextStyleData {
&mut self.text_style_data
}
}