#![allow(non_snake_case)]
use std::cell::Cell;
use std::rc::Rc;
use repose_core::NestedScrollConnection;
use repose_core::*;
use repose_ui::{
Box, Column, Row,
ViewExt,
};
use super::*;
use super::util::lerp_color;
#[derive(Clone, Copy, Debug)]
pub struct TopAppBarColors {
pub container_color: Color,
pub scrolled_container_color: Color,
pub navigation_icon_content_color: Color,
pub title_content_color: Color,
pub subtitle_content_color: Color,
pub action_icon_content_color: Color,
}
impl TopAppBarColors {
pub fn container_color(&self, scroll_fraction: f32) -> Color {
lerp_color(
self.container_color,
self.scrolled_container_color,
scroll_fraction.clamp(0.0, 1.0),
)
}
}
impl Default for TopAppBarColors {
fn default() -> Self {
Self {
container_color: TopAppBarDefaults::container_color(),
scrolled_container_color: TopAppBarDefaults::scrolled_container_color(),
navigation_icon_content_color: TopAppBarDefaults::navigation_icon_content_color(),
title_content_color: TopAppBarDefaults::title_content_color(),
subtitle_content_color: TopAppBarDefaults::subtitle_content_color(),
action_icon_content_color: TopAppBarDefaults::action_icon_content_color(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TopAppBarScrollMode {
Pinned,
EnterAlways,
}
pub struct TopAppBarScrollBehavior {
pub collapsed_offset: Signal<f32>,
pub height: f32,
pub collapsed_height: f32,
pub mode: TopAppBarScrollMode,
_pending: Rc<Cell<f32>>,
}
impl TopAppBarScrollBehavior {
pub fn new(height: f32, collapsed_height: f32, mode: TopAppBarScrollMode) -> Self {
Self {
collapsed_offset: signal(0.0),
height,
collapsed_height,
mode,
_pending: Rc::new(Cell::new(0.0)),
}
}
pub fn nested_scroll_connection(&self) -> NestedScrollConnection {
let off = self.collapsed_offset.clone();
let max_collapse = -(self.height - self.collapsed_height);
let mode = self.mode;
NestedScrollConnection::new().on_pre_scroll(move |d: Vec2, _source| -> Vec2 {
if mode == TopAppBarScrollMode::Pinned {
return Vec2::ZERO;
}
let current = off.get();
if d.y > 0.0 {
if current <= max_collapse {
return Vec2::ZERO;
}
let collapse_room = current - max_collapse;
let consume = d.y.min(collapse_room);
off.set(current - consume);
repose_core::request_frame();
Vec2 { x: 0.0, y: consume }
} else {
if current >= 0.0 {
return Vec2::ZERO;
}
let expansion_room = -current;
let consume = (-d.y).min(expansion_room);
off.set(current + consume);
repose_core::request_frame();
Vec2 { x: 0.0, y: consume }
}
})
}
pub fn offset(&self) -> f32 {
self.collapsed_offset.get()
}
}
#[derive(Clone, Debug)]
pub struct TopAppBarConfig {
pub modifier: Modifier,
pub colors: TopAppBarColors,
pub height: f32,
pub scroll_fraction: f32,
pub scroll_offset: f32,
pub window_insets: WindowInsets,
pub content_padding: PaddingValues,
}
#[derive(Clone, Copy, Debug)]
pub struct WindowInsets {
pub top: f32,
pub bottom: f32,
pub left: f32,
pub right: f32,
}
impl Default for WindowInsets {
fn default() -> Self {
Self {
top: 0.0,
bottom: 0.0,
left: 0.0,
right: 0.0,
}
}
}
impl Default for TopAppBarConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
colors: TopAppBarColors::default(),
height: TopAppBarDefaults::HEIGHT,
scroll_fraction: 0.0,
scroll_offset: 0.0,
window_insets: WindowInsets::default(),
content_padding: PaddingValues {
left: 4.0,
right: 4.0,
top: 0.0,
bottom: 0.0,
},
}
}
}
fn top_app_bar_layout(
title: View,
subtitle: Option<View>,
navigation_icon: Option<View>,
actions: Vec<View>,
config: TopAppBarConfig,
centered: bool,
) -> View {
let insets = config.window_insets;
let bg = config.colors.container_color(config.scroll_fraction);
let mut m = Modifier::new()
.min_width(200.0)
.height(config.height + insets.top)
.background(bg)
.translate(0.0, config.scroll_offset)
.padding_values(PaddingValues {
left: config.content_padding.left + insets.left,
right: config.content_padding.right + insets.right,
top: config.content_padding.top + insets.top,
bottom: config.content_padding.bottom + insets.bottom,
})
.align_items(AlignItems::CENTER)
.then(config.modifier);
if centered {
m = m.justify_content(JustifyContent::CENTER);
}
Row(m).child((
navigation_icon.unwrap_or(Box(Modifier::new().width(16.0).fill_max_height())),
Box(Modifier::new()
.padding_values(PaddingValues {
left: 16.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
})
.flex_grow(1.0))
.child(
Column(Modifier::new().justify_content(JustifyContent::CENTER)).child((
Box(Modifier::new()).child(with_content_color(
config.colors.title_content_color,
|| title,
)),
subtitle
.map(|s| {
Box(Modifier::new()).child(with_content_color(
config.colors.subtitle_content_color,
|| s,
))
})
.unwrap_or(Box(Modifier::new())),
)),
),
Row(Modifier::new()
.align_items(AlignItems::CENTER)
.clip_rounded(20.0))
.child(
actions
.into_iter()
.map(|a| {
with_content_color(config.colors.action_icon_content_color, move || a.clone())
})
.collect::<Vec<_>>(),
),
))
}
pub fn TopAppBar(
title: View,
subtitle: Option<View>,
navigation_icon: Option<View>,
actions: Vec<View>,
config: TopAppBarConfig,
) -> View {
top_app_bar_layout(title, subtitle, navigation_icon, actions, config, false)
}
pub fn CenterAlignedTopAppBar(
title: View,
subtitle: Option<View>,
navigation_icon: Option<View>,
actions: Vec<View>,
config: TopAppBarConfig,
) -> View {
top_app_bar_layout(title, subtitle, navigation_icon, actions, config, true)
}