1use crate::theme::Theme;
2use crate::tokens::mix;
3use egui::{
4 Align, Color32, CornerRadius, FontFamily, FontId, Frame, Response, RichText, Sense, Stroke, Ui,
5 WidgetText, pos2, vec2,
6};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum TextAs {
10 Span,
11 Div,
12 Label,
13 P,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub enum TextWeight {
18 Light,
19 Regular,
20 Medium,
21 Bold,
22 ExtraBold,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum TextAlign {
27 Left,
28 Center,
29 Right,
30}
31
32impl TextAlign {
33 fn to_egui(self) -> Align {
34 match self {
35 TextAlign::Left => Align::Min,
36 TextAlign::Center => Align::Center,
37 TextAlign::Right => Align::Max,
38 }
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum TextTrim {
44 Normal,
45 Start,
46 End,
47 Both,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum TextWrap {
52 Wrap,
53 NoWrap,
54 Pretty,
55 Balance,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum TypographyColor {
60 Default,
61 Muted,
62 Primary,
63}
64
65impl TypographyColor {
66 fn resolve(self, theme: &Theme, high_contrast: bool) -> Color32 {
67 match self {
68 TypographyColor::Default => theme.palette.foreground,
69 TypographyColor::Muted => {
70 if high_contrast {
71 mix(
72 theme.palette.muted_foreground,
73 theme.palette.foreground,
74 0.25,
75 )
76 } else {
77 theme.palette.muted_foreground
78 }
79 }
80 TypographyColor::Primary => theme.palette.primary,
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
86pub enum HeadingAs {
87 H1,
88 H2,
89 H3,
90 H4,
91 H5,
92 H6,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum LinkUnderline {
97 Auto,
98 Always,
99 Hover,
100 None,
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub enum CodeVariant {
105 Solid,
106 Soft,
107 Outline,
108 Ghost,
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub enum ShadcnTypographyVariant {
113 H1,
114 H2,
115 H3,
116 H4,
117 P,
118 Lead,
119 Large,
120 Small,
121 Muted,
122 InlineCode,
123 Blockquote,
124}
125
126#[derive(Clone, Debug)]
127pub struct TextProps {
128 pub text: WidgetText,
129 pub as_tag: TextAs,
130 pub size: Option<f32>,
131 pub weight: Option<TextWeight>,
132 pub align: Option<TextAlign>,
133 pub trim: Option<TextTrim>,
134 pub truncate: bool,
135 pub wrap: Option<TextWrap>,
136 pub color: Option<TypographyColor>,
137 pub high_contrast: bool,
138 pub italic: bool,
139 pub monospace: bool,
140 pub underline: bool,
141}
142
143impl TextProps {
144 pub fn new(text: impl Into<WidgetText>) -> Self {
145 Self {
146 text: text.into(),
147 as_tag: TextAs::Span,
148 size: None,
149 weight: None,
150 align: None,
151 trim: None,
152 truncate: false,
153 wrap: None,
154 color: None,
155 high_contrast: false,
156 italic: false,
157 monospace: false,
158 underline: false,
159 }
160 }
161
162 pub fn as_tag(mut self, as_tag: TextAs) -> Self {
163 self.as_tag = as_tag;
164 self
165 }
166
167 pub fn size(mut self, size: f32) -> Self {
168 self.size = Some(size);
169 self
170 }
171
172 pub fn weight(mut self, weight: TextWeight) -> Self {
173 self.weight = Some(weight);
174 self
175 }
176
177 pub fn align(mut self, align: TextAlign) -> Self {
178 self.align = Some(align);
179 self
180 }
181
182 pub fn trim(mut self, trim: TextTrim) -> Self {
183 self.trim = Some(trim);
184 self
185 }
186
187 pub fn truncate(mut self, truncate: bool) -> Self {
188 self.truncate = truncate;
189 self
190 }
191
192 pub fn wrap(mut self, wrap: TextWrap) -> Self {
193 self.wrap = Some(wrap);
194 self
195 }
196
197 pub fn color(mut self, color: TypographyColor) -> Self {
198 self.color = Some(color);
199 self
200 }
201
202 pub fn high_contrast(mut self, high_contrast: bool) -> Self {
203 self.high_contrast = high_contrast;
204 self
205 }
206
207 pub fn italic(mut self, italic: bool) -> Self {
208 self.italic = italic;
209 self
210 }
211
212 pub fn monospace(mut self, monospace: bool) -> Self {
213 self.monospace = monospace;
214 self
215 }
216
217 pub fn underline(mut self, underline: bool) -> Self {
218 self.underline = underline;
219 self
220 }
221}
222
223#[derive(Clone, Debug)]
224pub struct HeadingProps {
225 pub text: WidgetText,
226 pub as_tag: HeadingAs,
227 pub size: Option<f32>,
228 pub weight: Option<TextWeight>,
229 pub align: Option<TextAlign>,
230 pub trim: Option<TextTrim>,
231 pub truncate: bool,
232 pub wrap: Option<TextWrap>,
233 pub color: Option<TypographyColor>,
234 pub high_contrast: bool,
235}
236
237impl HeadingProps {
238 pub fn new(text: impl Into<WidgetText>) -> Self {
239 Self {
240 text: text.into(),
241 as_tag: HeadingAs::H1,
242 size: Some(30.0),
243 weight: None,
244 align: None,
245 trim: None,
246 truncate: false,
247 wrap: None,
248 color: None,
249 high_contrast: false,
250 }
251 }
252
253 pub fn as_tag(mut self, as_tag: HeadingAs) -> Self {
254 self.as_tag = as_tag;
255 self
256 }
257
258 pub fn size(mut self, size: f32) -> Self {
259 self.size = Some(size);
260 self
261 }
262
263 pub fn weight(mut self, weight: TextWeight) -> Self {
264 self.weight = Some(weight);
265 self
266 }
267
268 pub fn align(mut self, align: TextAlign) -> Self {
269 self.align = Some(align);
270 self
271 }
272
273 pub fn trim(mut self, trim: TextTrim) -> Self {
274 self.trim = Some(trim);
275 self
276 }
277
278 pub fn truncate(mut self, truncate: bool) -> Self {
279 self.truncate = truncate;
280 self
281 }
282
283 pub fn wrap(mut self, wrap: TextWrap) -> Self {
284 self.wrap = Some(wrap);
285 self
286 }
287
288 pub fn color(mut self, color: TypographyColor) -> Self {
289 self.color = Some(color);
290 self
291 }
292
293 pub fn high_contrast(mut self, high_contrast: bool) -> Self {
294 self.high_contrast = high_contrast;
295 self
296 }
297}
298
299#[derive(Clone, Debug)]
300pub struct LinkProps {
301 pub text: WidgetText,
302 pub underline: LinkUnderline,
303 pub size: Option<f32>,
304 pub weight: Option<TextWeight>,
305 pub trim: Option<TextTrim>,
306 pub truncate: bool,
307 pub wrap: Option<TextWrap>,
308 pub color: Option<TypographyColor>,
309 pub high_contrast: bool,
310}
311
312impl LinkProps {
313 pub fn new(text: impl Into<WidgetText>) -> Self {
314 Self {
315 text: text.into(),
316 underline: LinkUnderline::Auto,
317 size: None,
318 weight: None,
319 trim: None,
320 truncate: false,
321 wrap: None,
322 color: Some(TypographyColor::Primary),
323 high_contrast: false,
324 }
325 }
326
327 pub fn underline(mut self, underline: LinkUnderline) -> Self {
328 self.underline = underline;
329 self
330 }
331
332 pub fn size(mut self, size: f32) -> Self {
333 self.size = Some(size);
334 self
335 }
336
337 pub fn weight(mut self, weight: TextWeight) -> Self {
338 self.weight = Some(weight);
339 self
340 }
341
342 pub fn trim(mut self, trim: TextTrim) -> Self {
343 self.trim = Some(trim);
344 self
345 }
346
347 pub fn truncate(mut self, truncate: bool) -> Self {
348 self.truncate = truncate;
349 self
350 }
351
352 pub fn wrap(mut self, wrap: TextWrap) -> Self {
353 self.wrap = Some(wrap);
354 self
355 }
356
357 pub fn color(mut self, color: TypographyColor) -> Self {
358 self.color = Some(color);
359 self
360 }
361
362 pub fn high_contrast(mut self, high_contrast: bool) -> Self {
363 self.high_contrast = high_contrast;
364 self
365 }
366}
367
368#[derive(Clone, Debug)]
369pub struct CodeProps {
370 pub text: WidgetText,
371 pub variant: CodeVariant,
372 pub size: Option<f32>,
373 pub weight: Option<TextWeight>,
374 pub color: Option<TypographyColor>,
375 pub high_contrast: bool,
376 pub truncate: bool,
377 pub wrap: Option<TextWrap>,
378}
379
380impl CodeProps {
381 pub fn new(text: impl Into<WidgetText>) -> Self {
382 Self {
383 text: text.into(),
384 variant: CodeVariant::Soft,
385 size: None,
386 weight: None,
387 color: None,
388 high_contrast: false,
389 truncate: false,
390 wrap: None,
391 }
392 }
393
394 pub fn variant(mut self, variant: CodeVariant) -> Self {
395 self.variant = variant;
396 self
397 }
398
399 pub fn size(mut self, size: f32) -> Self {
400 self.size = Some(size);
401 self
402 }
403
404 pub fn weight(mut self, weight: TextWeight) -> Self {
405 self.weight = Some(weight);
406 self
407 }
408
409 pub fn color(mut self, color: TypographyColor) -> Self {
410 self.color = Some(color);
411 self
412 }
413
414 pub fn high_contrast(mut self, high_contrast: bool) -> Self {
415 self.high_contrast = high_contrast;
416 self
417 }
418
419 pub fn truncate(mut self, truncate: bool) -> Self {
420 self.truncate = truncate;
421 self
422 }
423
424 pub fn wrap(mut self, wrap: TextWrap) -> Self {
425 self.wrap = Some(wrap);
426 self
427 }
428}
429
430#[derive(Clone, Debug)]
431pub struct BlockquoteProps {
432 pub text: WidgetText,
433 pub size: Option<f32>,
434 pub high_contrast: bool,
435 pub truncate: bool,
436 pub wrap: Option<TextWrap>,
437}
438
439impl BlockquoteProps {
440 pub fn new(text: impl Into<WidgetText>) -> Self {
441 Self {
442 text: text.into(),
443 size: None,
444 high_contrast: false,
445 truncate: false,
446 wrap: None,
447 }
448 }
449
450 pub fn size(mut self, size: f32) -> Self {
451 self.size = Some(size);
452 self
453 }
454
455 pub fn high_contrast(mut self, high_contrast: bool) -> Self {
456 self.high_contrast = high_contrast;
457 self
458 }
459
460 pub fn truncate(mut self, truncate: bool) -> Self {
461 self.truncate = truncate;
462 self
463 }
464
465 pub fn wrap(mut self, wrap: TextWrap) -> Self {
466 self.wrap = Some(wrap);
467 self
468 }
469}
470
471#[derive(Clone, Debug)]
472pub struct TypographyProps {
473 pub text: WidgetText,
474 pub variant: ShadcnTypographyVariant,
475 pub align: Option<TextAlign>,
476}
477
478impl TypographyProps {
479 pub fn new(text: impl Into<WidgetText>) -> Self {
480 Self {
481 text: text.into(),
482 variant: ShadcnTypographyVariant::P,
483 align: None,
484 }
485 }
486
487 pub fn variant(mut self, variant: ShadcnTypographyVariant) -> Self {
488 self.variant = variant;
489 self
490 }
491
492 pub fn align(mut self, align: TextAlign) -> Self {
493 self.align = Some(align);
494 self
495 }
496}
497
498#[derive(Clone, Copy, Debug, PartialEq)]
499pub struct ResolvedTextStyle {
500 pub size: f32,
501 pub monospace: bool,
502 pub strong: bool,
503 pub italic: bool,
504 pub underline: bool,
505 pub color: TypographyColor,
506}
507
508pub fn resolve_shadcn_style(variant: ShadcnTypographyVariant) -> ResolvedTextStyle {
509 match variant {
510 ShadcnTypographyVariant::H1 => ResolvedTextStyle {
511 size: 36.0,
512 monospace: false,
513 strong: true,
514 italic: false,
515 underline: false,
516 color: TypographyColor::Default,
517 },
518 ShadcnTypographyVariant::H2 => ResolvedTextStyle {
519 size: 30.0,
520 monospace: false,
521 strong: true,
522 italic: false,
523 underline: false,
524 color: TypographyColor::Default,
525 },
526 ShadcnTypographyVariant::H3 => ResolvedTextStyle {
527 size: 24.0,
528 monospace: false,
529 strong: true,
530 italic: false,
531 underline: false,
532 color: TypographyColor::Default,
533 },
534 ShadcnTypographyVariant::H4 => ResolvedTextStyle {
535 size: 20.0,
536 monospace: false,
537 strong: true,
538 italic: false,
539 underline: false,
540 color: TypographyColor::Default,
541 },
542 ShadcnTypographyVariant::Lead => ResolvedTextStyle {
543 size: 20.0,
544 monospace: false,
545 strong: false,
546 italic: false,
547 underline: false,
548 color: TypographyColor::Muted,
549 },
550 ShadcnTypographyVariant::Large => ResolvedTextStyle {
551 size: 18.0,
552 monospace: false,
553 strong: true,
554 italic: false,
555 underline: false,
556 color: TypographyColor::Default,
557 },
558 ShadcnTypographyVariant::Small => ResolvedTextStyle {
559 size: 14.0,
560 monospace: false,
561 strong: true,
562 italic: false,
563 underline: false,
564 color: TypographyColor::Default,
565 },
566 ShadcnTypographyVariant::Muted => ResolvedTextStyle {
567 size: 14.0,
568 monospace: false,
569 strong: false,
570 italic: false,
571 underline: false,
572 color: TypographyColor::Muted,
573 },
574 ShadcnTypographyVariant::InlineCode => ResolvedTextStyle {
575 size: 14.0,
576 monospace: true,
577 strong: true,
578 italic: false,
579 underline: false,
580 color: TypographyColor::Default,
581 },
582 ShadcnTypographyVariant::Blockquote => ResolvedTextStyle {
583 size: 14.0,
584 monospace: false,
585 strong: false,
586 italic: true,
587 underline: false,
588 color: TypographyColor::Default,
589 },
590 ShadcnTypographyVariant::P => ResolvedTextStyle {
591 size: 14.0,
592 monospace: false,
593 strong: false,
594 italic: false,
595 underline: false,
596 color: TypographyColor::Default,
597 },
598 }
599}
600
601fn widget_text_to_plain(text: WidgetText) -> String {
602 match text {
603 WidgetText::Text(t) => t,
604 WidgetText::RichText(rt) => rt.text().to_string(),
605 WidgetText::Galley(g) => g.text().to_string(),
606 WidgetText::LayoutJob(job) => job.text.to_string(),
607 }
608}
609
610#[derive(Clone, Copy, Debug, Default)]
611struct TextStyleOptions {
612 size: Option<f32>,
613 weight: Option<TextWeight>,
614 italic: bool,
615 monospace: bool,
616 underline: bool,
617 color: Option<TypographyColor>,
618 high_contrast: bool,
619}
620
621fn resolve_text_style(theme: &Theme, opts: TextStyleOptions) -> ResolvedTextStyle {
622 let size = opts.size.unwrap_or(14.0);
623 let strong = matches!(
624 opts.weight.unwrap_or(TextWeight::Regular),
625 TextWeight::Medium | TextWeight::Bold | TextWeight::ExtraBold
626 );
627 let mut resolved = ResolvedTextStyle {
628 size,
629 monospace: opts.monospace,
630 strong,
631 italic: opts.italic,
632 underline: opts.underline,
633 color: opts.color.unwrap_or(TypographyColor::Default),
634 };
635 if opts.high_contrast && resolved.color == TypographyColor::Muted {
636 resolved.color = TypographyColor::Default;
637 }
638 let _ = theme;
639 resolved
640}
641
642fn label_for_text(
643 ui: &mut Ui,
644 rich: RichText,
645 align: Option<TextAlign>,
646 wrap: Option<TextWrap>,
647) -> Response {
648 let wrap = wrap.unwrap_or(TextWrap::Wrap);
649 let mut label = egui::Label::new(rich);
650 if !matches!(wrap, TextWrap::NoWrap) {
651 label = label.wrap();
652 }
653 if let Some(align) = align {
654 ui.with_layout(egui::Layout::top_down(align.to_egui()), |ui| ui.add(label))
655 .inner
656 } else {
657 ui.add(label)
658 }
659}
660
661pub fn text(ui: &mut Ui, theme: &Theme, props: TextProps) -> Response {
662 let resolved = resolve_text_style(
663 theme,
664 TextStyleOptions {
665 size: props.size,
666 weight: props.weight,
667 italic: props.italic,
668 monospace: props.monospace,
669 underline: props.underline,
670 color: props.color,
671 high_contrast: props.high_contrast,
672 },
673 );
674
675 let color = resolved.color.resolve(theme, props.high_contrast);
676 let mut rich = RichText::new(widget_text_to_plain(props.text));
677 rich = rich.color(color);
678 rich = rich.font(FontId::new(
679 resolved.size,
680 if resolved.monospace {
681 FontFamily::Monospace
682 } else {
683 FontFamily::Proportional
684 },
685 ));
686 if resolved.strong {
687 rich = rich.strong();
688 }
689 if resolved.italic {
690 rich = rich.italics();
691 }
692 if resolved.underline {
693 rich = rich.underline();
694 }
695
696 let _ = props.as_tag;
697 let _ = props.trim;
698 let _ = props.truncate;
699
700 label_for_text(ui, rich, props.align, props.wrap)
701}
702
703pub fn heading(ui: &mut Ui, theme: &Theme, props: HeadingProps) -> Response {
704 let mut resolved = resolve_text_style(
705 theme,
706 TextStyleOptions {
707 size: props.size,
708 weight: props.weight,
709 color: props.color,
710 high_contrast: props.high_contrast,
711 ..Default::default()
712 },
713 );
714 if !resolved.strong {
715 resolved.strong = true;
716 }
717
718 let color = resolved.color.resolve(theme, props.high_contrast);
719 let rich = RichText::new(widget_text_to_plain(props.text))
720 .color(color)
721 .font(FontId::proportional(resolved.size))
722 .strong();
723
724 let _ = props.as_tag;
725 let _ = props.trim;
726 let _ = props.truncate;
727
728 label_for_text(ui, rich, props.align, props.wrap)
729}
730
731pub fn link(ui: &mut Ui, theme: &Theme, props: LinkProps) -> Response {
732 let resolved = resolve_text_style(
733 theme,
734 TextStyleOptions {
735 size: props.size,
736 weight: props.weight,
737 color: props.color,
738 high_contrast: props.high_contrast,
739 ..Default::default()
740 },
741 );
742 let base_color = resolved.color.resolve(theme, props.high_contrast);
743 let mut rich = RichText::new(widget_text_to_plain(props.text))
744 .color(base_color)
745 .font(FontId::proportional(resolved.size));
746 if resolved.strong {
747 rich = rich.strong();
748 }
749
750 let base = ui.add(egui::Label::new(rich).sense(Sense::click()).wrap());
751 let underline = match props.underline {
752 LinkUnderline::None => false,
753 LinkUnderline::Always => true,
754 LinkUnderline::Hover => base.hovered(),
755 LinkUnderline::Auto => base.hovered(),
756 };
757 if underline {
758 let painter = ui.painter();
759 let y = base.rect.bottom() - 1.0;
760 painter.line_segment(
761 [pos2(base.rect.left(), y), pos2(base.rect.right(), y)],
762 Stroke::new(1.0, base_color),
763 );
764 }
765
766 let _ = props.trim;
767 let _ = props.truncate;
768 let _ = props.wrap;
769
770 base
771}
772
773pub fn code(ui: &mut Ui, theme: &Theme, props: CodeProps) -> Response {
774 let resolved = resolve_text_style(
775 theme,
776 TextStyleOptions {
777 size: props.size.or(Some(14.0)),
778 weight: props.weight.or(Some(TextWeight::Bold)),
779 monospace: true,
780 color: props.color,
781 high_contrast: props.high_contrast,
782 ..Default::default()
783 },
784 );
785 let fg = resolved.color.resolve(theme, props.high_contrast);
786 let rounding = CornerRadius::same(4);
787
788 let (fill, stroke) = match props.variant {
789 CodeVariant::Soft => (theme.palette.muted, Stroke::NONE),
790 CodeVariant::Solid => (theme.palette.primary, Stroke::NONE),
791 CodeVariant::Outline => (Color32::TRANSPARENT, Stroke::new(1.0, theme.palette.border)),
792 CodeVariant::Ghost => (Color32::TRANSPARENT, Stroke::NONE),
793 };
794
795 let rich = RichText::new(widget_text_to_plain(props.text))
796 .font(FontId::new(resolved.size, FontFamily::Monospace))
797 .color(match props.variant {
798 CodeVariant::Solid => theme.palette.primary_foreground,
799 _ => fg,
800 })
801 .strong();
802
803 let inner_margin = vec2(6.0, 4.0);
804 let frame = Frame::NONE
805 .fill(fill)
806 .stroke(stroke)
807 .corner_radius(rounding)
808 .inner_margin(inner_margin);
809
810 let response = frame
811 .show(ui, |ui| ui.add(egui::Label::new(rich).wrap()))
812 .inner;
813
814 let _ = props.truncate;
815 let _ = props.wrap;
816
817 response
818}
819
820pub fn blockquote(ui: &mut Ui, theme: &Theme, props: BlockquoteProps) -> Response {
821 let size = props.size.unwrap_or(14.0);
822 let fg = theme.palette.foreground;
823 let rich = RichText::new(widget_text_to_plain(props.text))
824 .font(FontId::proportional(size))
825 .color(fg)
826 .italics();
827
828 let indent = 24.0;
829 let border_width = 2.0;
830 let response = ui
831 .horizontal(|ui| {
832 ui.add_space(indent);
833 ui.add(egui::Label::new(rich).wrap())
834 })
835 .inner;
836 let border_x = response.rect.left() - indent + border_width * 0.5;
837 ui.painter().line_segment(
838 [
839 pos2(border_x, response.rect.top()),
840 pos2(border_x, response.rect.bottom()),
841 ],
842 Stroke::new(border_width, theme.palette.border),
843 );
844
845 let _ = props.truncate;
846 let _ = props.wrap;
847
848 response
849}
850
851pub fn typography(ui: &mut Ui, theme: &Theme, props: TypographyProps) -> Response {
852 match props.variant {
853 ShadcnTypographyVariant::InlineCode => code(
854 ui,
855 theme,
856 CodeProps::new(props.text).variant(CodeVariant::Soft),
857 ),
858 ShadcnTypographyVariant::Blockquote => {
859 blockquote(ui, theme, BlockquoteProps::new(props.text))
860 }
861 variant => {
862 let resolved = resolve_shadcn_style(variant);
863 let color = resolved.color.resolve(theme, false);
864 let mut rich = RichText::new(widget_text_to_plain(props.text))
865 .font(FontId::new(
866 resolved.size,
867 if resolved.monospace {
868 FontFamily::Monospace
869 } else {
870 FontFamily::Proportional
871 },
872 ))
873 .color(color);
874 if resolved.strong {
875 rich = rich.strong();
876 }
877 if resolved.italic {
878 rich = rich.italics();
879 }
880 if resolved.underline {
881 rich = rich.underline();
882 }
883
884 let response = label_for_text(ui, rich, props.align, Some(TextWrap::Wrap));
885 if variant == ShadcnTypographyVariant::H2 {
886 let padding_bottom = 8.0;
887 ui.add_space(padding_bottom);
888 let y = response.rect.bottom() + padding_bottom;
889 ui.painter().line_segment(
890 [
891 pos2(response.rect.left(), y),
892 pos2(response.rect.right(), y),
893 ],
894 Stroke::new(1.0, theme.palette.border),
895 );
896 }
897 response
898 }
899 }
900}