1use std::{
4 borrow::Cow,
5 fmt::{Arguments, Write},
6};
7
8use ravel::{Builder, State};
9use web_sys::wasm_bindgen::UnwrapThrowExt;
10
11use crate::{BuildCx, RebuildCx, ViewMarker, Web};
12
13pub struct Text<Value: ToString + AsRef<str>> {
15 value: Value,
16}
17
18impl<Value: ToString + AsRef<str>> Builder<Web> for Text<Value> {
19 type State = TextState<String>;
20
21 fn build(self, cx: BuildCx) -> Self::State {
22 let node =
23 web_sys::Text::new_with_data(self.value.as_ref()).unwrap_throw();
24
25 cx.position.insert(&node);
26
27 TextState {
28 node,
29 value: self.value.to_string(),
30 }
31 }
32
33 fn rebuild(self, _: RebuildCx, state: &mut Self::State) {
34 if state.value != self.value.as_ref() {
35 state.node.set_data(self.value.as_ref());
36 state.value = self.value.to_string();
37 }
38 }
39}
40
41pub struct TextState<Value> {
43 node: web_sys::Text,
44 value: Value,
45}
46
47impl<Output, Value: 'static> State<Output> for TextState<Value> {
48 fn run(&mut self, _: &mut Output) {}
49}
50
51impl<Value> ViewMarker for TextState<Value> {}
52
53pub fn text<V: ToString + AsRef<str>>(value: V) -> Text<V> {
55 Text { value }
56}
57
58impl Builder<Web> for &'static str {
59 type State = TextState<Self>;
60
61 fn build(self, cx: BuildCx) -> Self::State {
62 let node = web_sys::Text::new_with_data(self).unwrap_throw();
63
64 cx.position.insert(&node);
65
66 TextState { node, value: self }
67 }
68
69 fn rebuild(self, _: RebuildCx, state: &mut Self::State) {
70 if !std::ptr::eq(self, state.value) {
71 state.node.set_data(self);
72 state.value = self;
73 }
74 }
75}
76
77macro_rules! make_builder_web_to_string {
78 ($t:ty) => {
79 impl Builder<Web> for $t {
80 type State = TextState<Self>;
81
82 fn build(self, cx: BuildCx) -> Self::State {
83 let data = self.to_string();
84
85 let node = web_sys::Text::new_with_data(&data).unwrap_throw();
86 cx.position.insert(&node);
87
88 TextState { node, value: self }
89 }
90
91 fn rebuild(self, _: RebuildCx, state: &mut Self::State) {
92 if self == state.value {
93 return;
94 }
95
96 state.node.set_data(&self.to_string());
97 state.value = self.clone();
98 }
99 }
100 };
101}
102
103make_builder_web_to_string!(char);
104make_builder_web_to_string!(f32);
105make_builder_web_to_string!(f64);
106make_builder_web_to_string!(i128);
107make_builder_web_to_string!(i16);
108make_builder_web_to_string!(i32);
109make_builder_web_to_string!(i64);
110make_builder_web_to_string!(i8);
111make_builder_web_to_string!(isize);
112make_builder_web_to_string!(u128);
113make_builder_web_to_string!(u16);
114make_builder_web_to_string!(u32);
115make_builder_web_to_string!(u64);
116make_builder_web_to_string!(u8);
117make_builder_web_to_string!(usize);
118
119pub struct Display<T: ToString + PartialEq + Clone> {
121 value: T,
122}
123
124impl<T: 'static + ToString + PartialEq + Clone> Builder<Web> for Display<T> {
125 type State = DisplayState<T>;
126
127 fn build(self, cx: BuildCx<'_>) -> Self::State {
128 let data = self.value.to_string();
129
130 let node = web_sys::Text::new_with_data(&data).unwrap_throw();
131 cx.position.insert(&node);
132
133 DisplayState {
134 node,
135 value: self.value.clone(),
136 }
137 }
138
139 fn rebuild(self, _: RebuildCx<'_>, state: &mut Self::State) {
140 if self.value == state.value {
141 return;
142 }
143
144 state.node.set_data(&self.value.to_string());
145 state.value = self.value.clone();
146 }
147}
148
149pub struct DisplayRef<'a, T: ToString + PartialEq + Clone> {
151 value: &'a T,
152}
153
154impl<'a, T: 'static + ToString + PartialEq + Clone> Builder<Web>
155 for DisplayRef<'a, T>
156{
157 type State = DisplayState<T>;
158
159 fn build(self, cx: BuildCx<'_>) -> Self::State {
160 let data = self.value.to_string();
161
162 let node = web_sys::Text::new_with_data(&data).unwrap_throw();
163 cx.position.insert(&node);
164
165 DisplayState {
166 node,
167 value: self.value.clone(),
168 }
169 }
170
171 fn rebuild(self, _: RebuildCx<'_>, state: &mut Self::State) {
172 if *self.value == state.value {
173 return;
174 }
175
176 state.node.set_data(&self.value.to_string());
177 state.value = self.value.clone();
178 }
179}
180
181pub struct DisplayState<T: ToString + PartialEq> {
183 node: web_sys::Text,
184 value: T,
185}
186
187impl<T: 'static + ToString + PartialEq, Output> State<Output>
188 for DisplayState<T>
189{
190 fn run(&mut self, _: &mut Output) {}
191}
192
193impl<T: ToString + PartialEq> ViewMarker for DisplayState<T> {}
194
195pub fn display<T: ToString + PartialEq + Clone>(value: T) -> Display<T> {
197 Display { value }
198}
199
200pub fn display_ref<T: ToString + PartialEq + Clone>(
202 value: &T,
203) -> DisplayRef<'_, T> {
204 DisplayRef { value }
205}
206
207impl<'a> Builder<Web> for Arguments<'a> {
208 type State = TextState<Cow<'static, str>>;
209
210 fn build(self, cx: BuildCx) -> Self::State {
211 let value = match self.as_str() {
212 Some(s) => Cow::Borrowed(s),
213 None => Cow::Owned(self.to_string()),
214 };
215
216 let node = web_sys::Text::new_with_data(&value).unwrap_throw();
217
218 cx.position.insert(&node);
219
220 TextState { node, value }
221 }
222
223 fn rebuild(self, _: RebuildCx, state: &mut Self::State) {
224 match self.as_str() {
225 Some(new) => {
226 if !match &state.value {
227 Cow::Borrowed(old) => std::ptr::eq(new, *old),
228 Cow::Owned(old) => new == old,
229 } {
230 state.node.set_data(new);
231 state.value = Cow::Borrowed(new);
232 }
233 }
234 None => match &mut state.value {
235 Cow::Borrowed(_) => {
236 let new = self.to_string();
237 state.node.set_data(&new);
238 state.value = Cow::Owned(new);
239 }
240 Cow::Owned(value) => {
241 let mut w = UpdateString {
242 value,
243 index: 0,
244 changed: false,
245 };
246
247 std::fmt::write(&mut w, self).unwrap_throw();
248
249 if w.changed {
250 state.node.set_data(value);
251 }
252 }
253 },
254 }
255 }
256}
257
258struct UpdateString<'a> {
259 value: &'a mut String,
260 index: usize,
261 changed: bool,
262}
263
264impl<'a> Write for UpdateString<'a> {
265 fn write_str(&mut self, s: &str) -> std::fmt::Result {
266 let remaining = &self.value[self.index..];
267
268 if remaining.strip_prefix(s).is_none() {
269 self.value.truncate(self.index);
270 self.value.push_str(s);
271 self.changed = true;
272 }
273 self.index += s.len();
274
275 Ok(())
276 }
277}
278
279#[doc(hidden)]
280pub mod reexport {
281 pub use ravel::with;
282}
283
284#[macro_export]
289macro_rules! format_text {
290 ($fmt:expr) => {
291 $crate::text::reexport::with(move |cx| cx.build(::std::format_args!($fmt)))
292 };
293 ($fmt:expr, $($args:tt)*) => {
294 $crate::text::reexport::with(move |cx| cx.build(::std::format_args!($fmt, $($args)*)))
295 };
296}