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
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
51pub enum Alignment {
52 #[default]
57 Default,
58 Left,
60 Center,
62 Right,
64 Justified,
66}
67
68impl From<alignment::Horizontal> for Alignment {
69 fn from(alignment: alignment::Horizontal) -> Self {
70 match alignment {
71 alignment::Horizontal::Left => Self::Left,
72 alignment::Horizontal::Center => Self::Center,
73 alignment::Horizontal::Right => Self::Right,
74 }
75 }
76}
77
78impl From<crate::Alignment> for Alignment {
79 fn from(alignment: crate::Alignment) -> Self {
80 match alignment {
81 crate::Alignment::Start => Self::Left,
82 crate::Alignment::Center => Self::Center,
83 crate::Alignment::End => Self::Right,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
90pub enum Shaping {
91 #[default]
101 Basic,
102 Advanced,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
114pub enum Wrapping {
115 None,
117 #[default]
121 Word,
122 Glyph,
124 WordOrGlyph,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq)]
130pub enum LineHeight {
131 Relative(f32),
133
134 Absolute(Pixels),
136}
137
138impl LineHeight {
139 pub fn to_absolute(self, text_size: Pixels) -> Pixels {
141 match self {
142 Self::Relative(factor) => Pixels(factor * text_size.0),
143 Self::Absolute(pixels) => pixels,
144 }
145 }
146}
147
148impl Default for LineHeight {
149 fn default() -> Self {
150 Self::Relative(1.3)
151 }
152}
153
154impl From<f32> for LineHeight {
155 fn from(factor: f32) -> Self {
156 Self::Relative(factor)
157 }
158}
159
160impl From<Pixels> for LineHeight {
161 fn from(pixels: Pixels) -> Self {
162 Self::Absolute(pixels)
163 }
164}
165
166impl Hash for LineHeight {
167 fn hash<H: Hasher>(&self, state: &mut H) {
168 match self {
169 Self::Relative(factor) => {
170 state.write_u8(0);
171 factor.to_bits().hash(state);
172 }
173 Self::Absolute(pixels) => {
174 state.write_u8(1);
175 f32::from(*pixels).to_bits().hash(state);
176 }
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq)]
183pub enum Hit {
184 CharOffset(usize),
186}
187
188impl Hit {
189 pub fn cursor(self) -> usize {
191 match self {
192 Self::CharOffset(i) => i,
193 }
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum Difference {
205 None,
209
210 Bounds,
215
216 Shape,
222}
223
224pub trait Renderer: crate::Renderer {
226 type Font: Copy + PartialEq;
228
229 type Paragraph: Paragraph<Font = Self::Font> + 'static;
231
232 type Editor: Editor<Font = Self::Font> + 'static;
234
235 const ICON_FONT: Self::Font;
237
238 const CHECKMARK_ICON: char;
242
243 const ARROW_DOWN_ICON: char;
247
248 fn default_font(&self) -> Self::Font;
250
251 fn default_size(&self) -> Pixels;
253
254 fn fill_paragraph(
257 &mut self,
258 text: &Self::Paragraph,
259 position: Point,
260 color: Color,
261 clip_bounds: Rectangle,
262 );
263
264 fn fill_editor(
267 &mut self,
268 editor: &Self::Editor,
269 position: Point,
270 color: Color,
271 clip_bounds: Rectangle,
272 );
273
274 fn fill_text(
277 &mut self,
278 text: Text<String, Self::Font>,
279 position: Point,
280 color: Color,
281 clip_bounds: Rectangle,
282 );
283}
284
285#[derive(Debug, Clone)]
287pub struct Span<'a, Link = (), Font = crate::Font> {
288 pub text: Fragment<'a>,
290 pub size: Option<Pixels>,
292 pub line_height: Option<LineHeight>,
294 pub font: Option<Font>,
296 pub color: Option<Color>,
298 pub link: Option<Link>,
300 pub highlight: Option<Highlight>,
302 pub padding: Padding,
306 pub underline: bool,
308 pub strikethrough: bool,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq)]
314pub struct Highlight {
315 pub background: Background,
317 pub border: Border,
319}
320
321impl<'a, Link, Font> Span<'a, Link, Font> {
322 pub fn new(fragment: impl IntoFragment<'a>) -> Self {
324 Self {
325 text: fragment.into_fragment(),
326 ..Self::default()
327 }
328 }
329
330 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
332 self.size = Some(size.into());
333 self
334 }
335
336 pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
338 self.line_height = Some(line_height.into());
339 self
340 }
341
342 pub fn font(mut self, font: impl Into<Font>) -> Self {
344 self.font = Some(font.into());
345 self
346 }
347
348 pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
350 self.font = font.map(Into::into);
351 self
352 }
353
354 pub fn color(mut self, color: impl Into<Color>) -> Self {
356 self.color = Some(color.into());
357 self
358 }
359
360 pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
362 self.color = color.map(Into::into);
363 self
364 }
365
366 pub fn link(mut self, link: impl Into<Link>) -> Self {
368 self.link = Some(link.into());
369 self
370 }
371
372 pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
374 self.link = link.map(Into::into);
375 self
376 }
377
378 pub fn background(self, background: impl Into<Background>) -> Self {
380 self.background_maybe(Some(background))
381 }
382
383 pub fn background_maybe(
385 mut self,
386 background: Option<impl Into<Background>>,
387 ) -> Self {
388 let Some(background) = background else {
389 return self;
390 };
391
392 match &mut self.highlight {
393 Some(highlight) => {
394 highlight.background = background.into();
395 }
396 None => {
397 self.highlight = Some(Highlight {
398 background: background.into(),
399 border: Border::default(),
400 });
401 }
402 }
403
404 self
405 }
406
407 pub fn border(self, border: impl Into<Border>) -> Self {
409 self.border_maybe(Some(border))
410 }
411
412 pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
414 let Some(border) = border else {
415 return self;
416 };
417
418 match &mut self.highlight {
419 Some(highlight) => {
420 highlight.border = border.into();
421 }
422 None => {
423 self.highlight = Some(Highlight {
424 border: border.into(),
425 background: Background::Color(Color::TRANSPARENT),
426 });
427 }
428 }
429
430 self
431 }
432
433 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
441 self.padding = padding.into();
442 self
443 }
444
445 pub fn underline(mut self, underline: bool) -> Self {
447 self.underline = underline;
448 self
449 }
450
451 pub fn strikethrough(mut self, strikethrough: bool) -> Self {
453 self.strikethrough = strikethrough;
454 self
455 }
456
457 pub fn to_static(self) -> Span<'static, Link, Font> {
459 Span {
460 text: Cow::Owned(self.text.into_owned()),
461 size: self.size,
462 line_height: self.line_height,
463 font: self.font,
464 color: self.color,
465 link: self.link,
466 highlight: self.highlight,
467 padding: self.padding,
468 underline: self.underline,
469 strikethrough: self.strikethrough,
470 }
471 }
472}
473
474impl<Link, Font> Default for Span<'_, Link, Font> {
475 fn default() -> Self {
476 Self {
477 text: Cow::default(),
478 size: None,
479 line_height: None,
480 font: None,
481 color: None,
482 link: None,
483 highlight: None,
484 padding: Padding::default(),
485 underline: false,
486 strikethrough: false,
487 }
488 }
489}
490
491impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
492 fn from(value: &'a str) -> Self {
493 Span::new(value)
494 }
495}
496
497impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
498 fn eq(&self, other: &Self) -> bool {
499 self.text == other.text
500 && self.size == other.size
501 && self.line_height == other.line_height
502 && self.font == other.font
503 && self.color == other.color
504 }
505}
506
507pub type Fragment<'a> = Cow<'a, str>;
512
513pub trait IntoFragment<'a> {
515 fn into_fragment(self) -> Fragment<'a>;
517}
518
519impl<'a> IntoFragment<'a> for Fragment<'a> {
520 fn into_fragment(self) -> Fragment<'a> {
521 self
522 }
523}
524
525impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
526 fn into_fragment(self) -> Fragment<'a> {
527 Fragment::Borrowed(self)
528 }
529}
530
531impl<'a> IntoFragment<'a> for &'a str {
532 fn into_fragment(self) -> Fragment<'a> {
533 Fragment::Borrowed(self)
534 }
535}
536
537impl<'a> IntoFragment<'a> for &'a String {
538 fn into_fragment(self) -> Fragment<'a> {
539 Fragment::Borrowed(self.as_str())
540 }
541}
542
543impl<'a> IntoFragment<'a> for String {
544 fn into_fragment(self) -> Fragment<'a> {
545 Fragment::Owned(self)
546 }
547}
548
549macro_rules! into_fragment {
550 ($type:ty) => {
551 impl<'a> IntoFragment<'a> for $type {
552 fn into_fragment(self) -> Fragment<'a> {
553 Fragment::Owned(self.to_string())
554 }
555 }
556
557 impl<'a> IntoFragment<'a> for &$type {
558 fn into_fragment(self) -> Fragment<'a> {
559 Fragment::Owned(self.to_string())
560 }
561 }
562 };
563}
564
565into_fragment!(char);
566into_fragment!(bool);
567
568into_fragment!(u8);
569into_fragment!(u16);
570into_fragment!(u32);
571into_fragment!(u64);
572into_fragment!(u128);
573into_fragment!(usize);
574
575into_fragment!(i8);
576into_fragment!(i16);
577into_fragment!(i32);
578into_fragment!(i64);
579into_fragment!(i128);
580into_fragment!(isize);
581
582into_fragment!(f32);
583into_fragment!(f64);