use crate::ui::Ui;
use zenthra_core::{Color, Id, Align};
pub struct PanelBuilder<'u, 'a, 'b> {
ui: &'u mut Ui<'a>,
id: Id,
width: Option<f32>,
height: Option<f32>,
padding: f32,
bg: Option<Color>,
border_color: Option<Color>,
border_width: f32,
radius: f32,
title: Option<String>,
subtitle: Option<String>,
header_bg: Option<Color>,
header_padding: f32,
collapsible: bool,
collapsed: Option<&'b mut bool>,
shadow_color: Option<Color>,
shadow_offset: [f32; 2],
shadow_blur: f32,
shadow_opacity: f32,
}
impl<'u, 'a, 'b> PanelBuilder<'u, 'a, 'b> {
pub fn new(ui: &'u mut Ui<'a>) -> Self {
let id = ui.id();
Self {
ui,
id,
width: None,
height: None,
padding: 0.0,
bg: None,
border_color: None,
border_width: 0.0,
radius: 0.0,
title: None,
subtitle: None,
header_bg: None,
header_padding: 0.0,
collapsible: false,
collapsed: None,
shadow_color: None,
shadow_offset: [0.0, 0.0],
shadow_blur: 0.0,
shadow_opacity: 0.0,
}
}
pub fn id(mut self, id: impl std::hash::Hash) -> Self {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
use std::hash::Hasher;
id.hash(&mut hasher);
self.id = Id::from_u64(hasher.finish());
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
self.subtitle = Some(subtitle.into());
self
}
pub fn collapsible(mut self, collapsible: bool) -> Self {
self.collapsible = collapsible;
self
}
pub fn collapsed(mut self, state: &'b mut bool) -> Self {
self.collapsed = Some(state);
self
}
pub fn width(mut self, w: f32) -> Self {
self.width = Some(w);
self
}
pub fn height(mut self, h: f32) -> Self {
self.height = Some(h);
self
}
pub fn size(mut self, w: f32, h: f32) -> Self {
self.width = Some(w);
self.height = Some(h);
self
}
pub fn padding(mut self, p: f32) -> Self {
self.padding = p;
self
}
pub fn bg(mut self, bg: Color) -> Self {
self.bg = Some(bg);
self
}
pub fn border(mut self, color: Color, width: f32) -> Self {
self.border_color = Some(color);
self.border_width = width;
self
}
pub fn radius(mut self, r: f32) -> Self {
self.radius = r;
self
}
pub fn header_bg(mut self, color: Color) -> Self {
self.header_bg = Some(color);
self
}
pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
self.shadow_color = Some(color);
self.shadow_offset = [x, y];
self.shadow_blur = blur;
self
}
pub fn show<F>(mut self, f: F)
where F: FnOnce(&mut Ui) {
let collapsed_id = Id::from_u64(self.id.raw().wrapping_add(8000000));
let mut is_collapsed = if let Some(ref state) = self.collapsed {
**state
} else {
self.ui.interaction_state.get(&collapsed_id).map(|&v| v > 0.5).unwrap_or(false)
};
let mut panel_container = self.ui.container()
.id(self.id)
.column();
if let Some(bg) = self.bg {
panel_container = panel_container.bg(bg);
}
if let Some(bc) = self.border_color {
panel_container = panel_container.border(bc, self.border_width);
}
if self.radius != 0.0 {
panel_container = panel_container.radius_all(self.radius);
}
if let Some(sc) = self.shadow_color {
panel_container = panel_container.shadow(sc, self.shadow_offset[0], self.shadow_offset[1], self.shadow_blur)
.shadow_opacity(self.shadow_opacity);
}
if let Some(w) = self.width {
panel_container = panel_container.width(w);
}
if let Some(h) = self.height {
panel_container = panel_container.height(h);
}
panel_container.show(|ui: &mut Ui| {
let has_header = self.title.is_some() || self.subtitle.is_some() || self.collapsible;
if has_header {
let header_id = Id::from_u64(self.id.raw().wrapping_add(9000000));
let mut header_container = ui.container()
.id(header_id)
.full_width()
.row()
.halign(Align::SpaceBetween)
.valign(Align::Center)
.padding_all(self.header_padding);
if let Some(hbg) = self.header_bg {
header_container = header_container.bg(hbg);
}
let bottom_r = if is_collapsed { self.radius } else { 0.0 };
header_container = header_container.radius(self.radius, self.radius, bottom_r, bottom_r);
let header_resp = header_container.show(|ui: &mut Ui| {
ui.container()
.column()
.show(|ui: &mut Ui| {
if let Some(ref t) = self.title {
ui.text(t).size(14.0).bold().color(Color::WHITE).show();
}
if let Some(ref s) = self.subtitle {
if self.title.is_some() {
ui.spacing(2.0);
}
ui.text(s).size(11.0).color(Color::rgb(0.55, 0.55, 0.65)).show();
}
});
if self.collapsible {
let chevron = if is_collapsed {
crate::icons::NF_FA_CHEVRON_RIGHT
} else {
crate::icons::NF_FA_CHEVRON_DOWN
};
ui.text(chevron).size(12.0).color(Color::rgb(0.6, 0.6, 0.7)).show();
}
});
if self.collapsible && header_resp.clicked {
is_collapsed = !is_collapsed;
if let Some(ref mut state) = self.collapsed {
**state = is_collapsed;
} else {
ui.interaction_state.insert(collapsed_id, if is_collapsed { 1.0 } else { 0.0 });
}
ui.needs_redraw = true;
}
}
if !is_collapsed {
if has_header {
let mut div = ui.container()
.full_width()
.height(1.0);
if let Some(bc) = self.border_color {
div = div.bg(bc);
}
div.show(|_| {});
}
ui.container()
.full_width()
.column()
.padding_all(self.padding)
.show(f);
}
});
}
}