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
47impl<Content, Font> Text<Content, Font>
48where
49 Font: Copy,
50{
51 pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
54 Text {
55 content,
56 bounds: self.bounds,
57 size: self.size,
58 line_height: self.line_height,
59 font: self.font,
60 align_x: self.align_x,
61 align_y: self.align_y,
62 shaping: self.shaping,
63 wrapping: self.wrapping,
64 }
65 }
66}
67
68impl<Content, Font> Text<Content, Font>
69where
70 Content: AsRef<str>,
71 Font: Copy,
72{
73 pub fn as_ref(&self) -> Text<&str, Font> {
75 self.with_content(self.content.as_ref())
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
81pub enum Alignment {
82 #[default]
87 Default,
88 Left,
90 Center,
92 Right,
94 Justified,
96}
97
98impl From<alignment::Horizontal> for Alignment {
99 fn from(alignment: alignment::Horizontal) -> Self {
100 match alignment {
101 alignment::Horizontal::Left => Self::Left,
102 alignment::Horizontal::Center => Self::Center,
103 alignment::Horizontal::Right => Self::Right,
104 }
105 }
106}
107
108impl From<crate::Alignment> for Alignment {
109 fn from(alignment: crate::Alignment) -> Self {
110 match alignment {
111 crate::Alignment::Start => Self::Left,
112 crate::Alignment::Center => Self::Center,
113 crate::Alignment::End => Self::Right,
114 }
115 }
116}
117
118impl From<Alignment> for alignment::Horizontal {
119 fn from(alignment: Alignment) -> Self {
120 match alignment {
121 Alignment::Default | Alignment::Left | Alignment::Justified => {
122 alignment::Horizontal::Left
123 }
124 Alignment::Center => alignment::Horizontal::Center,
125 Alignment::Right => alignment::Horizontal::Right,
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub enum Shaping {
133 Auto,
142 Basic,
153 Advanced,
163}
164
165impl Default for Shaping {
166 fn default() -> Self {
167 if cfg!(feature = "advanced-shaping") {
168 Self::Advanced
169 } else if cfg!(feature = "basic-shaping") {
170 Self::Basic
171 } else {
172 Self::Auto
173 }
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
179pub enum Wrapping {
180 None,
182 #[default]
186 Word,
187 Glyph,
189 WordOrGlyph,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq)]
195pub enum LineHeight {
196 Relative(f32),
198
199 Absolute(Pixels),
201}
202
203impl LineHeight {
204 pub fn to_absolute(self, text_size: Pixels) -> Pixels {
206 match self {
207 Self::Relative(factor) => Pixels(factor * text_size.0),
208 Self::Absolute(pixels) => pixels,
209 }
210 }
211}
212
213impl Default for LineHeight {
214 fn default() -> Self {
215 Self::Relative(1.3)
216 }
217}
218
219impl From<f32> for LineHeight {
220 fn from(factor: f32) -> Self {
221 Self::Relative(factor)
222 }
223}
224
225impl From<Pixels> for LineHeight {
226 fn from(pixels: Pixels) -> Self {
227 Self::Absolute(pixels)
228 }
229}
230
231impl Hash for LineHeight {
232 fn hash<H: Hasher>(&self, state: &mut H) {
233 match self {
234 Self::Relative(factor) => {
235 state.write_u8(0);
236 factor.to_bits().hash(state);
237 }
238 Self::Absolute(pixels) => {
239 state.write_u8(1);
240 f32::from(*pixels).to_bits().hash(state);
241 }
242 }
243 }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq)]
248pub enum Hit {
249 CharOffset(usize),
251}
252
253impl Hit {
254 pub fn cursor(self) -> usize {
256 match self {
257 Self::CharOffset(i) => i,
258 }
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum Difference {
270 None,
274
275 Bounds,
280
281 Shape,
287}
288
289pub trait Renderer: crate::Renderer {
291 type Font: Copy + PartialEq;
293
294 type Paragraph: Paragraph<Font = Self::Font> + 'static;
296
297 type Editor: Editor<Font = Self::Font> + 'static;
299
300 const ICON_FONT: Self::Font;
302
303 const CHECKMARK_ICON: char;
307
308 const ARROW_DOWN_ICON: char;
312
313 const SCROLL_UP_ICON: char;
317
318 const SCROLL_DOWN_ICON: char;
322
323 const SCROLL_LEFT_ICON: char;
327
328 const SCROLL_RIGHT_ICON: char;
332
333 const ICED_LOGO: char;
337
338 fn default_font(&self) -> Self::Font;
340
341 fn default_size(&self) -> Pixels;
343
344 fn fill_paragraph(
347 &mut self,
348 text: &Self::Paragraph,
349 position: Point,
350 color: Color,
351 clip_bounds: Rectangle,
352 );
353
354 fn fill_editor(
357 &mut self,
358 editor: &Self::Editor,
359 position: Point,
360 color: Color,
361 clip_bounds: Rectangle,
362 );
363
364 fn fill_text(
367 &mut self,
368 text: Text<String, Self::Font>,
369 position: Point,
370 color: Color,
371 clip_bounds: Rectangle,
372 );
373}
374
375#[derive(Debug, Clone)]
377pub struct Span<'a, Link = (), Font = crate::Font> {
378 pub text: Fragment<'a>,
380 pub size: Option<Pixels>,
382 pub line_height: Option<LineHeight>,
384 pub font: Option<Font>,
386 pub color: Option<Color>,
388 pub link: Option<Link>,
390 pub highlight: Option<Highlight>,
392 pub padding: Padding,
396 pub underline: bool,
398 pub strikethrough: bool,
400}
401
402#[derive(Debug, Clone, Copy, PartialEq)]
404pub struct Highlight {
405 pub background: Background,
407 pub border: Border,
409}
410
411impl<'a, Link, Font> Span<'a, Link, Font> {
412 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
414 Self {
415 text: fragment.into_fragment(),
416 ..Self::default()
417 }
418 }
419
420 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
422 self.size = Some(size.into());
423 self
424 }
425
426 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
428 self.line_height = Some(line_height.into());
429 self
430 }
431
432 pub fn font(mut self, font: impl Into<Font>) -> Self {
434 self.font = Some(font.into());
435 self
436 }
437
438 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
440 self.font = font.map(Into::into);
441 self
442 }
443
444 pub fn color(mut self, color: impl Into<Color>) -> Self {
446 self.color = Some(color.into());
447 self
448 }
449
450 pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
452 self.color = color.map(Into::into);
453 self
454 }
455
456 pub fn link(mut self, link: impl Into<Link>) -> Self {
458 self.link = Some(link.into());
459 self
460 }
461
462 pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
464 self.link = link.map(Into::into);
465 self
466 }
467
468 pub fn background(self, background: impl Into<Background>) -> Self {
470 self.background_maybe(Some(background))
471 }
472
473 pub fn background_maybe(mut self, background: Option<impl Into<Background>>) -> Self {
475 let Some(background) = background else {
476 return self;
477 };
478
479 match &mut self.highlight {
480 Some(highlight) => {
481 highlight.background = background.into();
482 }
483 None => {
484 self.highlight = Some(Highlight {
485 background: background.into(),
486 border: Border::default(),
487 });
488 }
489 }
490
491 self
492 }
493
494 pub fn border(self, border: impl Into<Border>) -> Self {
496 self.border_maybe(Some(border))
497 }
498
499 pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
501 let Some(border) = border else {
502 return self;
503 };
504
505 match &mut self.highlight {
506 Some(highlight) => {
507 highlight.border = border.into();
508 }
509 None => {
510 self.highlight = Some(Highlight {
511 border: border.into(),
512 background: Background::Color(Color::TRANSPARENT),
513 });
514 }
515 }
516
517 self
518 }
519
520 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
528 self.padding = padding.into();
529 self
530 }
531
532 pub fn underline(mut self, underline: bool) -> Self {
534 self.underline = underline;
535 self
536 }
537
538 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
540 self.strikethrough = strikethrough;
541 self
542 }
543
544 pub fn to_static(self) -> Span<'static, Link, Font> {
546 Span {
547 text: Cow::Owned(self.text.into_owned()),
548 size: self.size,
549 line_height: self.line_height,
550 font: self.font,
551 color: self.color,
552 link: self.link,
553 highlight: self.highlight,
554 padding: self.padding,
555 underline: self.underline,
556 strikethrough: self.strikethrough,
557 }
558 }
559}
560
561impl<Link, Font> Default for Span<'_, Link, Font> {
562 fn default() -> Self {
563 Self {
564 text: Cow::default(),
565 size: None,
566 line_height: None,
567 font: None,
568 color: None,
569 link: None,
570 highlight: None,
571 padding: Padding::default(),
572 underline: false,
573 strikethrough: false,
574 }
575 }
576}
577
578impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
579 fn from(value: &'a str) -> Self {
580 Span::new(value)
581 }
582}
583
584impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
585 fn eq(&self, other: &Self) -> bool {
586 self.text == other.text
587 && self.size == other.size
588 && self.line_height == other.line_height
589 && self.font == other.font
590 && self.color == other.color
591 }
592}
593
594pub type Fragment<'a> = Cow<'a, str>;
599
600pub trait IntoFragment<'a> {
602 fn into_fragment(self) -> Fragment<'a>;
604}
605
606impl<'a> IntoFragment<'a> for Fragment<'a> {
607 fn into_fragment(self) -> Fragment<'a> {
608 self
609 }
610}
611
612impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
613 fn into_fragment(self) -> Fragment<'a> {
614 Fragment::Borrowed(self)
615 }
616}
617
618impl<'a> IntoFragment<'a> for &'a str {
619 fn into_fragment(self) -> Fragment<'a> {
620 Fragment::Borrowed(self)
621 }
622}
623
624impl<'a> IntoFragment<'a> for &'a String {
625 fn into_fragment(self) -> Fragment<'a> {
626 Fragment::Borrowed(self.as_str())
627 }
628}
629
630impl<'a> IntoFragment<'a> for String {
631 fn into_fragment(self) -> Fragment<'a> {
632 Fragment::Owned(self)
633 }
634}
635
636macro_rules! into_fragment {
637 ($type:ty) => {
638 impl<'a> IntoFragment<'a> for $type {
639 fn into_fragment(self) -> Fragment<'a> {
640 Fragment::Owned(self.to_string())
641 }
642 }
643
644 impl<'a> IntoFragment<'a> for &$type {
645 fn into_fragment(self) -> Fragment<'a> {
646 Fragment::Owned(self.to_string())
647 }
648 }
649 };
650}
651
652into_fragment!(char);
653into_fragment!(bool);
654
655into_fragment!(u8);
656into_fragment!(u16);
657into_fragment!(u32);
658into_fragment!(u64);
659into_fragment!(u128);
660into_fragment!(usize);
661
662into_fragment!(i8);
663into_fragment!(i16);
664into_fragment!(i32);
665into_fragment!(i64);
666into_fragment!(i128);
667into_fragment!(isize);
668
669into_fragment!(f32);
670into_fragment!(f64);