1pub mod editor;
3pub mod highlighter;
4pub mod input;
5pub mod paragraph;
6
7pub use editor::Editor;
8pub use highlighter::Highlighter;
9pub use input::Input;
10pub use paragraph::Paragraph;
11
12use crate::alignment;
13use crate::{Background, Border, Color, Padding, Pixels, Point, Rectangle, Size};
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 pub ellipsis: Ellipsis,
50
51 pub hint_factor: Option<f32>,
60}
61
62impl<Content, Font> Text<Content, Font>
63where
64 Font: Copy,
65{
66 pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
69 Text {
70 content,
71 bounds: self.bounds,
72 size: self.size,
73 line_height: self.line_height,
74 font: self.font,
75 align_x: self.align_x,
76 align_y: self.align_y,
77 shaping: self.shaping,
78 wrapping: self.wrapping,
79 ellipsis: self.ellipsis,
80 hint_factor: self.hint_factor,
81 }
82 }
83}
84
85impl<Content, Font> Text<Content, Font>
86where
87 Content: AsRef<str>,
88 Font: Copy,
89{
90 pub fn as_ref(&self) -> Text<&str, Font> {
92 self.with_content(self.content.as_ref())
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
98pub enum Alignment {
99 #[default]
104 Default,
105 Left,
107 Center,
109 Right,
111 Justified,
113}
114
115impl From<alignment::Horizontal> for Alignment {
116 fn from(alignment: alignment::Horizontal) -> Self {
117 match alignment {
118 alignment::Horizontal::Left => Self::Left,
119 alignment::Horizontal::Center => Self::Center,
120 alignment::Horizontal::Right => Self::Right,
121 }
122 }
123}
124
125impl From<crate::Alignment> for Alignment {
126 fn from(alignment: crate::Alignment) -> Self {
127 match alignment {
128 crate::Alignment::Start => Self::Left,
129 crate::Alignment::Center => Self::Center,
130 crate::Alignment::End => Self::Right,
131 }
132 }
133}
134
135impl From<Alignment> for alignment::Horizontal {
136 fn from(alignment: Alignment) -> Self {
137 match alignment {
138 Alignment::Default | Alignment::Left | Alignment::Justified => {
139 alignment::Horizontal::Left
140 }
141 Alignment::Center => alignment::Horizontal::Center,
142 Alignment::Right => alignment::Horizontal::Right,
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
149pub enum Shaping {
150 Auto,
159 Basic,
170 Advanced,
180}
181
182impl Default for Shaping {
183 fn default() -> Self {
184 if cfg!(feature = "advanced-shaping") {
185 Self::Advanced
186 } else if cfg!(feature = "basic-shaping") {
187 Self::Basic
188 } else {
189 Self::Auto
190 }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
196pub enum Wrapping {
197 None,
199 #[default]
203 Word,
204 Glyph,
206 WordOrGlyph,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
211pub enum Ellipsis {
213 #[default]
217 None,
218 Start,
220 Middle,
222 End,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq)]
228pub enum LineHeight {
229 Relative(f32),
231
232 Absolute(Pixels),
234}
235
236impl LineHeight {
237 pub fn to_absolute(self, text_size: impl Into<Pixels>) -> Pixels {
239 match self {
240 Self::Relative(factor) => Pixels(factor * text_size.into().0),
241 Self::Absolute(pixels) => pixels,
242 }
243 }
244}
245
246impl Default for LineHeight {
247 fn default() -> Self {
248 Self::Relative(1.3)
249 }
250}
251
252impl From<f32> for LineHeight {
253 fn from(factor: f32) -> Self {
254 Self::Relative(factor)
255 }
256}
257
258impl From<Pixels> for LineHeight {
259 fn from(pixels: Pixels) -> Self {
260 Self::Absolute(pixels)
261 }
262}
263
264impl Hash for LineHeight {
265 fn hash<H: Hasher>(&self, state: &mut H) {
266 match self {
267 Self::Relative(factor) => {
268 state.write_u8(0);
269 factor.to_bits().hash(state);
270 }
271 Self::Absolute(pixels) => {
272 state.write_u8(1);
273 f32::from(*pixels).to_bits().hash(state);
274 }
275 }
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq)]
281pub enum Hit {
282 CharOffset(usize),
284}
285
286impl Hit {
287 pub fn cursor(self) -> usize {
289 match self {
290 Self::CharOffset(i) => i,
291 }
292 }
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum Difference {
303 None,
307
308 Bounds,
313
314 Shape,
320}
321
322pub trait Renderer: crate::Renderer {
324 type Font: Copy + PartialEq;
326
327 type Paragraph: Paragraph<Font = Self::Font> + 'static;
329
330 type Editor: Editor<Font = Self::Font> + 'static;
332
333 const ICON_FONT: Self::Font;
335
336 const CHECKMARK_ICON: char;
340
341 const ARROW_DOWN_ICON: char;
345
346 const SCROLL_UP_ICON: char;
350
351 const SCROLL_DOWN_ICON: char;
355
356 const SCROLL_LEFT_ICON: char;
360
361 const SCROLL_RIGHT_ICON: char;
365
366 const ICED_LOGO: char;
370
371 fn default_font(&self) -> Self::Font;
373
374 fn default_size(&self) -> Pixels;
376
377 fn fill_paragraph(
380 &mut self,
381 text: &Self::Paragraph,
382 position: Point,
383 color: Color,
384 clip_bounds: Rectangle,
385 );
386
387 fn fill_editor(
390 &mut self,
391 editor: &Self::Editor,
392 position: Point,
393 color: Color,
394 clip_bounds: Rectangle,
395 );
396
397 fn fill_text(
400 &mut self,
401 text: Text<String, Self::Font>,
402 position: Point,
403 color: Color,
404 clip_bounds: Rectangle,
405 );
406}
407
408#[derive(Debug, Clone)]
410pub struct Span<'a, Link = (), Font = crate::Font> {
411 pub text: Fragment<'a>,
413 pub size: Option<Pixels>,
415 pub line_height: Option<LineHeight>,
417 pub font: Option<Font>,
419 pub color: Option<Color>,
421 pub link: Option<Link>,
423 pub highlight: Option<Highlight>,
425 pub padding: Padding,
429 pub underline: bool,
431 pub strikethrough: bool,
433}
434
435#[derive(Debug, Clone, Copy, PartialEq)]
437pub struct Highlight {
438 pub background: Background,
440 pub border: Border,
442}
443
444impl<'a, Link, Font> Span<'a, Link, Font> {
445 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
447 Self {
448 text: fragment.into_fragment(),
449 ..Self::default()
450 }
451 }
452
453 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
455 self.size = Some(size.into());
456 self
457 }
458
459 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
461 self.line_height = Some(line_height.into());
462 self
463 }
464
465 pub fn font(mut self, font: impl Into<Font>) -> Self {
467 self.font = Some(font.into());
468 self
469 }
470
471 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
473 self.font = font.map(Into::into);
474 self
475 }
476
477 pub fn color(mut self, color: impl Into<Color>) -> Self {
479 self.color = Some(color.into());
480 self
481 }
482
483 pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
485 self.color = color.map(Into::into);
486 self
487 }
488
489 pub fn link(mut self, link: impl Into<Link>) -> Self {
491 self.link = Some(link.into());
492 self
493 }
494
495 pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
497 self.link = link.map(Into::into);
498 self
499 }
500
501 pub fn background(self, background: impl Into<Background>) -> Self {
503 self.background_maybe(Some(background))
504 }
505
506 pub fn background_maybe(mut self, background: Option<impl Into<Background>>) -> Self {
508 let Some(background) = background else {
509 return self;
510 };
511
512 match &mut self.highlight {
513 Some(highlight) => {
514 highlight.background = background.into();
515 }
516 None => {
517 self.highlight = Some(Highlight {
518 background: background.into(),
519 border: Border::default(),
520 });
521 }
522 }
523
524 self
525 }
526
527 pub fn border(self, border: impl Into<Border>) -> Self {
529 self.border_maybe(Some(border))
530 }
531
532 pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
534 let Some(border) = border else {
535 return self;
536 };
537
538 match &mut self.highlight {
539 Some(highlight) => {
540 highlight.border = border.into();
541 }
542 None => {
543 self.highlight = Some(Highlight {
544 border: border.into(),
545 background: Background::Color(Color::TRANSPARENT),
546 });
547 }
548 }
549
550 self
551 }
552
553 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
561 self.padding = padding.into();
562 self
563 }
564
565 pub fn underline(mut self, underline: bool) -> Self {
567 self.underline = underline;
568 self
569 }
570
571 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
573 self.strikethrough = strikethrough;
574 self
575 }
576
577 pub fn to_static(self) -> Span<'static, Link, Font> {
579 Span {
580 text: Cow::Owned(self.text.into_owned()),
581 size: self.size,
582 line_height: self.line_height,
583 font: self.font,
584 color: self.color,
585 link: self.link,
586 highlight: self.highlight,
587 padding: self.padding,
588 underline: self.underline,
589 strikethrough: self.strikethrough,
590 }
591 }
592}
593
594impl<Link, Font> Default for Span<'_, Link, Font> {
595 fn default() -> Self {
596 Self {
597 text: Cow::default(),
598 size: None,
599 line_height: None,
600 font: None,
601 color: None,
602 link: None,
603 highlight: None,
604 padding: Padding::default(),
605 underline: false,
606 strikethrough: false,
607 }
608 }
609}
610
611impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
612 fn from(value: &'a str) -> Self {
613 Span::new(value)
614 }
615}
616
617impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
618 fn eq(&self, other: &Self) -> bool {
619 self.text == other.text
620 && self.size == other.size
621 && self.line_height == other.line_height
622 && self.font == other.font
623 && self.color == other.color
624 }
625}
626
627#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
629pub struct Position {
630 pub line: usize,
632
633 pub index: usize,
635}
636
637pub type Fragment<'a> = Cow<'a, str>;
642
643pub trait IntoFragment<'a> {
645 fn into_fragment(self) -> Fragment<'a>;
647}
648
649impl<'a> IntoFragment<'a> for Fragment<'a> {
650 fn into_fragment(self) -> Fragment<'a> {
651 self
652 }
653}
654
655impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
656 fn into_fragment(self) -> Fragment<'a> {
657 Fragment::Borrowed(self)
658 }
659}
660
661impl<'a> IntoFragment<'a> for &'a str {
662 fn into_fragment(self) -> Fragment<'a> {
663 Fragment::Borrowed(self)
664 }
665}
666
667impl<'a> IntoFragment<'a> for &'a String {
668 fn into_fragment(self) -> Fragment<'a> {
669 Fragment::Borrowed(self.as_str())
670 }
671}
672
673impl<'a> IntoFragment<'a> for String {
674 fn into_fragment(self) -> Fragment<'a> {
675 Fragment::Owned(self)
676 }
677}
678
679macro_rules! into_fragment {
680 ($type:ty) => {
681 impl<'a> IntoFragment<'a> for $type {
682 fn into_fragment(self) -> Fragment<'a> {
683 Fragment::Owned(self.to_string())
684 }
685 }
686
687 impl<'a> IntoFragment<'a> for &$type {
688 fn into_fragment(self) -> Fragment<'a> {
689 Fragment::Owned(self.to_string())
690 }
691 }
692 };
693}
694
695into_fragment!(char);
696into_fragment!(bool);
697
698into_fragment!(u8);
699into_fragment!(u16);
700into_fragment!(u32);
701into_fragment!(u64);
702into_fragment!(u128);
703into_fragment!(usize);
704
705into_fragment!(i8);
706into_fragment!(i16);
707into_fragment!(i32);
708into_fragment!(i64);
709into_fragment!(i128);
710into_fragment!(isize);
711
712into_fragment!(f32);
713into_fragment!(f64);