1#![allow(non_snake_case)]
2pub mod adaptive;
106pub mod anim;
107pub mod anim_ext;
108pub mod color_picker;
109pub mod gestures;
110pub mod layout;
111pub use layout::IntrinsicSizeMode;
112pub mod lazy;
113pub mod selection;
114pub mod subcompose;
115pub use lazy::{
116 LazyColumn, LazyHorizontalGrid, LazyRow, LazyVerticalGrid, LazyVerticalStaggeredGrid,
117 SimpleList,
118};
119pub mod lazy_states;
120pub use lazy_states::{
121 ItemHeight, LazyColumnConfig, LazyColumnState, LazyGridConfig, LazyGridState, LazyRowConfig,
122 LazyRowState, LazyVerticalStaggeredGridConfig, LazyVerticalStaggeredGridState,
123};
124pub use subcompose::{
125 BoxWithConstraints, SubcomposeLayout, box_with_constraints_with_key, subcompose_hash_key,
126 subcompose_layout_with_slots, subcompose_with_key, subcompose_with_key_slots,
127};
128pub mod overlay;
129pub mod pager;
130pub mod scroll;
131pub mod windowing;
132
133use std::cell::RefCell;
134use std::collections::{HashMap, HashSet};
135use std::rc::Rc;
136use std::sync::atomic::{AtomicU64, Ordering};
137
138use repose_core::*;
139use taffy::style::FlexDirection;
140
141pub mod textfield;
142use repose_core::locals;
143pub use selection::SelectableText;
144pub use textfield::{
145 BasicSecureTextField, BasicTextField, KeyboardOptions, TextFieldConfig, TextFieldState,
146};
147
148thread_local! {
149 static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
150 RefCell::new(layout::LayoutEngine::new());
151}
152
153#[derive(Default)]
154pub struct Interactions {
155 pub hover: Option<u64>,
156 pub pressed: HashSet<u64>,
157}
158
159pub fn Box(modifier: Modifier) -> View {
160 View::new(0, ViewKind::Box).modifier(modifier)
161}
162
163pub fn Row(modifier: Modifier) -> View {
164 View::new(0, ViewKind::Row).modifier(modifier)
165}
166
167pub fn Column(modifier: Modifier) -> View {
168 View::new(0, ViewKind::Column).modifier(modifier)
169}
170
171pub fn FlowRow(modifier: Modifier) -> View {
174 Row(modifier.flex_wrap(FlexWrap::Wrap))
175}
176
177pub fn FlowColumn(modifier: Modifier) -> View {
180 Column(modifier.flex_wrap(FlexWrap::Wrap))
181}
182
183pub fn Center(modifier: Modifier) -> View {
185 Box(modifier.align_self(AlignSelf::CENTER))
186}
187
188pub fn Stack(modifier: Modifier) -> View {
189 View::new(0, ViewKind::Stack).modifier(modifier)
190}
191
192pub fn ZStack(modifier: Modifier) -> View {
193 View::new(0, ViewKind::ZStack).modifier(modifier)
194}
195
196pub fn OverlayHost(modifier: Modifier) -> View {
197 View::new(0, ViewKind::OverlayHost).modifier(modifier)
198}
199
200#[deprecated = "Use Modifier::vertical_scroll instead"]
201pub fn Scroll(modifier: Modifier) -> View {
202 View::new(0, ViewKind::Box)
203 .modifier(modifier.vertical_scroll(ScrollAxisBinding {
204 show_scrollbar: true,
205 ..Default::default()
206 }))
207}
208
209pub fn Text(text: impl Into<String>) -> View {
210 View::new(
211 0,
212 ViewKind::Text {
213 text: text.into(),
214 color: locals::content_color(),
215 font_size: 16.0, soft_wrap: true,
217 max_lines: None,
218 overflow: TextOverflow::Visible,
219 font_family: Some("sans-serif"),
220 annotations: None,
221 text_align: TextAlign::Start,
222 font_weight: FontWeight::NORMAL,
223 font_style: FontStyle::Normal,
224 text_decoration: TextDecoration::default(),
225 letter_spacing: 0.0,
226 line_height: 0.0,
227 url: None,
228 font_variation_settings: None,
229 },
230 )
231}
232
233pub fn AnnotatedText(annotated: AnnotatedString) -> View {
237 let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
238 None
239 } else {
240 Some(annotated.spans.clone())
241 };
242 View::new(
243 0,
244 ViewKind::Text {
245 text: annotated.text,
246 color: locals::content_color(),
247 font_size: 16.0,
248 soft_wrap: true,
249 max_lines: None,
250 overflow: TextOverflow::Visible,
251 font_family: Some("sans-serif"),
252 annotations,
253 text_align: TextAlign::Start,
254 font_weight: FontWeight::NORMAL,
255 font_style: FontStyle::Normal,
256 text_decoration: TextDecoration::default(),
257 letter_spacing: 0.0,
258 line_height: 0.0,
259 url: None,
260 font_variation_settings: None,
261 },
262 )
263}
264
265pub fn Spacer() -> View {
266 Box(Modifier::new().flex_grow(1.0))
267}
268
269pub fn Space(modifier: Modifier) -> View {
270 Box(modifier)
271}
272
273pub fn Grid(
274 columns: usize,
275 modifier: Modifier,
276 children: Vec<View>,
277 row_gap: f32,
278 column_gap: f32,
279) -> View {
280 Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
281}
282
283pub fn Expander(modifier: Modifier, expanded: bool, on_toggle: impl Fn() + 'static) -> View {
284 View::new(
285 0,
286 ViewKind::Expander {
287 expanded,
288 on_toggle: Some(Rc::new(on_toggle)),
289 },
290 )
291 .modifier(modifier)
292}
293
294pub fn TreeRow(
300 modifier: Modifier,
301 depth: usize,
302 has_children: bool,
303 is_expanded: bool,
304 is_selected: bool,
305 on_toggle: impl Fn() + 'static,
306 on_select: impl Fn() + 'static,
307) -> View {
308 View::new(
309 0,
310 ViewKind::TreeRow {
311 depth,
312 has_children,
313 is_expanded,
314 is_selected,
315 on_toggle: Some(Rc::new(on_toggle)),
316 on_select: Some(Rc::new(on_select)),
317 },
318 )
319 .modifier(modifier)
320}
321
322static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
323
324pub fn DragValue(
329 value: f32,
330 range: (f32, f32),
331 speed: f32,
332 on_change: impl Fn(f32) + 'static,
333) -> View {
334 let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
335 let drag_start_x = remember_state_with_key(format!("dv_dsx_{}", id), || 0.0f32);
336 let drag_start_val = remember_state_with_key(format!("dv_dsv_{}", id), || 0.0f32);
337 let is_dragging = remember_state_with_key(format!("dv_drg_{}", id), || false);
338
339 let oc = Rc::new(on_change);
340 let min = range.0;
341 let max = range.1;
342 let cur = value;
343
344 let th = locals::theme();
345
346 Box(Modifier::new()
347 .min_width(48.0)
348 .height(28.0)
349 .background(th.surface_container)
350 .border(1.0, th.outline, 4.0)
351 .clip_rounded(4.0)
352 .padding_values(PaddingValues {
353 left: 4.0,
354 right: 4.0,
355 top: 0.0,
356 bottom: 0.0,
357 })
358 .on_pointer_down({
359 let dsx = drag_start_x.clone();
360 let dsv = drag_start_val.clone();
361 let drg = is_dragging.clone();
362 move |pe: PointerEvent| {
363 *drg.borrow_mut() = true;
364 *dsx.borrow_mut() = pe.position.x;
365 *dsv.borrow_mut() = cur;
366 }
367 })
368 .on_pointer_move({
369 let dsx = drag_start_x.clone();
370 let dsv = drag_start_val.clone();
371 let drg = is_dragging.clone();
372 let oc = oc.clone();
373 move |pe: PointerEvent| {
374 if !*drg.borrow() {
375 return;
376 }
377 let dx = pe.position.x - *dsx.borrow();
378 let new_val = (*dsv.borrow() + dx * speed).clamp(min, max);
379 (oc)(new_val);
380 }
381 })
382 .on_pointer_up({
383 let drg = is_dragging.clone();
384 move |_pe: PointerEvent| {
385 *drg.borrow_mut() = false;
386 }
387 })
388 .cursor(CursorIcon::EwResize))
389 .child(
390 Text(format_value(value))
391 .size(13.0)
392 .color(th.on_surface)
393 .single_line()
394 .overflow_ellipsize(),
395 )
396}
397
398fn format_value(v: f32) -> String {
399 if (v - v.round()).abs() < 1e-6 {
400 format!("{}", v.round() as i64)
401 } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
402 format!("{:.1}", v)
403 } else {
404 format!("{:.2}", v)
405 }
406}
407
408pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
409 View::new(
410 0,
411 ViewKind::Image {
412 handle,
413 tint: Color::WHITE,
414 fit: ImageFit::Contain,
415 },
416 )
417 .modifier(modifier)
418}
419
420pub trait ImageExt {
421 fn image_tint(self, c: Color) -> View;
422 fn image_fit(self, fit: ImageFit) -> View;
423}
424impl ImageExt for View {
425 fn image_tint(mut self, c: Color) -> View {
426 if let ViewKind::Image { tint, .. } = &mut self.kind {
427 *tint = c;
428 }
429 self
430 }
431 fn image_fit(mut self, fit: ImageFit) -> View {
432 if let ViewKind::Image { fit: f, .. } = &mut self.kind {
433 *f = fit;
434 }
435 self
436 }
437}
438
439fn flex_dir_for(kind: &ViewKind, modifier: &Modifier) -> Option<FlexDirection> {
440 if let Some(ref scroll) = modifier.scroll {
441 return Some(match scroll.axis() {
442 ScrollAxis::Vertical => FlexDirection::Column,
443 ScrollAxis::Horizontal => FlexDirection::Row,
444 ScrollAxis::Both => FlexDirection::Column,
445 });
446 }
447 match kind {
448 ViewKind::Row => {
449 if repose_core::locals::text_direction() == repose_core::locals::TextDirection::Rtl {
450 Some(FlexDirection::RowReverse)
451 } else {
452 Some(FlexDirection::Row)
453 }
454 }
455 ViewKind::Column => Some(FlexDirection::Column),
456 _ => None,
457 }
458}
459
460pub trait ViewExt: Sized {
462 fn child(self, children: impl IntoChildren) -> Self;
463}
464
465impl ViewExt for View {
466 fn child(self, children: impl IntoChildren) -> Self {
467 self.with_children(children.into_children())
468 }
469}
470
471pub trait IntoChildren {
472 fn into_children(self) -> Vec<View>;
473}
474
475impl IntoChildren for View {
476 fn into_children(self) -> Vec<View> {
477 vec![self]
478 }
479}
480
481impl IntoChildren for Vec<View> {
482 fn into_children(self) -> Vec<View> {
483 self
484 }
485}
486
487impl<const N: usize> IntoChildren for [View; N] {
488 fn into_children(self) -> Vec<View> {
489 self.into()
490 }
491}
492
493macro_rules! impl_into_children_tuple {
495 ($($idx:tt $t:ident),+) => {
496 impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
497 fn into_children(self) -> Vec<View> {
498 let mut v = Vec::new();
499 $(v.extend(self.$idx.into_children());)+
500 v
501 }
502 }
503 };
504}
505
506impl_into_children_tuple!(0 A);
507impl_into_children_tuple!(0 A, 1 B);
508impl_into_children_tuple!(0 A, 1 B, 2 C);
509impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
510impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
511impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
512impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
513impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
514
515pub fn layout_and_paint(
517 root: &View,
518 size_px_u32: (u32, u32),
519 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
520 interactions: &Interactions,
521 focused: Option<u64>,
522) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
523 LAYOUT_ENGINE.with(|engine| {
524 engine
525 .borrow_mut()
526 .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
527 })
528}
529
530pub trait TextStyle {
532 fn color(self, c: Color) -> View;
533 fn size(self, px: f32) -> View;
534 fn max_lines(self, n: usize) -> View;
535 fn single_line(self) -> View;
536 fn overflow_ellipsize(self) -> View;
537 fn overflow_clip(self) -> View;
538 fn overflow_visible(self) -> View;
539 fn font_family(self, family: &'static str) -> View;
540 fn text_align(self, align: TextAlign) -> View;
541 fn font_weight(self, weight: FontWeight) -> View;
542 fn font_style(self, style: FontStyle) -> View;
543 fn text_decoration(self, decoration: TextDecoration) -> View;
544 fn letter_spacing(self, spacing: f32) -> View;
545 fn line_height(self, height: f32) -> View;
546 fn url(self, url: impl Into<std::sync::Arc<str>>) -> View;
547 fn font_variation_settings(self, settings: &str) -> View;
548}
549impl TextStyle for View {
550 fn color(mut self, c: Color) -> View {
551 if let ViewKind::Text {
552 color: text_color, ..
553 } = &mut self.kind
554 {
555 *text_color = c;
556 }
557 self
558 }
559 fn size(mut self, dp_font: f32) -> View {
560 if let ViewKind::Text {
561 font_size: text_size_dp,
562 ..
563 } = &mut self.kind
564 {
565 *text_size_dp = dp_font;
566 }
567 self
568 }
569 fn max_lines(mut self, n: usize) -> View {
570 if let ViewKind::Text {
571 max_lines,
572 soft_wrap,
573 ..
574 } = &mut self.kind
575 {
576 *max_lines = Some(n);
577 *soft_wrap = true;
578 }
579 self
580 }
581 fn single_line(mut self) -> View {
582 if let ViewKind::Text {
583 soft_wrap,
584 max_lines,
585 ..
586 } = &mut self.kind
587 {
588 *soft_wrap = false;
589 *max_lines = Some(1);
590 }
591 self
592 }
593 fn overflow_ellipsize(mut self) -> View {
594 if let ViewKind::Text { overflow, .. } = &mut self.kind {
595 *overflow = TextOverflow::Ellipsis;
596 }
597 self
598 }
599 fn overflow_clip(mut self) -> View {
600 if let ViewKind::Text { overflow, .. } = &mut self.kind {
601 *overflow = TextOverflow::Clip;
602 }
603 self
604 }
605 fn overflow_visible(mut self) -> View {
606 if let ViewKind::Text { overflow, .. } = &mut self.kind {
607 *overflow = TextOverflow::Visible;
608 }
609 self
610 }
611 fn font_family(mut self, family: &'static str) -> View {
612 if let ViewKind::Text {
613 font_family: ff, ..
614 } = &mut self.kind
615 {
616 *ff = Some(family);
617 }
618 self
619 }
620 fn text_align(mut self, align: TextAlign) -> View {
621 if let ViewKind::Text { text_align, .. } = &mut self.kind {
622 *text_align = align;
623 }
624 self
625 }
626 fn font_weight(mut self, weight: FontWeight) -> View {
627 if let ViewKind::Text { font_weight, .. } = &mut self.kind {
628 *font_weight = weight;
629 }
630 self
631 }
632 fn font_style(mut self, style: FontStyle) -> View {
633 if let ViewKind::Text { font_style, .. } = &mut self.kind {
634 *font_style = style;
635 }
636 self
637 }
638 fn text_decoration(mut self, decoration: TextDecoration) -> View {
639 if let ViewKind::Text {
640 text_decoration, ..
641 } = &mut self.kind
642 {
643 *text_decoration = decoration;
644 }
645 self
646 }
647 fn letter_spacing(mut self, spacing: f32) -> View {
648 if let ViewKind::Text { letter_spacing, .. } = &mut self.kind {
649 *letter_spacing = spacing;
650 }
651 self
652 }
653 fn line_height(mut self, height: f32) -> View {
654 if let ViewKind::Text { line_height, .. } = &mut self.kind {
655 *line_height = height;
656 }
657 self
658 }
659 fn url(mut self, url: impl Into<std::sync::Arc<str>>) -> View {
660 if let ViewKind::Text {
661 url: u,
662 text_decoration,
663 ..
664 } = &mut self.kind
665 {
666 *u = Some(url.into());
667 if !text_decoration.underline && !text_decoration.strikethrough {
668 *text_decoration = TextDecoration::UNDERLINE;
669 }
670 }
671 self
672 }
673 fn font_variation_settings(mut self, settings: &str) -> View {
674 if let ViewKind::Text {
675 font_variation_settings,
676 ..
677 } = &mut self.kind
678 {
679 *font_variation_settings = Some(settings.into());
680 }
681 self
682 }
683}