1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, StyleNode};
5use maycoon_core::signal::MaybeSignal;
6use maycoon_core::vgi::{Brush, Scene};
7use maycoon_core::widget::{Widget, WidgetLayoutExt};
8use maycoon_theme::id::WidgetId;
9use maycoon_theme::theme::Theme;
10use nalgebra::Vector2;
11use std::ops::Deref;
12
13pub struct Text {
24 style: MaybeSignal<LayoutStyle>,
25 text: MaybeSignal<String>,
26 font: MaybeSignal<Option<String>>,
27 font_size: MaybeSignal<f32>,
28 hinting: MaybeSignal<bool>,
29 line_gap: MaybeSignal<f32>,
30}
31
32impl Text {
33 pub fn new(text: impl Into<MaybeSignal<String>>) -> Self {
35 Self {
36 style: LayoutStyle::default().into(),
37 text: text.into(),
38 font: None.into(),
39 font_size: 30.0.into(),
40 hinting: true.into(),
41 line_gap: 7.5.into(),
42 }
43 }
44
45 pub fn with_hinting(mut self, hinting: impl Into<MaybeSignal<bool>>) -> Self {
50 self.hinting = hinting.into();
51 self
52 }
53
54 pub fn with_font(mut self, font: impl Into<MaybeSignal<Option<String>>>) -> Self {
56 self.font = font.into();
57 self
58 }
59
60 pub fn with_font_size(mut self, size: impl Into<MaybeSignal<f32>>) -> Self {
62 self.font_size = size.into();
63 self
64 }
65
66 pub fn with_line_gap(mut self, gap: impl Into<MaybeSignal<f32>>) -> Self {
70 self.line_gap = gap.into();
71 self
72 }
73}
74
75impl WidgetLayoutExt for Text {
76 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
77 self.style = layout_style.into();
78 }
79}
80
81impl Widget for Text {
82 fn render(
83 &mut self,
84 scene: &mut dyn Scene,
85 theme: &mut dyn Theme,
86 layout_node: &LayoutNode,
87 info: &AppInfo,
88 _: AppContext,
89 ) {
90 let font_size = *self.font_size.get();
91 let hinting = *self.hinting.get();
92
93 let font_name = self.font.get();
94
95 let font = if font_name.is_some() {
96 info.font_context
97 .get(font_name.deref().clone().unwrap())
98 .expect("Font not found")
99 } else {
100 info.font_context.default_font().clone()
101 };
102
103 let color = if let Some(style) = theme.of(Self::widget_id(self)) {
104 if theme.globals().invert_text_color {
105 style.get_color("color_invert").unwrap()
106 } else {
107 style.get_color("color").unwrap()
108 }
109 } else {
110 theme.defaults().text().foreground()
111 };
112
113 let line_gap = *self.line_gap.get();
114
115 let text = self.text.get();
116
117 scene.draw_text(
118 &Brush::Solid(color),
119 None,
120 Vector2::new(layout_node.layout.location.x, layout_node.layout.location.y),
121 text.as_str(),
122 hinting,
123 &font,
124 font_size,
125 line_gap,
126 );
127 }
128
129 fn layout_style(&self) -> StyleNode {
130 let text = self.text.get();
131
132 let font_size = *self.font_size.get();
133
134 let style = self.style.get().deref().clone();
135
136 StyleNode {
137 style: LayoutStyle {
138 size: Vector2::new(
139 Dimension::length(font_size * text.len() as f32),
140 Dimension::length(font_size),
141 ),
142 ..style
143 },
144 children: Vec::new(),
145 }
146 }
147
148 fn update(&mut self, _: &LayoutNode, _: AppContext, _: &AppInfo) -> Update {
149 Update::empty()
150 }
151
152 fn widget_id(&self) -> WidgetId {
153 WidgetId::new("maycoon-widgets", "Text")
154 }
155}