#![allow(non_snake_case)]
use std::rc::Rc;
use repose_core::*;
use repose_ui::{Box, Column, Row, Text, TextStyle, ViewExt};
pub fn TopAppBar(
title: impl Into<String>,
navigation_icon: Option<View>,
actions: Vec<View>,
) -> View {
let th = theme();
Row(Modifier::new()
.fill_max_width()
.height(64.0)
.background(th.surface)
.padding_values(PaddingValues {
left: 4.0,
right: 4.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center))
.child((
navigation_icon.unwrap_or(Box(Modifier::new().size(16.0, 1.0))),
Box(Modifier::new()
.padding_values(PaddingValues {
left: 16.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
})
.flex_grow(1.0))
.child(
Text(title)
.color(th.on_surface)
.size(th.typography.title_large),
),
Row(Modifier::new().align_items(AlignItems::Center)).child(actions),
))
}
pub fn IconButton(icon: View, on_click: impl Fn() + 'static) -> View {
Box(Modifier::new()
.size(40.0, 40.0)
.clip_rounded(20.0)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(icon)
}
pub fn FilledIconButton(icon: View, on_click: impl Fn() + 'static) -> View {
let th = theme();
Box(Modifier::new()
.size(40.0, 40.0)
.clip_rounded(20.0)
.background(th.primary)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(icon)
}
pub fn FilledButton(on_click: impl Fn() + 'static, content: impl FnOnce() -> View) -> View {
let th = theme();
let content = with_content_color(th.on_primary, content);
Box(Modifier::new()
.height(40.0)
.min_width(48.0)
.background(th.primary)
.clip_rounded(20.0)
.padding_values(PaddingValues {
left: 24.0,
right: 24.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(content)
}
pub fn FilledTonalButton(on_click: impl Fn() + 'static, content: impl FnOnce() -> View) -> View {
let th = theme();
let content = with_content_color(th.on_secondary_container, content);
Box(Modifier::new()
.height(40.0)
.min_width(48.0)
.background(th.secondary_container)
.clip_rounded(20.0)
.padding_values(PaddingValues {
left: 24.0,
right: 24.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(content)
}
pub fn OutlinedButton(on_click: impl Fn() + 'static, content: impl FnOnce() -> View) -> View {
let th = theme();
let content = with_content_color(th.on_surface, content);
Box(Modifier::new()
.height(40.0)
.min_width(48.0)
.border(1.0, th.outline_variant, 20.0)
.clip_rounded(20.0)
.padding_values(PaddingValues {
left: 24.0,
right: 24.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(content)
}
pub fn TextButton(on_click: impl Fn() + 'static, content: impl FnOnce() -> View) -> View {
let th = theme();
let content = with_content_color(th.on_surface, content);
Box(Modifier::new()
.height(40.0)
.min_width(48.0)
.clip_rounded(20.0)
.padding_values(PaddingValues {
left: 12.0,
right: 12.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(content)
}
pub fn FAB(icon: View, on_click: impl Fn() + 'static) -> View {
let th = theme();
Box(Modifier::new()
.size(56.0, 56.0)
.background(th.primary_container)
.clip_rounded(16.0)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(icon)
}
pub fn SmallFAB(icon: View, on_click: impl Fn() + 'static) -> View {
let th = theme();
Box(Modifier::new()
.size(40.0, 40.0)
.background(th.primary_container)
.clip_rounded(12.0)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(icon)
}
pub fn LargeFAB(icon: View, on_click: impl Fn() + 'static) -> View {
let th = theme();
Box(Modifier::new()
.size(96.0, 96.0)
.background(th.primary_container)
.clip_rounded(28.0)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child(icon)
}
pub fn ExtendedFAB(
icon: Option<View>,
label: impl Into<String>,
on_click: impl Fn() + 'static,
) -> View {
let th = theme();
let has_icon = icon.is_some();
Row(Modifier::new()
.height(56.0)
.min_width(80.0)
.background(th.primary_container)
.clip_rounded(16.0)
.padding_values(PaddingValues {
left: 16.0,
right: 20.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.clickable()
.on_pointer_down(move |_| on_click()))
.child((
icon.unwrap_or(Box(Modifier::new())),
Box(Modifier::new().size(if has_icon { 12.0 } else { 0.0 }, 1.0)),
Text(label)
.color(th.on_primary_container)
.size(th.typography.label_large)
.single_line(),
))
}
pub fn Divider() -> View {
let th = theme();
Box(Modifier::new()
.fill_max_width()
.height(1.0)
.background(th.outline_variant))
}
pub fn VerticalDivider() -> View {
let th = theme();
Box(Modifier::new()
.width(1.0)
.fill_max_height()
.background(th.outline_variant))
}
pub fn Badge(label: Option<impl Into<String>>) -> View {
let th = theme();
match label {
None => Box(Modifier::new()
.size(6.0, 6.0)
.background(th.error)
.clip_rounded(3.0)),
Some(text) => {
let text = text.into();
Box(Modifier::new()
.min_width(16.0)
.height(16.0)
.background(th.error)
.clip_rounded(8.0)
.padding_values(PaddingValues {
left: 4.0,
right: 4.0,
top: 0.0,
bottom: 0.0,
})
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center))
.child(
Text(text)
.color(th.on_error)
.size(th.typography.label_small)
.single_line(),
)
}
}
}
pub fn ListItem(
headline: impl Into<String>,
supporting_text: Option<String>,
leading: Option<View>,
trailing: Option<View>,
on_click: Option<Rc<dyn Fn()>>,
) -> View {
let th = theme();
let mut modifier = Modifier::new()
.fill_max_width()
.min_height(if supporting_text.is_some() {
72.0
} else {
56.0
})
.padding_values(PaddingValues {
left: 16.0,
right: 24.0,
top: 8.0,
bottom: 8.0,
})
.align_items(AlignItems::Center);
if let Some(cb) = on_click {
modifier = modifier.clickable().on_pointer_down(move |_| cb());
}
Row(modifier).child((
leading
.map(|v| {
Box(Modifier::new().padding_values(PaddingValues {
left: 0.0,
right: 16.0,
top: 0.0,
bottom: 0.0,
}))
.child(v)
})
.unwrap_or(Box(Modifier::new())),
Column(
Modifier::new()
.flex_grow(1.0)
.justify_content(JustifyContent::Center),
)
.child((
Text(headline)
.color(th.on_surface)
.size(th.typography.body_large)
.single_line(),
supporting_text
.map(|st| {
Text(st)
.color(th.on_surface_variant)
.size(th.typography.body_medium)
.max_lines(2)
.overflow_ellipsize()
})
.unwrap_or(Box(Modifier::new())),
)),
trailing
.map(|v| {
Box(Modifier::new().padding_values(PaddingValues {
left: 16.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
}))
.child(v)
})
.unwrap_or(Box(Modifier::new())),
))
}
pub struct Tab {
pub label: String,
pub icon: Option<View>,
pub on_click: Rc<dyn Fn()>,
}
pub fn TabRow(selected_index: usize, tabs: Vec<Tab>) -> View {
let th = theme();
Row(Modifier::new()
.fill_max_width()
.height(48.0)
.background(th.surface))
.child(
tabs.into_iter()
.enumerate()
.map(|(i, tab)| {
let selected = i == selected_index;
let color = if selected {
th.primary
} else {
th.on_surface_variant
};
let cb = tab.on_click.clone();
Column(
Modifier::new()
.flex_grow(1.0)
.fill_max_height()
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.clickable()
.on_pointer_down(move |_| cb()),
)
.child((
tab.icon.unwrap_or(Box(Modifier::new())),
Text(tab.label)
.color(color)
.size(th.typography.title_small)
.single_line(),
if selected {
Box(Modifier::new()
.fill_max_width()
.height(3.0)
.background(th.primary)
.clip_rounded(1.5))
} else {
Box(Modifier::new().height(3.0))
},
))
})
.collect::<Vec<_>>(),
)
}
pub struct Segment {
pub label: String,
pub icon: Option<View>,
pub on_click: Rc<dyn Fn()>,
}
pub fn SegmentedButton(selected: &[usize], segments: Vec<Segment>) -> View {
let th = theme();
let count = segments.len();
Row(Modifier::new()
.height(40.0)
.border(1.0, th.outline, 20.0)
.clip_rounded(20.0))
.child(
segments
.into_iter()
.enumerate()
.map(|(i, seg)| {
let is_selected = selected.contains(&i);
let bg = if is_selected {
th.secondary_container
} else {
Color::TRANSPARENT
};
let fg = if is_selected {
th.on_secondary_container
} else {
th.on_surface
};
let cb = seg.on_click.clone();
let mut modifier = Modifier::new()
.flex_grow(1.0)
.fill_max_height()
.background(bg)
.align_items(AlignItems::Center)
.justify_content(JustifyContent::Center)
.padding_values(PaddingValues {
left: 12.0,
right: 12.0,
top: 0.0,
bottom: 0.0,
})
.clickable()
.on_pointer_down(move |_| cb());
if i < count - 1 {
modifier = modifier.border(1.0, th.outline, 0.0);
}
Row(modifier).child((
seg.icon.unwrap_or(Box(Modifier::new())),
Text(seg.label)
.color(fg)
.size(th.typography.label_large)
.single_line(),
))
})
.collect::<Vec<_>>(),
)
}
pub fn CircularProgress(value: Option<f32>) -> View {
View::new(
0,
ViewKind::ProgressBar {
value: value.unwrap_or(0.0),
min: 0.0,
max: 1.0,
circular: true,
},
)
.modifier(Modifier::new().size(48.0, 48.0))
.semantics(Semantics {
role: Role::ProgressBar,
label: None,
focused: false,
enabled: true,
})
}