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::{Background, Border, Color, Padding, Pixels, Point, Rectangle, Size};
12
13use std::borrow::Cow;
14use std::hash::{Hash, Hasher};
15
16#[derive(Debug, Clone, Copy)]
18pub struct Text<Content = String, Font = crate::Font> {
19 pub content: Content,
21
22 pub bounds: Size,
24
25 pub size: Pixels,
27
28 pub line_height: LineHeight,
30
31 pub font: Font,
33
34 pub align_x: Alignment,
36
37 pub align_y: alignment::Vertical,
39
40 pub shaping: Shaping,
42
43 pub wrapping: Wrapping,
45
46 pub ellipsis: Ellipsis,
48
49 pub hint_factor: Option<f32>,
58}
59
60impl<Content, Font> Text<Content, Font>
61where
62 Font: Copy,
63{
64 pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
67 Text {
68 content,
69 bounds: self.bounds,
70 size: self.size,
71 line_height: self.line_height,
72 font: self.font,
73 align_x: self.align_x,
74 align_y: self.align_y,
75 shaping: self.shaping,
76 wrapping: self.wrapping,
77 ellipsis: self.ellipsis,
78 hint_factor: self.hint_factor,
79 }
80 }
81}
82
83impl<Content, Font> Text<Content, Font>
84where
85 Content: AsRef<str>,
86 Font: Copy,
87{
88 pub fn as_ref(&self) -> Text<&str, Font> {
90 self.with_content(self.content.as_ref())
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
96pub enum Alignment {
97 #[default]
102 Default,
103 Left,
105 Center,
107 Right,
109 Justified,
111}
112
113impl From<alignment::Horizontal> for Alignment {
114 fn from(alignment: alignment::Horizontal) -> Self {
115 match alignment {
116 alignment::Horizontal::Left => Self::Left,
117 alignment::Horizontal::Center => Self::Center,
118 alignment::Horizontal::Right => Self::Right,
119 }
120 }
121}
122
123impl From<crate::Alignment> for Alignment {
124 fn from(alignment: crate::Alignment) -> Self {
125 match alignment {
126 crate::Alignment::Start => Self::Left,
127 crate::Alignment::Center => Self::Center,
128 crate::Alignment::End => Self::Right,
129 }
130 }
131}
132
133impl From<Alignment> for alignment::Horizontal {
134 fn from(alignment: Alignment) -> Self {
135 match alignment {
136 Alignment::Default | Alignment::Left | Alignment::Justified => {
137 alignment::Horizontal::Left
138 }
139 Alignment::Center => alignment::Horizontal::Center,
140 Alignment::Right => alignment::Horizontal::Right,
141 }
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum Shaping {
148 Auto,
157 Basic,
168 Advanced,
178}
179
180impl Default for Shaping {
181 fn default() -> Self {
182 if cfg!(feature = "advanced-shaping") {
183 Self::Advanced
184 } else if cfg!(feature = "basic-shaping") {
185 Self::Basic
186 } else {
187 Self::Auto
188 }
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
194pub enum Wrapping {
195 None,
197 #[default]
201 Word,
202 Glyph,
204 WordOrGlyph,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
209pub enum Ellipsis {
211 #[default]
215 None,
216 Start,
218 Middle,
220 End,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq)]
226pub enum LineHeight {
227 Relative(f32),
229
230 Absolute(Pixels),
232}
233
234impl LineHeight {
235 pub fn to_absolute(self, text_size: Pixels) -> Pixels {
237 match self {
238 Self::Relative(factor) => Pixels(factor * text_size.0),
239 Self::Absolute(pixels) => pixels,
240 }
241 }
242}
243
244impl Default for LineHeight {
245 fn default() -> Self {
246 Self::Relative(1.3)
247 }
248}
249
250impl From<f32> for LineHeight {
251 fn from(factor: f32) -> Self {
252 Self::Relative(factor)
253 }
254}
255
256impl From<Pixels> for LineHeight {
257 fn from(pixels: Pixels) -> Self {
258 Self::Absolute(pixels)
259 }
260}
261
262impl Hash for LineHeight {
263 fn hash<H: Hasher>(&self, state: &mut H) {
264 match self {
265 Self::Relative(factor) => {
266 state.write_u8(0);
267 factor.to_bits().hash(state);
268 }
269 Self::Absolute(pixels) => {
270 state.write_u8(1);
271 f32::from(*pixels).to_bits().hash(state);
272 }
273 }
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq)]
279pub enum Hit {
280 CharOffset(usize),
282}
283
284impl Hit {
285 pub fn cursor(self) -> usize {
287 match self {
288 Self::CharOffset(i) => i,
289 }
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum Difference {
301 None,
305
306 Bounds,
311
312 Shape,
318}
319
320pub trait Renderer: crate::Renderer {
322 type Font: Copy + PartialEq;
324
325 type Paragraph: Paragraph<Font = Self::Font> + 'static;
327
328 type Editor: Editor<Font = Self::Font> + 'static;
330
331 const ICON_FONT: Self::Font;
333
334 const CHECKMARK_ICON: char;
338
339 const ARROW_DOWN_ICON: char;
343
344 const SCROLL_UP_ICON: char;
348
349 const SCROLL_DOWN_ICON: char;
353
354 const SCROLL_LEFT_ICON: char;
358
359 const SCROLL_RIGHT_ICON: char;
363
364 const ICED_LOGO: char;
368
369 fn default_font(&self) -> Self::Font;
371
372 fn default_size(&self) -> Pixels;
374
375 fn fill_paragraph(
378 &mut self,
379 text: &Self::Paragraph,
380 position: Point,
381 color: Color,
382 clip_bounds: Rectangle,
383 );
384
385 fn fill_editor(
388 &mut self,
389 editor: &Self::Editor,
390 position: Point,
391 color: Color,
392 clip_bounds: Rectangle,
393 );
394
395 fn fill_text(
398 &mut self,
399 text: Text<String, Self::Font>,
400 position: Point,
401 color: Color,
402 clip_bounds: Rectangle,
403 );
404}
405
406#[derive(Debug, Clone)]
408pub struct Span<'a, Link = (), Font = crate::Font> {
409 pub text: Fragment<'a>,
411 pub size: Option<Pixels>,
413 pub line_height: Option<LineHeight>,
415 pub font: Option<Font>,
417 pub color: Option<Color>,
419 pub link: Option<Link>,
421 pub highlight: Option<Highlight>,
423 pub padding: Padding,
427 pub underline: bool,
429 pub strikethrough: bool,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq)]
435pub struct Highlight {
436 pub background: Background,
438 pub border: Border,
440}
441
442impl<'a, Link, Font> Span<'a, Link, Font> {
443 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
445 Self {
446 text: fragment.into_fragment(),
447 ..Self::default()
448 }
449 }
450
451 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
453 self.size = Some(size.into());
454 self
455 }
456
457 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
459 self.line_height = Some(line_height.into());
460 self
461 }
462
463 pub fn font(mut self, font: impl Into<Font>) -> Self {
465 self.font = Some(font.into());
466 self
467 }
468
469 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
471 self.font = font.map(Into::into);
472 self
473 }
474
475 pub fn color(mut self, color: impl Into<Color>) -> Self {
477 self.color = Some(color.into());
478 self
479 }
480
481 pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
483 self.color = color.map(Into::into);
484 self
485 }
486
487 pub fn link(mut self, link: impl Into<Link>) -> Self {
489 self.link = Some(link.into());
490 self
491 }
492
493 pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
495 self.link = link.map(Into::into);
496 self
497 }
498
499 pub fn background(self, background: impl Into<Background>) -> Self {
501 self.background_maybe(Some(background))
502 }
503
504 pub fn background_maybe(mut self, background: Option<impl Into<Background>>) -> Self {
506 let Some(background) = background else {
507 return self;
508 };
509
510 match &mut self.highlight {
511 Some(highlight) => {
512 highlight.background = background.into();
513 }
514 None => {
515 self.highlight = Some(Highlight {
516 background: background.into(),
517 border: Border::default(),
518 });
519 }
520 }
521
522 self
523 }
524
525 pub fn border(self, border: impl Into<Border>) -> Self {
527 self.border_maybe(Some(border))
528 }
529
530 pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
532 let Some(border) = border else {
533 return self;
534 };
535
536 match &mut self.highlight {
537 Some(highlight) => {
538 highlight.border = border.into();
539 }
540 None => {
541 self.highlight = Some(Highlight {
542 border: border.into(),
543 background: Background::Color(Color::TRANSPARENT),
544 });
545 }
546 }
547
548 self
549 }
550
551 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
559 self.padding = padding.into();
560 self
561 }
562
563 pub fn underline(mut self, underline: bool) -> Self {
565 self.underline = underline;
566 self
567 }
568
569 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
571 self.strikethrough = strikethrough;
572 self
573 }
574
575 pub fn to_static(self) -> Span<'static, Link, Font> {
577 Span {
578 text: Cow::Owned(self.text.into_owned()),
579 size: self.size,
580 line_height: self.line_height,
581 font: self.font,
582 color: self.color,
583 link: self.link,
584 highlight: self.highlight,
585 padding: self.padding,
586 underline: self.underline,
587 strikethrough: self.strikethrough,
588 }
589 }
590}
591
592impl<Link, Font> Default for Span<'_, Link, Font> {
593 fn default() -> Self {
594 Self {
595 text: Cow::default(),
596 size: None,
597 line_height: None,
598 font: None,
599 color: None,
600 link: None,
601 highlight: None,
602 padding: Padding::default(),
603 underline: false,
604 strikethrough: false,
605 }
606 }
607}
608
609impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
610 fn from(value: &'a str) -> Self {
611 Span::new(value)
612 }
613}
614
615impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
616 fn eq(&self, other: &Self) -> bool {
617 self.text == other.text
618 && self.size == other.size
619 && self.line_height == other.line_height
620 && self.font == other.font
621 && self.color == other.color
622 }
623}
624
625pub type Fragment<'a> = Cow<'a, str>;
630
631pub trait IntoFragment<'a> {
633 fn into_fragment(self) -> Fragment<'a>;
635}
636
637impl<'a> IntoFragment<'a> for Fragment<'a> {
638 fn into_fragment(self) -> Fragment<'a> {
639 self
640 }
641}
642
643impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
644 fn into_fragment(self) -> Fragment<'a> {
645 Fragment::Borrowed(self)
646 }
647}
648
649impl<'a> IntoFragment<'a> for &'a str {
650 fn into_fragment(self) -> Fragment<'a> {
651 Fragment::Borrowed(self)
652 }
653}
654
655impl<'a> IntoFragment<'a> for &'a String {
656 fn into_fragment(self) -> Fragment<'a> {
657 Fragment::Borrowed(self.as_str())
658 }
659}
660
661impl<'a> IntoFragment<'a> for String {
662 fn into_fragment(self) -> Fragment<'a> {
663 Fragment::Owned(self)
664 }
665}
666
667macro_rules! into_fragment {
668 ($type:ty) => {
669 impl<'a> IntoFragment<'a> for $type {
670 fn into_fragment(self) -> Fragment<'a> {
671 Fragment::Owned(self.to_string())
672 }
673 }
674
675 impl<'a> IntoFragment<'a> for &$type {
676 fn into_fragment(self) -> Fragment<'a> {
677 Fragment::Owned(self.to_string())
678 }
679 }
680 };
681}
682
683into_fragment!(char);
684into_fragment!(bool);
685
686into_fragment!(u8);
687into_fragment!(u16);
688into_fragment!(u32);
689into_fragment!(u64);
690into_fragment!(u128);
691into_fragment!(usize);
692
693into_fragment!(i8);
694into_fragment!(i16);
695into_fragment!(i32);
696into_fragment!(i64);
697into_fragment!(i128);
698into_fragment!(isize);
699
700into_fragment!(f32);
701into_fragment!(f64);