1#![allow(non_snake_case)]
2pub mod anim;
106pub mod anim_ext;
107pub mod gestures;
108pub mod layout;
109pub mod lazy;
110pub mod navigation;
111pub mod overlay;
112pub mod scroll;
113pub mod windowing;
114
115use rustc_hash::{FxHashMap, FxHashSet};
116use std::collections::{HashMap, HashSet};
117use std::rc::Rc;
118use std::{cell::RefCell, cmp::Ordering};
119
120use repose_core::*;
121use taffy::style::FlexDirection;
122use taffy::{Overflow, Point};
123
124pub mod textfield;
125use crate::textfield::{TF_FONT_DP, TF_PADDING_X_DP, byte_to_char_index, measure_text};
126use repose_core::locals;
127pub use textfield::{TextArea, TextField, TextFieldState};
128
129thread_local! {
130 static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
131 RefCell::new(layout::LayoutEngine::new());
132}
133
134#[derive(Default)]
135pub struct Interactions {
136 pub hover: Option<u64>,
137 pub pressed: HashSet<u64>,
138}
139
140pub fn Surface(modifier: Modifier, child: View) -> View {
141 let mut v = View::new(0, ViewKind::Surface).modifier(modifier);
142 v.children = vec![child];
143 v
144}
145
146pub fn Box(modifier: Modifier) -> View {
147 View::new(0, ViewKind::Box).modifier(modifier)
148}
149
150pub fn Row(modifier: Modifier) -> View {
151 View::new(0, ViewKind::Row).modifier(modifier)
152}
153
154pub fn Column(modifier: Modifier) -> View {
155 View::new(0, ViewKind::Column).modifier(modifier)
156}
157
158pub fn Stack(modifier: Modifier) -> View {
159 View::new(0, ViewKind::Stack).modifier(modifier)
160}
161
162pub fn OverlayHost(modifier: Modifier) -> View {
163 View::new(0, ViewKind::OverlayHost).modifier(modifier)
164}
165
166#[deprecated = "Use ScollArea instead"]
167pub fn Scroll(modifier: Modifier) -> View {
168 View::new(
169 0,
170 ViewKind::ScrollV {
171 on_scroll: None,
172 set_viewport_height: None,
173 set_content_height: None,
174 get_scroll_offset: None,
175 set_scroll_offset: None,
176 },
177 )
178 .modifier(modifier)
179}
180
181pub fn Text(text: impl Into<String>) -> View {
182 View::new(
183 0,
184 ViewKind::Text {
185 text: text.into(),
186 color: locals::theme().on_surface,
187 font_size: 16.0, soft_wrap: true,
189 max_lines: None,
190 overflow: TextOverflow::Visible,
191 },
192 )
193}
194
195pub fn Spacer() -> View {
196 Box(Modifier::new().flex_grow(1.0))
197}
198
199pub fn Space(modifier: Modifier) -> View {
200 Box(modifier)
201}
202
203pub fn Grid(
204 columns: usize,
205 modifier: Modifier,
206 children: Vec<View>,
207 row_gap: f32,
208 column_gap: f32,
209) -> View {
210 Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
211}
212
213pub fn Button(content: impl IntoChildren, on_click: impl Fn() + 'static) -> View {
214 View::new(
215 0,
216 ViewKind::Button {
217 on_click: Some(Rc::new(on_click)),
218 },
219 )
220 .with_children(content.into_children())
221 .semantics(Semantics {
222 role: Role::Button,
223 label: None, focused: false,
225 enabled: true,
226 })
227}
228
229pub fn Checkbox(checked: bool, on_change: impl Fn(bool) + 'static) -> View {
230 View::new(
231 0,
232 ViewKind::Checkbox {
233 checked,
234 on_change: Some(Rc::new(on_change)),
235 },
236 )
237 .semantics(Semantics {
238 role: Role::Checkbox,
239 label: None,
240 focused: false,
241 enabled: true,
242 })
243}
244
245pub fn RadioButton(selected: bool, on_select: impl Fn() + 'static) -> View {
246 View::new(
247 0,
248 ViewKind::RadioButton {
249 selected,
250 on_select: Some(Rc::new(on_select)),
251 },
252 )
253 .semantics(Semantics {
254 role: Role::RadioButton,
255 label: None,
256 focused: false,
257 enabled: true,
258 })
259}
260
261pub fn Switch(checked: bool, on_change: impl Fn(bool) + 'static) -> View {
262 View::new(
263 0,
264 ViewKind::Switch {
265 checked,
266 on_change: Some(Rc::new(on_change)),
267 },
268 )
269 .semantics(Semantics {
270 role: Role::Switch,
271 label: None,
272 focused: false,
273 enabled: true,
274 })
275}
276pub fn Slider(
277 value: f32,
278 range: (f32, f32),
279 step: Option<f32>,
280 on_change: impl Fn(f32) + 'static,
281) -> View {
282 View::new(
283 0,
284 ViewKind::Slider {
285 value,
286 min: range.0,
287 max: range.1,
288 step,
289 on_change: Some(Rc::new(on_change)),
290 },
291 )
292 .semantics(Semantics {
293 role: Role::Slider,
294 label: None,
295 focused: false,
296 enabled: true,
297 })
298}
299
300pub fn RangeSlider(
301 start: f32,
302 end: f32,
303 range: (f32, f32),
304 step: Option<f32>,
305 on_change: impl Fn(f32, f32) + 'static,
306) -> View {
307 View::new(
308 0,
309 ViewKind::RangeSlider {
310 start,
311 end,
312 min: range.0,
313 max: range.1,
314 step,
315 on_change: Some(Rc::new(on_change)),
316 },
317 )
318 .semantics(Semantics {
319 role: Role::Slider,
320 label: None,
321 focused: false,
322 enabled: true,
323 })
324}
325
326pub fn LinearProgress(value: Option<f32>) -> View {
327 View::new(
328 0,
329 ViewKind::ProgressBar {
330 value: value.unwrap_or(0.0),
331 min: 0.0,
332 max: 1.0,
333 circular: false,
334 },
335 )
336 .semantics(Semantics {
337 role: Role::ProgressBar,
338 label: None,
339 focused: false,
340 enabled: true,
341 })
342}
343
344pub fn ProgressBar(value: f32, range: (f32, f32)) -> View {
345 View::new(
346 0,
347 ViewKind::ProgressBar {
348 value,
349 min: range.0,
350 max: range.1,
351 circular: false,
352 },
353 )
354 .semantics(Semantics {
355 role: Role::ProgressBar,
356 label: None,
357 focused: false,
358 enabled: true,
359 })
360}
361
362pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
363 View::new(
364 0,
365 ViewKind::Image {
366 handle,
367 tint: Color::WHITE,
368 fit: ImageFit::Contain,
369 },
370 )
371 .modifier(modifier)
372}
373
374pub trait ImageExt {
375 fn image_tint(self, c: Color) -> View;
376 fn image_fit(self, fit: ImageFit) -> View;
377}
378impl ImageExt for View {
379 fn image_tint(mut self, c: Color) -> View {
380 if let ViewKind::Image { tint, .. } = &mut self.kind {
381 *tint = c;
382 }
383 self
384 }
385 fn image_fit(mut self, fit: ImageFit) -> View {
386 if let ViewKind::Image { fit: f, .. } = &mut self.kind {
387 *f = fit;
388 }
389 self
390 }
391}
392
393fn flex_dir_for(kind: &ViewKind) -> Option<FlexDirection> {
394 match kind {
395 ViewKind::Row => {
396 if repose_core::locals::text_direction() == repose_core::locals::TextDirection::Rtl {
397 Some(FlexDirection::RowReverse)
398 } else {
399 Some(FlexDirection::Row)
400 }
401 }
402 ViewKind::Column | ViewKind::Surface | ViewKind::ScrollV { .. } => {
403 Some(FlexDirection::Column)
404 }
405 _ => None,
406 }
407}
408
409pub trait ViewExt: Sized {
411 fn child(self, children: impl IntoChildren) -> Self;
412}
413
414impl ViewExt for View {
415 fn child(self, children: impl IntoChildren) -> Self {
416 self.with_children(children.into_children())
417 }
418}
419
420pub trait IntoChildren {
421 fn into_children(self) -> Vec<View>;
422}
423
424impl IntoChildren for View {
425 fn into_children(self) -> Vec<View> {
426 vec![self]
427 }
428}
429
430impl IntoChildren for Vec<View> {
431 fn into_children(self) -> Vec<View> {
432 self
433 }
434}
435
436impl<const N: usize> IntoChildren for [View; N] {
437 fn into_children(self) -> Vec<View> {
438 self.into()
439 }
440}
441
442macro_rules! impl_into_children_tuple {
444 ($($idx:tt $t:ident),+) => {
445 impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
446 fn into_children(self) -> Vec<View> {
447 let mut v = Vec::new();
448 $(v.extend(self.$idx.into_children());)+
449 v
450 }
451 }
452 };
453}
454
455impl_into_children_tuple!(0 A);
456impl_into_children_tuple!(0 A, 1 B);
457impl_into_children_tuple!(0 A, 1 B, 2 C);
458impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
459impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
460impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
461impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
462impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
463
464pub fn layout_and_paint(
466 root: &View,
467 size_px_u32: (u32, u32),
468 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
469 interactions: &Interactions,
470 focused: Option<u64>,
471) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
472 LAYOUT_ENGINE.with(|engine| {
473 engine
474 .borrow_mut()
475 .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
476 })
477}
478
479pub trait TextStyle {
481 fn color(self, c: Color) -> View;
482 fn size(self, px: f32) -> View;
483 fn max_lines(self, n: usize) -> View;
484 fn single_line(self) -> View;
485 fn overflow_ellipsize(self) -> View;
486 fn overflow_clip(self) -> View;
487 fn overflow_visible(self) -> View;
488}
489impl TextStyle for View {
490 fn color(mut self, c: Color) -> View {
491 if let ViewKind::Text {
492 color: text_color, ..
493 } = &mut self.kind
494 {
495 *text_color = c;
496 }
497 self
498 }
499 fn size(mut self, dp_font: f32) -> View {
500 if let ViewKind::Text {
501 font_size: text_size_dp,
502 ..
503 } = &mut self.kind
504 {
505 *text_size_dp = dp_font;
506 }
507 self
508 }
509 fn max_lines(mut self, n: usize) -> View {
510 if let ViewKind::Text {
511 max_lines,
512 soft_wrap,
513 ..
514 } = &mut self.kind
515 {
516 *max_lines = Some(n);
517 *soft_wrap = true;
518 }
519 self
520 }
521 fn single_line(mut self) -> View {
522 if let ViewKind::Text {
523 soft_wrap,
524 max_lines,
525 ..
526 } = &mut self.kind
527 {
528 *soft_wrap = false;
529 *max_lines = Some(1);
530 }
531 self
532 }
533 fn overflow_ellipsize(mut self) -> View {
534 if let ViewKind::Text { overflow, .. } = &mut self.kind {
535 *overflow = TextOverflow::Ellipsis;
536 }
537 self
538 }
539 fn overflow_clip(mut self) -> View {
540 if let ViewKind::Text { overflow, .. } = &mut self.kind {
541 *overflow = TextOverflow::Clip;
542 }
543 self
544 }
545 fn overflow_visible(mut self) -> View {
546 if let ViewKind::Text { overflow, .. } = &mut self.kind {
547 *overflow = TextOverflow::Visible;
548 }
549 self
550 }
551}