1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, StyleNode};
6use maycoon_core::signal::MaybeSignal;
7use maycoon_core::vgi::{Brush, Scene};
8use maycoon_core::widget::{Widget, WidgetLayoutExt};
9use maycoon_theme::id::WidgetId;
10use maycoon_theme::theme::Theme;
11use nalgebra::Vector2;
12use std::ops::Deref;
13
14pub struct Text {
27 style: MaybeSignal<LayoutStyle>,
28 text: MaybeSignal<String>,
29 font: MaybeSignal<Option<String>>,
30 font_size: MaybeSignal<f32>,
31 line_gap: MaybeSignal<f32>,
32 wrap: MaybeSignal<bool>,
33 hinting: MaybeSignal<bool>,
34 max_width: f32,
35}
36
37impl Text {
38 #[inline(always)]
40 pub fn new(text: impl Into<MaybeSignal<String>>) -> Self {
41 Self {
42 style: LayoutStyle::default().into(),
43 text: text.into(),
44 font: None.into(),
45 font_size: 30.0.into(),
46 line_gap: 7.5.into(),
47 wrap: true.into(),
48 hinting: true.into(),
49 max_width: 0.0,
50 }
51 }
52
53 #[inline(always)]
55 pub fn with_wrap(mut self, linebreaks: impl Into<MaybeSignal<bool>>) -> Self {
56 self.wrap = linebreaks.into();
57 self
58 }
59
60 #[inline(always)]
65 pub fn with_hinting(mut self, hinting: impl Into<MaybeSignal<bool>>) -> Self {
66 self.hinting = hinting.into();
67 self
68 }
69
70 #[inline(always)]
72 pub fn with_font(mut self, font: impl Into<MaybeSignal<Option<String>>>) -> Self {
73 self.font = font.into();
74 self
75 }
76
77 #[inline(always)]
79 pub fn with_font_size(mut self, size: impl Into<MaybeSignal<f32>>) -> Self {
80 self.font_size = size.into();
81 self
82 }
83
84 #[inline(always)]
88 pub fn with_line_gap(mut self, gap: impl Into<MaybeSignal<f32>>) -> Self {
89 self.line_gap = gap.into();
90 self
91 }
92}
93
94impl WidgetLayoutExt for Text {
95 #[inline(always)]
96 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
97 self.style = layout_style.into();
98 }
99}
100
101impl Widget for Text {
102 fn render(
103 &mut self,
104 scene: &mut dyn Scene,
105 theme: &mut dyn Theme,
106 layout_node: &LayoutNode,
107 info: &AppInfo,
108 _: AppContext,
109 ) {
110 let font_name = self.font.get();
111
112 let font = if font_name.is_some() {
113 info.font_context
114 .get(font_name.deref().clone().unwrap())
115 .expect("Font not found")
116 } else {
117 info.font_context.default_font().clone()
118 };
119
120 let color = if let Some(style) = theme.of(Self::widget_id(self)) {
121 if theme.globals().invert_text_color {
122 style.get_color("color_invert").unwrap()
123 } else {
124 style.get_color("color").unwrap()
125 }
126 } else {
127 theme.defaults().text().foreground()
128 };
129
130 if *self.wrap.get() {
131 self.max_width = layout_node.layout.size.width;
132 } else {
133 self.max_width = f32::INFINITY;
134 }
135
136 scene.draw_text(
137 &Brush::Solid(color),
138 None,
139 Vector2::new(layout_node.layout.location.x, layout_node.layout.location.y),
140 self.text.get().as_str(),
141 *self.hinting.get(),
142 &font,
143 *self.font_size.get(),
144 *self.line_gap.get(),
145 self.max_width,
146 );
147 }
148
149 fn layout_style(&self) -> StyleNode {
150 let text = self.text.get();
151
152 let font_size = *self.font_size.get();
153
154 let style = self.style.get().deref().clone();
155
156 StyleNode {
157 style: LayoutStyle {
158 size: Vector2::new(
159 Dimension::length(font_size * text.len() as f32),
160 Dimension::length(font_size),
161 ),
162 ..style
163 },
164 children: Vec::new(),
165 }
166 }
167
168 #[inline(always)]
169 fn update(&mut self, layout: &LayoutNode, _: AppContext, _: &AppInfo) -> Update {
170 if *self.wrap.get() && !layout::equal(layout.layout.size.width, self.max_width) {
172 Update::LAYOUT
173 } else {
174 Update::empty()
175 }
176 }
177
178 #[inline(always)]
179 fn widget_id(&self) -> WidgetId {
180 WidgetId::new("maycoon-widgets", "Text")
181 }
182}