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