1pub mod editor;
3pub mod highlighter;
4pub mod paragraph;
5
6pub use editor::Editor;
7pub use highlighter::Highlighter;
8pub use paragraph::Paragraph;
9
10use crate::alignment;
11use crate::{
12 Background, Border, Color, Padding, Pixels, Point, Rectangle, Size,
13};
14
15use std::borrow::Cow;
16use std::hash::{Hash, Hasher};
17
18#[derive(Debug, Clone, Copy)]
20pub struct Text<Content = String, Font = crate::Font> {
21 pub content: Content,
23
24 pub bounds: Size,
26
27 pub size: Pixels,
29
30 pub line_height: LineHeight,
32
33 pub font: Font,
35
36 pub align_x: Alignment,
38
39 pub align_y: alignment::Vertical,
41
42 pub shaping: Shaping,
44
45 pub wrapping: Wrapping,
47}
48
49impl<Content, Font> Text<Content, Font>
50where
51 Font: Copy,
52{
53 pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
56 Text {
57 content,
58 bounds: self.bounds,
59 size: self.size,
60 line_height: self.line_height,
61 font: self.font,
62 align_x: self.align_x,
63 align_y: self.align_y,
64 shaping: self.shaping,
65 wrapping: self.wrapping,
66 }
67 }
68}
69
70impl<Content, Font> Text<Content, Font>
71where
72 Content: AsRef<str>,
73 Font: Copy,
74{
75 pub fn as_ref(&self) -> Text<&str, Font> {
77 self.with_content(self.content.as_ref())
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
83pub enum Alignment {
84 #[default]
89 Default,
90 Left,
92 Center,
94 Right,
96 Justified,
98}
99
100impl From<alignment::Horizontal> for Alignment {
101 fn from(alignment: alignment::Horizontal) -> Self {
102 match alignment {
103 alignment::Horizontal::Left => Self::Left,
104 alignment::Horizontal::Center => Self::Center,
105 alignment::Horizontal::Right => Self::Right,
106 }
107 }
108}
109
110impl From<crate::Alignment> for Alignment {
111 fn from(alignment: crate::Alignment) -> Self {
112 match alignment {
113 crate::Alignment::Start => Self::Left,
114 crate::Alignment::Center => Self::Center,
115 crate::Alignment::End => Self::Right,
116 }
117 }
118}
119
120impl From<Alignment> for alignment::Horizontal {
121 fn from(alignment: Alignment) -> Self {
122 match alignment {
123 Alignment::Default | Alignment::Left | Alignment::Justified => {
124 alignment::Horizontal::Left
125 }
126 Alignment::Center => alignment::Horizontal::Center,
127 Alignment::Right => alignment::Horizontal::Right,
128 }
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
134pub enum Shaping {
135 Auto,
144 Basic,
155 Advanced,
165}
166
167impl Default for Shaping {
168 fn default() -> Self {
169 if cfg!(feature = "advanced-shaping") {
170 Self::Advanced
171 } else if cfg!(feature = "basic-shaping") {
172 Self::Basic
173 } else {
174 Self::Auto
175 }
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
181pub enum Wrapping {
182 None,
184 #[default]
188 Word,
189 Glyph,
191 WordOrGlyph,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq)]
197pub enum LineHeight {
198 Relative(f32),
200
201 Absolute(Pixels),
203}
204
205impl LineHeight {
206 pub fn to_absolute(self, text_size: Pixels) -> Pixels {
208 match self {
209 Self::Relative(factor) => Pixels(factor * text_size.0),
210 Self::Absolute(pixels) => pixels,
211 }
212 }
213}
214
215impl Default for LineHeight {
216 fn default() -> Self {
217 Self::Relative(1.3)
218 }
219}
220
221impl From<f32> for LineHeight {
222 fn from(factor: f32) -> Self {
223 Self::Relative(factor)
224 }
225}
226
227impl From<Pixels> for LineHeight {
228 fn from(pixels: Pixels) -> Self {
229 Self::Absolute(pixels)
230 }
231}
232
233impl Hash for LineHeight {
234 fn hash<H: Hasher>(&self, state: &mut H) {
235 match self {
236 Self::Relative(factor) => {
237 state.write_u8(0);
238 factor.to_bits().hash(state);
239 }
240 Self::Absolute(pixels) => {
241 state.write_u8(1);
242 f32::from(*pixels).to_bits().hash(state);
243 }
244 }
245 }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq)]
250pub enum Hit {
251 CharOffset(usize),
253}
254
255impl Hit {
256 pub fn cursor(self) -> usize {
258 match self {
259 Self::CharOffset(i) => i,
260 }
261 }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum Difference {
272 None,
276
277 Bounds,
282
283 Shape,
289}
290
291pub trait Renderer: crate::Renderer {
293 type Font: Copy + PartialEq;
295
296 type Paragraph: Paragraph<Font = Self::Font> + 'static;
298
299 type Editor: Editor<Font = Self::Font> + 'static;
301
302 const MONOSPACE_FONT: Self::Font;
306
307 const ICON_FONT: Self::Font;
309
310 const CHECKMARK_ICON: char;
314
315 const ARROW_DOWN_ICON: char;
319
320 fn default_font(&self) -> Self::Font;
322
323 fn default_size(&self) -> Pixels;
325
326 fn fill_paragraph(
329 &mut self,
330 text: &Self::Paragraph,
331 position: Point,
332 color: Color,
333 clip_bounds: Rectangle,
334 );
335
336 fn fill_editor(
339 &mut self,
340 editor: &Self::Editor,
341 position: Point,
342 color: Color,
343 clip_bounds: Rectangle,
344 );
345
346 fn fill_text(
349 &mut self,
350 text: Text<String, Self::Font>,
351 position: Point,
352 color: Color,
353 clip_bounds: Rectangle,
354 );
355}
356
357#[derive(Debug, Clone)]
359pub struct Span<'a, Link = (), Font = crate::Font> {
360 pub text: Fragment<'a>,
362 pub size: Option<Pixels>,
364 pub line_height: Option<LineHeight>,
366 pub font: Option<Font>,
368 pub color: Option<Color>,
370 pub link: Option<Link>,
372 pub highlight: Option<Highlight>,
374 pub padding: Padding,
378 pub underline: bool,
380 pub strikethrough: bool,
382}
383
384#[derive(Debug, Clone, Copy, PartialEq)]
386pub struct Highlight {
387 pub background: Background,
389 pub border: Border,
391}
392
393impl<'a, Link, Font> Span<'a, Link, Font> {
394 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
396 Self {
397 text: fragment.into_fragment(),
398 ..Self::default()
399 }
400 }
401
402 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
404 self.size = Some(size.into());
405 self
406 }
407
408 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
410 self.line_height = Some(line_height.into());
411 self
412 }
413
414 pub fn font(mut self, font: impl Into<Font>) -> Self {
416 self.font = Some(font.into());
417 self
418 }
419
420 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
422 self.font = font.map(Into::into);
423 self
424 }
425
426 pub fn color(mut self, color: impl Into<Color>) -> Self {
428 self.color = Some(color.into());
429 self
430 }
431
432 pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
434 self.color = color.map(Into::into);
435 self
436 }
437
438 pub fn link(mut self, link: impl Into<Link>) -> Self {
440 self.link = Some(link.into());
441 self
442 }
443
444 pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
446 self.link = link.map(Into::into);
447 self
448 }
449
450 pub fn background(self, background: impl Into<Background>) -> Self {
452 self.background_maybe(Some(background))
453 }
454
455 pub fn background_maybe(
457 mut self,
458 background: Option<impl Into<Background>>,
459 ) -> Self {
460 let Some(background) = background else {
461 return self;
462 };
463
464 match &mut self.highlight {
465 Some(highlight) => {
466 highlight.background = background.into();
467 }
468 None => {
469 self.highlight = Some(Highlight {
470 background: background.into(),
471 border: Border::default(),
472 });
473 }
474 }
475
476 self
477 }
478
479 pub fn border(self, border: impl Into<Border>) -> Self {
481 self.border_maybe(Some(border))
482 }
483
484 pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
486 let Some(border) = border else {
487 return self;
488 };
489
490 match &mut self.highlight {
491 Some(highlight) => {
492 highlight.border = border.into();
493 }
494 None => {
495 self.highlight = Some(Highlight {
496 border: border.into(),
497 background: Background::Color(Color::TRANSPARENT),
498 });
499 }
500 }
501
502 self
503 }
504
505 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
513 self.padding = padding.into();
514 self
515 }
516
517 pub fn underline(mut self, underline: bool) -> Self {
519 self.underline = underline;
520 self
521 }
522
523 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
525 self.strikethrough = strikethrough;
526 self
527 }
528
529 pub fn to_static(self) -> Span<'static, Link, Font> {
531 Span {
532 text: Cow::Owned(self.text.into_owned()),
533 size: self.size,
534 line_height: self.line_height,
535 font: self.font,
536 color: self.color,
537 link: self.link,
538 highlight: self.highlight,
539 padding: self.padding,
540 underline: self.underline,
541 strikethrough: self.strikethrough,
542 }
543 }
544}
545
546impl<Link, Font> Default for Span<'_, Link, Font> {
547 fn default() -> Self {
548 Self {
549 text: Cow::default(),
550 size: None,
551 line_height: None,
552 font: None,
553 color: None,
554 link: None,
555 highlight: None,
556 padding: Padding::default(),
557 underline: false,
558 strikethrough: false,
559 }
560 }
561}
562
563impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
564 fn from(value: &'a str) -> Self {
565 Span::new(value)
566 }
567}
568
569impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
570 fn eq(&self, other: &Self) -> bool {
571 self.text == other.text
572 && self.size == other.size
573 && self.line_height == other.line_height
574 && self.font == other.font
575 && self.color == other.color
576 }
577}
578
579pub type Fragment<'a> = Cow<'a, str>;
584
585pub trait IntoFragment<'a> {
587 fn into_fragment(self) -> Fragment<'a>;
589}
590
591impl<'a> IntoFragment<'a> for Fragment<'a> {
592 fn into_fragment(self) -> Fragment<'a> {
593 self
594 }
595}
596
597impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
598 fn into_fragment(self) -> Fragment<'a> {
599 Fragment::Borrowed(self)
600 }
601}
602
603impl<'a> IntoFragment<'a> for &'a str {
604 fn into_fragment(self) -> Fragment<'a> {
605 Fragment::Borrowed(self)
606 }
607}
608
609impl<'a> IntoFragment<'a> for &'a String {
610 fn into_fragment(self) -> Fragment<'a> {
611 Fragment::Borrowed(self.as_str())
612 }
613}
614
615impl<'a> IntoFragment<'a> for String {
616 fn into_fragment(self) -> Fragment<'a> {
617 Fragment::Owned(self)
618 }
619}
620
621macro_rules! into_fragment {
622 ($type:ty) => {
623 impl<'a> IntoFragment<'a> for $type {
624 fn into_fragment(self) -> Fragment<'a> {
625 Fragment::Owned(self.to_string())
626 }
627 }
628
629 impl<'a> IntoFragment<'a> for &$type {
630 fn into_fragment(self) -> Fragment<'a> {
631 Fragment::Owned(self.to_string())
632 }
633 }
634 };
635}
636
637into_fragment!(char);
638into_fragment!(bool);
639
640into_fragment!(u8);
641into_fragment!(u16);
642into_fragment!(u32);
643into_fragment!(u64);
644into_fragment!(u128);
645into_fragment!(usize);
646
647into_fragment!(i8);
648into_fragment!(i16);
649into_fragment!(i32);
650into_fragment!(i64);
651into_fragment!(i128);
652into_fragment!(isize);
653
654into_fragment!(f32);
655into_fragment!(f64);