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: None,
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 },
229 )
230}
231
232pub fn AnnotatedText(annotated: AnnotatedString) -> View {
236 let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
237 None
238 } else {
239 Some(annotated.spans.clone())
240 };
241 View::new(
242 0,
243 ViewKind::Text {
244 text: annotated.text,
245 color: locals::content_color(),
246 font_size: 16.0,
247 soft_wrap: true,
248 max_lines: None,
249 overflow: TextOverflow::Visible,
250 font_family: None,
251 annotations,
252 text_align: TextAlign::Start,
253 font_weight: FontWeight::NORMAL,
254 font_style: FontStyle::Normal,
255 text_decoration: TextDecoration::default(),
256 letter_spacing: 0.0,
257 line_height: 0.0,
258 url: None,
259 },
260 )
261}
262
263pub fn Spacer() -> View {
264 Box(Modifier::new().flex_grow(1.0))
265}
266
267pub fn Space(modifier: Modifier) -> View {
268 Box(modifier)
269}
270
271pub fn Grid(
272 columns: usize,
273 modifier: Modifier,
274 children: Vec<View>,
275 row_gap: f32,
276 column_gap: f32,
277) -> View {
278 Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
279}
280
281pub fn Expander(modifier: Modifier, expanded: bool, on_toggle: impl Fn() + 'static) -> View {
282 View::new(
283 0,
284 ViewKind::Expander {
285 expanded,
286 on_toggle: Some(Rc::new(on_toggle)),
287 },
288 )
289 .modifier(modifier)
290}
291
292pub fn TreeRow(
298 modifier: Modifier,
299 depth: usize,
300 has_children: bool,
301 is_expanded: bool,
302 is_selected: bool,
303 on_toggle: impl Fn() + 'static,
304 on_select: impl Fn() + 'static,
305) -> View {
306 View::new(
307 0,
308 ViewKind::TreeRow {
309 depth,
310 has_children,
311 is_expanded,
312 is_selected,
313 on_toggle: Some(Rc::new(on_toggle)),
314 on_select: Some(Rc::new(on_select)),
315 },
316 )
317 .modifier(modifier)
318}
319
320static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
321
322pub fn DragValue(
327 value: f32,
328 range: (f32, f32),
329 speed: f32,
330 on_change: impl Fn(f32) + 'static,
331) -> View {
332 let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
333 let drag_start_x = remember_state_with_key(format!("dv_dsx_{}", id), || 0.0f32);
334 let drag_start_val = remember_state_with_key(format!("dv_dsv_{}", id), || 0.0f32);
335 let is_dragging = remember_state_with_key(format!("dv_drg_{}", id), || false);
336
337 let oc = Rc::new(on_change);
338 let min = range.0;
339 let max = range.1;
340 let cur = value;
341
342 let th = locals::theme();
343
344 Box(Modifier::new()
345 .min_width(48.0)
346 .height(28.0)
347 .background(th.surface_container)
348 .border(1.0, th.outline, 4.0)
349 .clip_rounded(4.0)
350 .padding_values(PaddingValues {
351 left: 4.0,
352 right: 4.0,
353 top: 0.0,
354 bottom: 0.0,
355 })
356 .on_pointer_down({
357 let dsx = drag_start_x.clone();
358 let dsv = drag_start_val.clone();
359 let drg = is_dragging.clone();
360 move |pe: PointerEvent| {
361 *drg.borrow_mut() = true;
362 *dsx.borrow_mut() = pe.position.x;
363 *dsv.borrow_mut() = cur;
364 }
365 })
366 .on_pointer_move({
367 let dsx = drag_start_x.clone();
368 let dsv = drag_start_val.clone();
369 let drg = is_dragging.clone();
370 let oc = oc.clone();
371 move |pe: PointerEvent| {
372 if !*drg.borrow() {
373 return;
374 }
375 let dx = pe.position.x - *dsx.borrow();
376 let new_val = (*dsv.borrow() + dx * speed).clamp(min, max);
377 (oc)(new_val);
378 }
379 })
380 .on_pointer_up({
381 let drg = is_dragging.clone();
382 move |_pe: PointerEvent| {
383 *drg.borrow_mut() = false;
384 }
385 })
386 .cursor(CursorIcon::EwResize))
387 .child(
388 Text(format_value(value))
389 .size(13.0)
390 .color(th.on_surface)
391 .single_line()
392 .overflow_ellipsize(),
393 )
394}
395
396fn format_value(v: f32) -> String {
397 if (v - v.round()).abs() < 1e-6 {
398 format!("{}", v.round() as i64)
399 } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
400 format!("{:.1}", v)
401 } else {
402 format!("{:.2}", v)
403 }
404}
405
406pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
407 View::new(
408 0,
409 ViewKind::Image {
410 handle,
411 tint: Color::WHITE,
412 fit: ImageFit::Contain,
413 },
414 )
415 .modifier(modifier)
416}
417
418pub trait ImageExt {
419 fn image_tint(self, c: Color) -> View;
420 fn image_fit(self, fit: ImageFit) -> View;
421}
422impl ImageExt for View {
423 fn image_tint(mut self, c: Color) -> View {
424 if let ViewKind::Image { tint, .. } = &mut self.kind {
425 *tint = c;
426 }
427 self
428 }
429 fn image_fit(mut self, fit: ImageFit) -> View {
430 if let ViewKind::Image { fit: f, .. } = &mut self.kind {
431 *f = fit;
432 }
433 self
434 }
435}
436
437fn flex_dir_for(kind: &ViewKind, modifier: &Modifier) -> Option<FlexDirection> {
438 if let Some(ref scroll) = modifier.scroll {
439 return Some(match scroll.axis() {
440 ScrollAxis::Vertical => FlexDirection::Column,
441 ScrollAxis::Horizontal => FlexDirection::Row,
442 ScrollAxis::Both => FlexDirection::Column,
443 });
444 }
445 match kind {
446 ViewKind::Row => {
447 if repose_core::locals::text_direction() == repose_core::locals::TextDirection::Rtl {
448 Some(FlexDirection::RowReverse)
449 } else {
450 Some(FlexDirection::Row)
451 }
452 }
453 ViewKind::Column => Some(FlexDirection::Column),
454 _ => None,
455 }
456}
457
458pub trait ViewExt: Sized {
460 fn child(self, children: impl IntoChildren) -> Self;
461}
462
463impl ViewExt for View {
464 fn child(self, children: impl IntoChildren) -> Self {
465 self.with_children(children.into_children())
466 }
467}
468
469pub trait IntoChildren {
470 fn into_children(self) -> Vec<View>;
471}
472
473impl IntoChildren for View {
474 fn into_children(self) -> Vec<View> {
475 vec![self]
476 }
477}
478
479impl IntoChildren for Vec<View> {
480 fn into_children(self) -> Vec<View> {
481 self
482 }
483}
484
485impl<const N: usize> IntoChildren for [View; N] {
486 fn into_children(self) -> Vec<View> {
487 self.into()
488 }
489}
490
491macro_rules! impl_into_children_tuple {
493 ($($idx:tt $t:ident),+) => {
494 impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
495 fn into_children(self) -> Vec<View> {
496 let mut v = Vec::new();
497 $(v.extend(self.$idx.into_children());)+
498 v
499 }
500 }
501 };
502}
503
504impl_into_children_tuple!(0 A);
505impl_into_children_tuple!(0 A, 1 B);
506impl_into_children_tuple!(0 A, 1 B, 2 C);
507impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
508impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
509impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
510impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
511impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
512
513pub fn layout_and_paint(
515 root: &View,
516 size_px_u32: (u32, u32),
517 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
518 interactions: &Interactions,
519 focused: Option<u64>,
520) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
521 LAYOUT_ENGINE.with(|engine| {
522 engine
523 .borrow_mut()
524 .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
525 })
526}
527
528pub trait TextStyle {
530 fn color(self, c: Color) -> View;
531 fn size(self, px: f32) -> View;
532 fn max_lines(self, n: usize) -> View;
533 fn single_line(self) -> View;
534 fn overflow_ellipsize(self) -> View;
535 fn overflow_clip(self) -> View;
536 fn overflow_visible(self) -> View;
537 fn font_family(self, family: &'static str) -> View;
538 fn text_align(self, align: TextAlign) -> View;
539 fn font_weight(self, weight: FontWeight) -> View;
540 fn font_style(self, style: FontStyle) -> View;
541 fn text_decoration(self, decoration: TextDecoration) -> View;
542 fn letter_spacing(self, spacing: f32) -> View;
543 fn line_height(self, height: f32) -> View;
544 fn url(self, url: impl Into<std::sync::Arc<str>>) -> View;
545}
546impl TextStyle for View {
547 fn color(mut self, c: Color) -> View {
548 if let ViewKind::Text {
549 color: text_color, ..
550 } = &mut self.kind
551 {
552 *text_color = c;
553 }
554 self
555 }
556 fn size(mut self, dp_font: f32) -> View {
557 if let ViewKind::Text {
558 font_size: text_size_dp,
559 ..
560 } = &mut self.kind
561 {
562 *text_size_dp = dp_font;
563 }
564 self
565 }
566 fn max_lines(mut self, n: usize) -> View {
567 if let ViewKind::Text {
568 max_lines,
569 soft_wrap,
570 ..
571 } = &mut self.kind
572 {
573 *max_lines = Some(n);
574 *soft_wrap = true;
575 }
576 self
577 }
578 fn single_line(mut self) -> View {
579 if let ViewKind::Text {
580 soft_wrap,
581 max_lines,
582 ..
583 } = &mut self.kind
584 {
585 *soft_wrap = false;
586 *max_lines = Some(1);
587 }
588 self
589 }
590 fn overflow_ellipsize(mut self) -> View {
591 if let ViewKind::Text { overflow, .. } = &mut self.kind {
592 *overflow = TextOverflow::Ellipsis;
593 }
594 self
595 }
596 fn overflow_clip(mut self) -> View {
597 if let ViewKind::Text { overflow, .. } = &mut self.kind {
598 *overflow = TextOverflow::Clip;
599 }
600 self
601 }
602 fn overflow_visible(mut self) -> View {
603 if let ViewKind::Text { overflow, .. } = &mut self.kind {
604 *overflow = TextOverflow::Visible;
605 }
606 self
607 }
608 fn font_family(mut self, family: &'static str) -> View {
609 if let ViewKind::Text {
610 font_family: ff, ..
611 } = &mut self.kind
612 {
613 *ff = Some(family);
614 }
615 self
616 }
617 fn text_align(mut self, align: TextAlign) -> View {
618 if let ViewKind::Text { text_align, .. } = &mut self.kind {
619 *text_align = align;
620 }
621 self
622 }
623 fn font_weight(mut self, weight: FontWeight) -> View {
624 if let ViewKind::Text { font_weight, .. } = &mut self.kind {
625 *font_weight = weight;
626 }
627 self
628 }
629 fn font_style(mut self, style: FontStyle) -> View {
630 if let ViewKind::Text { font_style, .. } = &mut self.kind {
631 *font_style = style;
632 }
633 self
634 }
635 fn text_decoration(mut self, decoration: TextDecoration) -> View {
636 if let ViewKind::Text {
637 text_decoration, ..
638 } = &mut self.kind
639 {
640 *text_decoration = decoration;
641 }
642 self
643 }
644 fn letter_spacing(mut self, spacing: f32) -> View {
645 if let ViewKind::Text { letter_spacing, .. } = &mut self.kind {
646 *letter_spacing = spacing;
647 }
648 self
649 }
650 fn line_height(mut self, height: f32) -> View {
651 if let ViewKind::Text { line_height, .. } = &mut self.kind {
652 *line_height = height;
653 }
654 self
655 }
656 fn url(mut self, url: impl Into<std::sync::Arc<str>>) -> View {
657 if let ViewKind::Text {
658 url: u,
659 text_decoration,
660 ..
661 } = &mut self.kind
662 {
663 *u = Some(url.into());
664 if !text_decoration.underline && !text_decoration.strikethrough {
665 *text_decoration = TextDecoration::UNDERLINE;
666 }
667 }
668 self
669 }
670}