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, LazyColumnState, LazyGridState, LazyRow, LazyRowState, LazyVerticalGrid,
117 LazyVerticalStaggeredGrid, LazyVerticalStaggeredGridState, SimpleList,
118};
119pub use subcompose::{
120 BoxWithConstraints, SubcomposeLayout, box_with_constraints_with_key, subcompose_hash_key,
121 subcompose_layout_with_slots, subcompose_with_key, subcompose_with_key_slots,
122};
123pub mod overlay;
124pub mod pager;
125pub mod scroll;
126pub mod windowing;
127
128use std::cell::RefCell;
129use std::collections::{HashMap, HashSet};
130use std::rc::Rc;
131use std::sync::atomic::{AtomicU64, Ordering};
132
133use repose_core::*;
134use taffy::style::FlexDirection;
135
136pub mod textfield;
137use repose_core::locals;
138pub use selection::SelectableText;
139pub use textfield::{
140 KeyboardOptions, TextArea, TextAreaEx, TextField, TextFieldEx, TextFieldState,
141};
142
143thread_local! {
144 static LAYOUT_ENGINE: RefCell<layout::LayoutEngine> =
145 RefCell::new(layout::LayoutEngine::new());
146}
147
148#[derive(Default)]
149pub struct Interactions {
150 pub hover: Option<u64>,
151 pub pressed: HashSet<u64>,
152}
153
154pub fn Box(modifier: Modifier) -> View {
155 View::new(0, ViewKind::Box).modifier(modifier)
156}
157
158pub fn Row(modifier: Modifier) -> View {
159 View::new(0, ViewKind::Row).modifier(modifier)
160}
161
162pub fn Column(modifier: Modifier) -> View {
163 View::new(0, ViewKind::Column).modifier(modifier)
164}
165
166pub fn FlowRow(modifier: Modifier) -> View {
169 Row(modifier.flex_wrap(FlexWrap::Wrap))
170}
171
172pub fn FlowColumn(modifier: Modifier) -> View {
175 Column(modifier.flex_wrap(FlexWrap::Wrap))
176}
177
178pub fn Center(modifier: Modifier) -> View {
180 Box(modifier.align_self(AlignSelf::Center))
181}
182
183pub fn Stack(modifier: Modifier) -> View {
184 View::new(0, ViewKind::Stack).modifier(modifier)
185}
186
187pub fn ZStack(modifier: Modifier) -> View {
188 View::new(0, ViewKind::ZStack).modifier(modifier)
189}
190
191pub fn OverlayHost(modifier: Modifier) -> View {
192 View::new(0, ViewKind::OverlayHost).modifier(modifier)
193}
194
195#[deprecated = "Use ScollArea instead"]
196pub fn Scroll(modifier: Modifier) -> View {
197 View::new(
198 0,
199 ViewKind::ScrollV {
200 on_scroll: None,
201 set_viewport_height: None,
202 set_content_height: None,
203 get_scroll_offset: None,
204 set_scroll_offset: None,
205 show_scrollbar: true,
206 tick_scroll: None,
207 },
208 )
209 .modifier(modifier)
210}
211
212pub fn Text(text: impl Into<String>) -> View {
213 View::new(
214 0,
215 ViewKind::Text {
216 text: text.into(),
217 color: locals::content_color(),
218 font_size: 16.0, soft_wrap: true,
220 max_lines: None,
221 overflow: TextOverflow::Visible,
222 font_family: None,
223 annotations: None,
224 },
225 )
226}
227
228pub fn AnnotatedText(annotated: AnnotatedString) -> View {
232 let annotations: Option<std::sync::Arc<[TextSpan]>> = if annotated.spans.is_empty() {
233 None
234 } else {
235 Some(annotated.spans.clone())
236 };
237 View::new(
238 0,
239 ViewKind::Text {
240 text: annotated.text,
241 color: locals::content_color(),
242 font_size: 16.0,
243 soft_wrap: true,
244 max_lines: None,
245 overflow: TextOverflow::Visible,
246 font_family: None,
247 annotations,
248 },
249 )
250}
251
252pub fn Spacer() -> View {
253 Box(Modifier::new().flex_grow(1.0))
254}
255
256pub fn Space(modifier: Modifier) -> View {
257 Box(modifier)
258}
259
260pub fn Grid(
261 columns: usize,
262 modifier: Modifier,
263 children: Vec<View>,
264 row_gap: f32,
265 column_gap: f32,
266) -> View {
267 Column(modifier.grid(columns, row_gap, column_gap)).with_children(children)
268}
269
270pub fn Expander(modifier: Modifier, expanded: bool, on_toggle: impl Fn() + 'static) -> View {
271 View::new(
272 0,
273 ViewKind::Expander {
274 expanded,
275 on_toggle: Some(Rc::new(on_toggle)),
276 },
277 )
278 .modifier(modifier)
279}
280
281pub fn TreeRow(
287 modifier: Modifier,
288 depth: usize,
289 has_children: bool,
290 is_expanded: bool,
291 is_selected: bool,
292 on_toggle: impl Fn() + 'static,
293 on_select: impl Fn() + 'static,
294) -> View {
295 View::new(
296 0,
297 ViewKind::TreeRow {
298 depth,
299 has_children,
300 is_expanded,
301 is_selected,
302 on_toggle: Some(Rc::new(on_toggle)),
303 on_select: Some(Rc::new(on_select)),
304 },
305 )
306 .modifier(modifier)
307}
308
309static DRAGVALUE_COUNTER: AtomicU64 = AtomicU64::new(0);
310
311pub fn DragValue(
316 value: f32,
317 range: (f32, f32),
318 speed: f32,
319 on_change: impl Fn(f32) + 'static,
320) -> View {
321 let id = DRAGVALUE_COUNTER.fetch_add(1, Ordering::Relaxed);
322 let drag_start_x = remember_state_with_key(format!("dv_dsx_{}", id), || 0.0f32);
323 let drag_start_val = remember_state_with_key(format!("dv_dsv_{}", id), || 0.0f32);
324 let is_dragging = remember_state_with_key(format!("dv_drg_{}", id), || false);
325
326 let oc = Rc::new(on_change);
327 let min = range.0;
328 let max = range.1;
329 let cur = value;
330
331 let th = locals::theme();
332
333 Box(Modifier::new()
334 .min_width(48.0)
335 .height(28.0)
336 .background(th.surface_container)
337 .border(1.0, th.outline, 4.0)
338 .clip_rounded(4.0)
339 .padding_values(PaddingValues {
340 left: 4.0,
341 right: 4.0,
342 top: 0.0,
343 bottom: 0.0,
344 })
345 .on_pointer_down({
346 let dsx = drag_start_x.clone();
347 let dsv = drag_start_val.clone();
348 let drg = is_dragging.clone();
349 move |pe: PointerEvent| {
350 *drg.borrow_mut() = true;
351 *dsx.borrow_mut() = pe.position.x;
352 *dsv.borrow_mut() = cur;
353 }
354 })
355 .on_pointer_move({
356 let dsx = drag_start_x.clone();
357 let dsv = drag_start_val.clone();
358 let drg = is_dragging.clone();
359 let oc = oc.clone();
360 move |pe: PointerEvent| {
361 if !*drg.borrow() {
362 return;
363 }
364 let dx = pe.position.x - *dsx.borrow();
365 let new_val = (*dsv.borrow() + dx * speed).clamp(min, max);
366 (oc)(new_val);
367 }
368 })
369 .on_pointer_up({
370 let drg = is_dragging.clone();
371 move |_pe: PointerEvent| {
372 *drg.borrow_mut() = false;
373 }
374 })
375 .cursor(CursorIcon::EwResize))
376 .child(
377 Text(format_value(value))
378 .size(13.0)
379 .color(th.on_surface)
380 .single_line()
381 .overflow_ellipsize(),
382 )
383}
384
385fn format_value(v: f32) -> String {
386 if (v - v.round()).abs() < 1e-6 {
387 format!("{}", v.round() as i64)
388 } else if (v * 10.0 - (v * 10.0).round()).abs() < 1e-6 {
389 format!("{:.1}", v)
390 } else {
391 format!("{:.2}", v)
392 }
393}
394
395pub fn Image(modifier: Modifier, handle: ImageHandle) -> View {
396 View::new(
397 0,
398 ViewKind::Image {
399 handle,
400 tint: Color::WHITE,
401 fit: ImageFit::Contain,
402 },
403 )
404 .modifier(modifier)
405}
406
407pub trait ImageExt {
408 fn image_tint(self, c: Color) -> View;
409 fn image_fit(self, fit: ImageFit) -> View;
410}
411impl ImageExt for View {
412 fn image_tint(mut self, c: Color) -> View {
413 if let ViewKind::Image { tint, .. } = &mut self.kind {
414 *tint = c;
415 }
416 self
417 }
418 fn image_fit(mut self, fit: ImageFit) -> View {
419 if let ViewKind::Image { fit: f, .. } = &mut self.kind {
420 *f = fit;
421 }
422 self
423 }
424}
425
426fn flex_dir_for(kind: &ViewKind) -> Option<FlexDirection> {
427 match kind {
428 ViewKind::Row => {
429 if repose_core::locals::text_direction() == repose_core::locals::TextDirection::Rtl {
430 Some(FlexDirection::RowReverse)
431 } else {
432 Some(FlexDirection::Row)
433 }
434 }
435 ViewKind::Column | ViewKind::ScrollV { .. } => Some(FlexDirection::Column),
436 _ => None,
437 }
438}
439
440pub trait ViewExt: Sized {
442 fn child(self, children: impl IntoChildren) -> Self;
443}
444
445impl ViewExt for View {
446 fn child(self, children: impl IntoChildren) -> Self {
447 self.with_children(children.into_children())
448 }
449}
450
451pub trait IntoChildren {
452 fn into_children(self) -> Vec<View>;
453}
454
455impl IntoChildren for View {
456 fn into_children(self) -> Vec<View> {
457 vec![self]
458 }
459}
460
461impl IntoChildren for Vec<View> {
462 fn into_children(self) -> Vec<View> {
463 self
464 }
465}
466
467impl<const N: usize> IntoChildren for [View; N] {
468 fn into_children(self) -> Vec<View> {
469 self.into()
470 }
471}
472
473macro_rules! impl_into_children_tuple {
475 ($($idx:tt $t:ident),+) => {
476 impl<$($t: IntoChildren),+> IntoChildren for ($($t,)+) {
477 fn into_children(self) -> Vec<View> {
478 let mut v = Vec::new();
479 $(v.extend(self.$idx.into_children());)+
480 v
481 }
482 }
483 };
484}
485
486impl_into_children_tuple!(0 A);
487impl_into_children_tuple!(0 A, 1 B);
488impl_into_children_tuple!(0 A, 1 B, 2 C);
489impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D);
490impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
491impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
492impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
493impl_into_children_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
494
495pub fn layout_and_paint(
497 root: &View,
498 size_px_u32: (u32, u32),
499 textfield_states: &HashMap<u64, Rc<RefCell<TextFieldState>>>,
500 interactions: &Interactions,
501 focused: Option<u64>,
502) -> (Scene, Vec<HitRegion>, Vec<SemNode>) {
503 LAYOUT_ENGINE.with(|engine| {
504 engine
505 .borrow_mut()
506 .layout_frame(root, size_px_u32, textfield_states, interactions, focused)
507 })
508}
509
510pub trait TextStyle {
512 fn color(self, c: Color) -> View;
513 fn size(self, px: f32) -> View;
514 fn max_lines(self, n: usize) -> View;
515 fn single_line(self) -> View;
516 fn overflow_ellipsize(self) -> View;
517 fn overflow_clip(self) -> View;
518 fn overflow_visible(self) -> View;
519 fn font_family(self, family: &'static str) -> View;
520}
521impl TextStyle for View {
522 fn color(mut self, c: Color) -> View {
523 if let ViewKind::Text {
524 color: text_color, ..
525 } = &mut self.kind
526 {
527 *text_color = c;
528 }
529 self
530 }
531 fn size(mut self, dp_font: f32) -> View {
532 if let ViewKind::Text {
533 font_size: text_size_dp,
534 ..
535 } = &mut self.kind
536 {
537 *text_size_dp = dp_font;
538 }
539 self
540 }
541 fn max_lines(mut self, n: usize) -> View {
542 if let ViewKind::Text {
543 max_lines,
544 soft_wrap,
545 ..
546 } = &mut self.kind
547 {
548 *max_lines = Some(n);
549 *soft_wrap = true;
550 }
551 self
552 }
553 fn single_line(mut self) -> View {
554 if let ViewKind::Text {
555 soft_wrap,
556 max_lines,
557 ..
558 } = &mut self.kind
559 {
560 *soft_wrap = false;
561 *max_lines = Some(1);
562 }
563 self
564 }
565 fn overflow_ellipsize(mut self) -> View {
566 if let ViewKind::Text { overflow, .. } = &mut self.kind {
567 *overflow = TextOverflow::Ellipsis;
568 }
569 self
570 }
571 fn overflow_clip(mut self) -> View {
572 if let ViewKind::Text { overflow, .. } = &mut self.kind {
573 *overflow = TextOverflow::Clip;
574 }
575 self
576 }
577 fn overflow_visible(mut self) -> View {
578 if let ViewKind::Text { overflow, .. } = &mut self.kind {
579 *overflow = TextOverflow::Visible;
580 }
581 self
582 }
583 fn font_family(mut self, family: &'static str) -> View {
584 if let ViewKind::Text {
585 font_family: ff, ..
586 } = &mut self.kind
587 {
588 *ff = Some(family);
589 }
590 self
591 }
592}