#![allow(non_snake_case)]
use crate::composable;
use crate::modifier::Modifier;
use crate::widgets::wear::density::WearDensity;
use crate::widgets::wear::theme::{WearColors, WearTextStyle};
use crate::widgets::{Layout, Text};
use cranpose_core::NodeId;
use cranpose_ui_graphics::Size;
use cranpose_ui_layout::{Constraints, Measurable, MeasurePolicy, MeasureResult, Placement};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ListHeaderSpec {
pub colors: WearColors,
pub text_style: WearTextStyle,
pub min_height: f32,
pub padding_start: f32,
pub padding_top: f32,
pub padding_end: f32,
pub padding_bottom: f32,
}
impl Default for ListHeaderSpec {
fn default() -> Self {
Self {
colors: WearColors::default(),
text_style: WearTextStyle::TITLE_MEDIUM,
min_height: 48.0,
padding_start: 14.0,
padding_top: 16.0,
padding_end: 14.0,
padding_bottom: 12.0,
}
}
}
impl ListHeaderSpec {
pub fn colors(mut self, colors: WearColors) -> Self {
self.colors = colors;
self
}
}
#[composable]
pub fn ListHeader<S>(modifier: Modifier, spec: ListHeaderSpec, label: S) -> NodeId
where
S: crate::widgets::IntoTextSource + Clone + PartialEq + 'static,
{
let style = spec.text_style.resolve(spec.colors.on_background);
Layout(
modifier,
ListHeaderMeasurePolicy {
spec,
density: WearDensity::current().density(),
},
move || {
Text(label.clone(), Modifier::empty(), style.clone());
},
)
}
#[derive(Clone, Debug, PartialEq)]
struct ListHeaderMeasurePolicy {
spec: ListHeaderSpec,
density: f32,
}
impl ListHeaderMeasurePolicy {
fn horizontal(&self, density: WearDensity) -> f32 {
density.dp(self.spec.padding_start) + density.dp(self.spec.padding_end)
}
fn vertical(&self, density: WearDensity) -> f32 {
density.dp(self.spec.padding_top) + density.dp(self.spec.padding_bottom)
}
}
impl MeasurePolicy for ListHeaderMeasurePolicy {
fn measure(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> Size {
placements.clear();
let density = WearDensity::new(self.density, 1.0);
let horizontal = self.horizontal(density);
let available = (constraints.max_width - horizontal).max(0.0);
let child_constraints = Constraints {
min_width: 0.0,
max_width: available,
min_height: 0.0,
max_height: f32::INFINITY,
};
let mut content_width = 0.0f32;
let mut content_height = 0.0f32;
let mut placeables = Vec::with_capacity(measurables.len());
for measurable in measurables {
let placeable = measurable.measure(child_constraints);
content_width = content_width.max(placeable.width());
content_height = content_height.max(placeable.height());
placeables.push(placeable);
}
let width = density
.ceil(content_width + horizontal)
.clamp(constraints.min_width, constraints.max_width);
let padded = density.ceil(content_height) + self.vertical(density);
let height = density
.dp(self.spec.min_height)
.max(padded)
.clamp(constraints.min_height, constraints.max_height);
let surplus = density.centre(height, padded);
let top = density.dp(self.spec.padding_top) + surplus;
for placeable in placeables {
let x = density.centre(width, placeable.width());
placements.push(Placement::new(placeable.node_id(), x, top, 0));
}
Size::new(width, height)
}
fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
let density = WearDensity::new(self.density, 1.0);
measurables
.iter()
.map(|m| m.min_intrinsic_width(height))
.fold(0.0, f32::max)
+ self.horizontal(density)
}
fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
let density = WearDensity::new(self.density, 1.0);
measurables
.iter()
.map(|m| m.max_intrinsic_width(height))
.fold(0.0, f32::max)
+ self.horizontal(density)
}
fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
let density = WearDensity::new(self.density, 1.0);
let content = measurables
.iter()
.map(|m| m.min_intrinsic_height(width))
.fold(0.0, f32::max);
density
.dp(self.spec.min_height)
.max(density.ceil(content) + self.vertical(density))
}
fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
self.min_intrinsic_height(measurables, width)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_defaults_are_the_ones_the_tokens_declare() {
let spec = ListHeaderSpec::default();
assert_eq!(spec.min_height, 48.0, "ListHeaderTokens.Height");
assert_eq!(spec.padding_start, 14.0);
assert_eq!(spec.padding_end, 14.0);
assert_eq!(spec.padding_top, 16.0);
assert_eq!(spec.padding_bottom, 12.0);
assert_eq!(spec.text_style, WearTextStyle::TITLE_MEDIUM);
}
}