1#![allow(non_snake_case)]
2pub mod adaptive;
87pub mod anim;
88pub mod anim_ext;
89pub mod color_picker;
90pub(crate) mod hit_testing;
91pub use hit_testing::{
92 HitRegionSnapshot, dispatch_keyboard_action, hit_region_contains, hit_region_local_rect,
93 hit_region_pointer_coordinates, hit_region_to_local, hit_region_transformed_origin,
94 hit_region_world_rect, hit_test_enabled_frame, hit_test_frame, hit_test_frame_path,
95 hit_test_frame_regions,
96};
97pub mod layout;
98pub use layout::IntrinsicSizeMode;
99pub mod lazy;
100pub mod selection;
101pub mod subcompose;
102pub use lazy::{
103 LazyColumn, LazyHorizontalGrid, LazyRow, LazyVerticalGrid, LazyVerticalStaggeredGrid,
104 SimpleList,
105};
106pub mod lazy_states;
107pub use lazy_states::{
108 ItemHeight, LazyColumnConfig, LazyColumnState, LazyGridConfig, LazyGridState, LazyRowConfig,
109 LazyRowState, LazyVerticalStaggeredGridConfig, LazyVerticalStaggeredGridState,
110};
111pub use subcompose::{
112 BoxWithConstraints, SubcomposeLayout, box_with_constraints_with_key, subcompose_hash_key,
113 subcompose_layout_with_slots, subcompose_with_key, subcompose_with_key_slots,
114};
115pub mod overlay;
116pub mod pager;
117pub mod scroll;
118pub mod window_v2;
119pub mod windowing;
120
121use std::cell::RefCell;
122use std::collections::{HashMap, HashSet};
123use std::rc::Rc;
124use std::sync::atomic::{AtomicU64, Ordering};
125
126use repose_core::*;
127
128pub(crate) mod text_selection;
129pub mod textfield;
130use repose_core::locals;
131pub use selection::{SelectableText, SelectableTextExt};
132pub use textfield::{
133 BasicSecureTextField, BasicTextField, KeyboardOptions, TextFieldConfig, TextFieldState,
134};
135
136thread_local! {
137 static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
138 RefCell::new(layout::LayoutEngine::new());
139}
140
141#[derive(Default)]
142pub struct Interactions {
143 pub hover: Option<u64>,
144 pub hover_ancestors: std::collections::HashSet<u64>,
145 pub pressed: HashSet<u64>,
146}
147
148pub fn Box(modifier: Modifier) -> View {
149 View::new(0, ViewKind::Box).modifier(modifier)
150}
151
152pub fn Row(modifier: Modifier) -> View {
153 View::new(0, ViewKind::Row).modifier(modifier)
154}
155
156pub struct RowScope {
170 _private: (),
171}
172
173impl RowScope {
174 pub fn align_by_baseline(&self, view: View) -> View {
178 self.align_by(view, BaselineAlign::FirstBaseline)
179 }
180
181 pub fn align_by_last_baseline(&self, view: View) -> View {
184 self.align_by(view, BaselineAlign::LastBaseline)
185 }
186
187 pub fn align_by(&self, mut view: View, line: BaselineAlign) -> View {
189 view.modifier.baseline_align = Some(line);
190 view
191 }
192}
193
194pub fn row_scope<R>(content: impl FnOnce(&RowScope) -> R) -> R {
198 content(&RowScope { _private: () })
199}
200
201pub fn Column(modifier: Modifier) -> View {
202 View::new(0, ViewKind::Column).modifier(modifier)
203}
204
205#[derive(Clone, Copy, Debug, PartialEq)]
208pub struct FlowRowConfig {
209 pub balanced: bool,
212 pub min_lines: u16,
215}
216
217impl Default for FlowRowConfig {
218 fn default() -> Self {
219 Self {
220 balanced: false,
221 min_lines: 1,
222 }
223 }
224}
225
226#[derive(Clone, Copy, Debug, PartialEq)]
228pub struct FlowColumnConfig {
229 pub balanced: bool,
231 pub min_lines: u16,
233}
234
235impl Default for FlowColumnConfig {
236 fn default() -> Self {
237 Self {
238 balanced: false,
239 min_lines: 1,
240 }
241 }
242}
243
244pub fn FlowRow(modifier: Modifier, config: FlowRowConfig) -> View {
248 let mut modifier = modifier.flex_wrap(if config.balanced {
249 FlexWrap::Balance
250 } else {
251 FlexWrap::Wrap
252 });
253 modifier.flex_line_count = Some(config.min_lines.max(1));
254 Row(modifier)
255}
256
257#[deprecated = "Use Column instead (identical behavior)"]
260pub fn Stack(modifier: Modifier) -> View {
261 Column(modifier)
262}
263
264pub fn FlowColumn(modifier: Modifier, config: FlowColumnConfig) -> View {
268 let mut modifier = modifier.flex_wrap(if config.balanced {
269 FlexWrap::Balance
270 } else {
271 FlexWrap::Wrap
272 });
273 modifier.flex_line_count = Some(config.min_lines.max(1));
274 Column(modifier)
275}
276
277pub fn Center(modifier: Modifier) -> View {
282 Box(modifier.content_alignment_safe(Alignment::Center))
283}
284
285pub fn ZStack(modifier: Modifier) -> View {
286 View::new(0, ViewKind::ZStack).modifier(modifier)
287}
288
289pub fn OverlayHost(modifier: Modifier) -> View {
290 View::new(0, ViewKind::OverlayHost).modifier(modifier)
291}
292
293#[deprecated = "Use Modifier::vertical_scroll instead"]
294pub fn Scroll(modifier: Modifier) -> View {
295 View::new(0, ViewKind::Box).modifier(modifier.vertical_scroll(ScrollAxisBinding {
296 show_scrollbar: true,
297 ..Default::default()
298 }))
299}
300
301pub fn Text(text: impl Into<String>) -> View {
302 View::new(
303 0,
304 ViewKind::Text {
305 text: text.into(),
306 color: locals::content_color(),
307 font_size: locals::text_size().unwrap_or(Sp(16.0)),
309 soft_wrap: true,
310 max_lines: None,
311 overflow: TextOverflow::Clip,
312 font_family: Some("sans-serif"),
313 annotations: None,
314 text_align: TextAlign::Start,
315 font_weight: FontWeight::NORMAL,
316 font_style: FontStyle::Normal,
317 text_decoration: TextDecoration::default(),
318 letter_spacing: Sp::ZERO,
319 line_height: Sp::ZERO,
320 url: None,
321 font_variation_settings: None,
322 draw_style: DrawStyle::Fill,
323 },
324 )
325}
326
327pub fn AnnotatedText(annotated: AnnotatedString) -> View {
331 let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
332 None
333 } else {
334 Some(annotated.spans.clone())
335 };
336 View::new(
337 0,
338 ViewKind::Text {
339 text: annotated.text,
340 color: locals::content_color(),
341 font_size: locals::text_size().unwrap_or(Sp(16.0)),
342 soft_wrap: true,
343 max_lines: None,
344 overflow: TextOverflow::Clip,
345 font_family: Some("sans-serif"),
346 annotations,
347 text_align: TextAlign::Start,
348 font_weight: FontWeight::NORMAL,
349 font_style: FontStyle::Normal,
350 text_decoration: TextDecoration::default(),
351 letter_spacing: Sp::ZERO,
352 line_height: Sp::ZERO,
353 url: None,
354 font_variation_settings: None,
355 draw_style: DrawStyle::Fill,
356 },
357 )
358}
359
360pub fn Spacer() -> View {
361 Box(Modifier::new().flex_grow(1.0))
362}
363
364pub fn Space(modifier: Modifier) -> View {
365 Box(modifier)
366}
367
368pub fn Grid(
369 columns: usize,
370 modifier: Modifier,
371 children: Vec<View>,
372 row_gap: Dp,
373 column_gap: Dp,
374) -> View {
375 Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
376}
377
378static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
379
380pub fn DragValue(
385 value: f32,
386 range: (f32, f32),
387 speed: f32,
388 on_change: impl Fn(f32) + 'static,
389) -> View {
390 let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
391 let drag_start_x = remember_mutable_with_key(format!("dv_dsx_{}", id), || 0.0f32);
392 let drag_start_val = remember_mutable_with_key(format!("dv_dsv_{}", id), || 0.0f32);
393 let is_dragging = remember_mutable_with_key(format!("dv_drg_{}", id), || false);
394
395 let oc = Rc::new(on_change);
396 let min = range.0;
397 let max = range.1;
398 let cur = value;
399
400 let th = locals::theme();
401
402 Box(Modifier::new()
403 .min_width(Dp(48.0))
404 .height(Dp(28.0))
405 .background(th.surface_container)
406 .border(Dp(1.0), th.outline, Dp(4.0))
407 .clip_rounded(Dp(4.0))
408 .padding_values(PaddingValues {
409 left: Dp(4.0),
410 right: Dp(4.0),
411 top: Dp::ZERO,
412 bottom: Dp::ZERO,
413 })
414 .on_pointer_down({
415 let dsx = drag_start_x.clone();
416 let dsv = drag_start_val.clone();
417 let drg = is_dragging.clone();
418 move |pe: PointerEvent| {
419 drg.set(true);
420 dsx.set(pe.position_in_window().x);
421 dsv.set(cur);
422 }
423 })
424 .on_pointer_move({
425 let dsx = drag_start_x.clone();
426 let dsv = drag_start_val.clone();
427 let drg = is_dragging.clone();
428 let oc = oc.clone();
429 move |pe: PointerEvent| {
430 if !drg.with(|v| *v) {
431 return;
432 }
433 let start_x = dsx.with(|v| *v);
434 let start_val = dsv.with(|v| *v);
435 let new_val =
436 (start_val + (pe.position_in_window().x - start_x) * speed).clamp(min, max);
437 (oc)(new_val);
438 }
439 })
440 .on_pointer_up({
441 let drg = is_dragging.clone();
442 move |_pe: PointerEvent| {
443 drg.set(false);
444 }
445 })
446 .on_pointer_cancel({
447 let drg = is_dragging.clone();
448 move |_pe: PointerEvent| {
449 drg.set(false);
450 }
451 })
452 .cursor(CursorIcon::EwResize))
453 .child(
454 Text(format_value(value))
455 .size(Sp(13.0))
456 .color(th.on_surface)
457 .single_line()
458 .overflow_ellipsize(),
459 )
460}
461
462fn format_value(v: f32) -> String {
463 if (v - v.round()).abs() < 1e-6 {
464 format!("{}", v.round() as i64)
465 } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
466 format!("{:.1}", v)
467 } else {
468 format!("{:.2}", v)
469 }
470}
471
472pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
473 View::new(
474 0,
475 ViewKind::Image {
476 handle,
477 tint: Color::WHITE,
478 fit: ImageFit::Contain,
479 filter: ImageFilter::Linear,
480 source_rect: None,
481 alignment: ImageAlignment::Center,
482 },
483 )
484 .modifier(modifier)
485}
486
487pub fn Embedded(modifier: Modifier, payload: PaintCallbackPayload) -> View {
489 let mut m = modifier.paint_callback(payload);
490 let has_size = m.size.is_some()
491 || m.width.is_some()
492 || m.height.is_some()
493 || m.fill_max.is_some()
494 || m.fill_max_w.is_some()
495 || m.fill_max_h.is_some();
496 if !has_size {
497 m = m.size(Dp(100.0), Dp(100.0));
498 }
499 Box(m)
500}
501
502pub trait ImageExt {
503 fn image_tint(self, c: Color) -> View;
504 fn image_fit(self, fit: ImageFit) -> View;
505 fn image_filter(self, filter: ImageFilter) -> View;
506 fn image_source_rect(self, source_rect: ImageSourceRect) -> View;
507 fn image_alignment(self, alignment: ImageAlignment) -> View;
508}
509impl ImageExt for View {
510 fn image_tint(mut self, c: Color) -> View {
511 if let ViewKind::Image { tint, .. } = &mut self.kind {
512 *tint = c;
513 }
514 self
515 }
516 fn image_fit(mut self, fit: ImageFit) -> View {
517 if let ViewKind::Image { fit: f, .. } = &mut self.kind {
518 *f = fit;
519 }
520 self
521 }
522 fn image_filter(mut self, filter: ImageFilter) -> View {
523 if let ViewKind::Image { filter: f, .. } = &mut self.kind {
524 *f = filter;
525 }
526 self
527 }
528 fn image_source_rect(mut self, source: ImageSourceRect) -> View {
529 if let ViewKind::Image { source_rect, .. } = &mut self.kind {
530 *source_rect = Some(source);
531 }
532 self
533 }
534 fn image_alignment(mut self, alignment: ImageAlignment) -> View {
535 if let ViewKind::Image {
536 alignment: current, ..
537 } = &mut self.kind
538 {
539 *current = alignment;
540 }
541 self
542 }
543}
544
545pub trait ViewExt: Sized {
547 fn child(self, children: impl IntoChildren) -> Self;
548}
549
550impl ViewExt for View {
551 fn child(mut self, children: impl IntoChildren) -> Self {
552 self.children.extend(children.into_children());
553 self
554 }
555}
556
557pub trait IntoChildren {
558 fn into_children(self) -> Vec<View>;
559}
560
561impl IntoChildren for View {
562 fn into_children(self) -> Vec<View> {
563 vec![self]
564 }
565}
566
567impl IntoChildren for Vec<View> {
568 fn into_children(self) -> Vec<View> {
569 self
570 }
571}
572
573impl<const N: usize> IntoChildren for [View; N] {
574 fn into_children(self) -> Vec<View> {
575 self.into()
576 }
577}
578
579macro_rules! impl_into_children_tuple {
581 ($($idx:tt $t:ident),+) => {
582 impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
583 fn into_children(self) -> Vec<View> {
584 let mut v = Vec::new();
585 $(v.extend(self.$idx.into_children());)+
586 v
587 }
588 }
589 };
590}
591
592impl_into_children_tuple!(0 A);
593impl_into_children_tuple!(0 A, 1 B);
594impl_into_children_tuple!(0 A, 1 B, 2 C);
595impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
596impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
597impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
598impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
599impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
600
601pub fn layout_and_paint(
604 root: &View,
605 size_px_u32: (u32, u32),
606 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
607 interactions: &Interactions,
608 focused: Option<u64>,
609) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
610 let result = LAYOUT_ENGINE.with(|engine| {
611 engine
612 .borrow_mut()
613 .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
614 });
615 textfield::prune_textfield_registries();
616 result
617}
618
619pub fn last_layout_stats() -> layout::LayoutStats {
623 LAYOUT_ENGINE.with(|engine| engine.borrow().stats.clone())
624}
625
626pub use layout::LayoutStats;
627
628pub trait TextStyle {
630 fn color(self, c: Color) -> View;
631 fn size(self, size: Sp) -> View;
632 fn max_lines(self, n: usize) -> View;
633 fn single_line(self) -> View;
634 fn overflow_ellipsize(self) -> View;
635 fn overflow_clip(self) -> View;
636 fn overflow_visible(self) -> View;
637 fn font_family(self, family: &'static str) -> View;
638 fn text_align(self, align: TextAlign) -> View;
639 fn font_weight(self, weight: FontWeight) -> View;
640 fn font_style(self, style: FontStyle) -> View;
641 fn text_decoration(self, decoration: TextDecoration) -> View;
642 fn letter_spacing(self, spacing: Sp) -> View;
643 fn line_height(self, height: Sp) -> View;
644 fn url(self, url: impl Into<std::sync::Arc<str>>) -> View;
645 fn font_variation_settings(self, settings: &str) -> View;
646 fn draw_style(self, style: DrawStyle) -> View;
647 fn fill_and_stroke(self, width: f32) -> View;
650 fn outlined(self, width: f32, color: Color) -> View;
655}
656impl TextStyle for View {
657 fn color(mut self, c: Color) -> View {
658 if let ViewKind::Text {
659 color: text_color, ..
660 } = &mut self.kind
661 {
662 *text_color = c;
663 }
664 self
665 }
666 fn size(mut self, size: Sp) -> View {
667 if let ViewKind::Text {
668 font_size: text_size_sp,
669 ..
670 } = &mut self.kind
671 {
672 *text_size_sp = size;
673 }
674 self
675 }
676 fn max_lines(mut self, n: usize) -> View {
677 if let ViewKind::Text {
678 max_lines,
679 soft_wrap,
680 ..
681 } = &mut self.kind
682 {
683 *max_lines = Some(n);
684 *soft_wrap = true;
685 }
686 self
687 }
688 fn single_line(mut self) -> View {
689 if let ViewKind::Text {
690 soft_wrap,
691 max_lines,
692 ..
693 } = &mut self.kind
694 {
695 *soft_wrap = false;
696 *max_lines = Some(1);
697 }
698 self
699 }
700 fn overflow_ellipsize(mut self) -> View {
701 if let ViewKind::Text { overflow, .. } = &mut self.kind {
702 *overflow = TextOverflow::Ellipsis;
703 }
704 self
705 }
706 fn overflow_clip(mut self) -> View {
707 if let ViewKind::Text { overflow, .. } = &mut self.kind {
708 *overflow = TextOverflow::Clip;
709 }
710 self
711 }
712 fn overflow_visible(mut self) -> View {
713 if let ViewKind::Text { overflow, .. } = &mut self.kind {
714 *overflow = TextOverflow::Visible;
715 }
716 self
717 }
718 fn font_family(mut self, family: &'static str) -> View {
719 if let ViewKind::Text {
720 font_family: ff, ..
721 } = &mut self.kind
722 {
723 *ff = Some(family);
724 }
725 self
726 }
727 fn text_align(mut self, align: TextAlign) -> View {
728 if let ViewKind::Text { text_align, .. } = &mut self.kind {
729 *text_align = align;
730 }
731 self
732 }
733 fn font_weight(mut self, weight: FontWeight) -> View {
734 if let ViewKind::Text { font_weight, .. } = &mut self.kind {
735 *font_weight = weight;
736 }
737 self
738 }
739 fn font_style(mut self, style: FontStyle) -> View {
740 if let ViewKind::Text { font_style, .. } = &mut self.kind {
741 *font_style = style;
742 }
743 self
744 }
745 fn text_decoration(mut self, decoration: TextDecoration) -> View {
746 if let ViewKind::Text {
747 text_decoration, ..
748 } = &mut self.kind
749 {
750 *text_decoration = decoration;
751 }
752 self
753 }
754 fn letter_spacing(mut self, spacing: Sp) -> View {
755 if let ViewKind::Text { letter_spacing, .. } = &mut self.kind {
756 *letter_spacing = spacing;
757 }
758 self
759 }
760 fn line_height(mut self, height: Sp) -> View {
761 if let ViewKind::Text { line_height, .. } = &mut self.kind {
762 *line_height = height;
763 }
764 self
765 }
766 fn url(mut self, url: impl Into<std::sync::Arc<str>>) -> View {
767 if let ViewKind::Text {
768 url: u,
769 text_decoration,
770 ..
771 } = &mut self.kind
772 {
773 *u = Some(url.into());
774 if !text_decoration.underline && !text_decoration.strikethrough {
775 *text_decoration = TextDecoration::UNDERLINE;
776 }
777 }
778 self
779 }
780 fn font_variation_settings(mut self, settings: &str) -> View {
781 if let ViewKind::Text {
782 font_variation_settings,
783 ..
784 } = &mut self.kind
785 {
786 *font_variation_settings = Some(settings.into());
787 }
788 self
789 }
790 fn draw_style(mut self, style: DrawStyle) -> View {
791 if let ViewKind::Text { draw_style, .. } = &mut self.kind {
792 *draw_style = style;
793 }
794 self
795 }
796 fn fill_and_stroke(mut self, width: f32) -> View {
797 if let ViewKind::Text { draw_style, .. } = &mut self.kind {
798 *draw_style = DrawStyle::fill_and_stroke(width);
799 }
800 self
801 }
802 fn outlined(mut self, width: f32, color: Color) -> View {
803 if let ViewKind::Text { draw_style, .. } = &mut self.kind {
804 *draw_style = DrawStyle::fill_and_stroke_with_outline(width, color);
805 }
806 self
807 }
808}