use super::{Widget, WidgetBase, WidgetId, LayoutContext, PaintContext, EventContext};
use crate::css::{ClassList, WidgetState};
use crate::event::{Event, EventResult};
use crate::geometry::{BorderRadius, Color, Rect, Size, EdgeInsets};
use crate::layout::{Constraints, LayoutResult};
use crate::render::Painter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BarPosition {
#[default]
Top,
Bottom,
Left,
Right,
}
impl BarPosition {
pub fn is_horizontal(&self) -> bool {
matches!(self, BarPosition::Top | BarPosition::Bottom)
}
pub fn is_vertical(&self) -> bool {
matches!(self, BarPosition::Left | BarPosition::Right)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BarVariant {
#[default]
Solid,
Transparent,
Floating,
Minimal,
}
pub struct Bar {
base: WidgetBase,
position: BarPosition,
variant: BarVariant,
thickness: f32,
padding: EdgeInsets,
start: Option<Box<dyn Widget>>,
center: Option<Box<dyn Widget>>,
end: Option<Box<dyn Widget>>,
gap: f32,
auto_hide: bool,
visible: bool,
background: Option<Color>,
border_radius: Option<f32>,
}
impl Bar {
pub fn new() -> Self {
Self {
base: WidgetBase::new().with_class("bar"),
position: BarPosition::default(),
variant: BarVariant::default(),
thickness: 40.0,
padding: EdgeInsets::symmetric(8.0, 12.0),
start: None,
center: None,
end: None,
gap: 8.0,
auto_hide: false,
visible: true,
background: None,
border_radius: None,
}
}
pub fn position(mut self, position: BarPosition) -> Self {
self.position = position;
self
}
pub fn variant(mut self, variant: BarVariant) -> Self {
self.variant = variant;
self
}
pub fn thickness(mut self, thickness: f32) -> Self {
self.thickness = thickness;
self
}
pub fn height(self, height: f32) -> Self {
self.thickness(height)
}
pub fn width(self, width: f32) -> Self {
self.thickness(width)
}
pub fn padding(mut self, padding: f32) -> Self {
self.padding = EdgeInsets::all(padding);
self
}
pub fn padding_xy(mut self, horizontal: f32, vertical: f32) -> Self {
self.padding = EdgeInsets::symmetric(vertical, horizontal);
self
}
pub fn start<W: Widget + 'static>(mut self, widget: W) -> Self {
self.start = Some(Box::new(widget));
self
}
pub fn center<W: Widget + 'static>(mut self, widget: W) -> Self {
self.center = Some(Box::new(widget));
self
}
pub fn end<W: Widget + 'static>(mut self, widget: W) -> Self {
self.end = Some(Box::new(widget));
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.gap = gap;
self
}
pub fn auto_hide(mut self, auto_hide: bool) -> Self {
self.auto_hide = auto_hide;
self
}
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
pub fn border_radius(mut self, radius: f32) -> Self {
self.border_radius = Some(radius);
self
}
pub fn class(mut self, class: &str) -> Self {
self.base.classes.add(class);
self
}
pub fn show(&mut self) {
self.visible = true;
}
pub fn hide(&mut self) {
self.visible = false;
}
fn get_background_color(&self, theme: &crate::theme::ThemeData) -> Color {
if let Some(color) = self.background {
return color;
}
match self.variant {
BarVariant::Solid => theme.colors.card,
BarVariant::Transparent => theme.colors.background.with_alpha(0.8),
BarVariant::Floating => theme.colors.card,
BarVariant::Minimal => Color::TRANSPARENT,
}
}
fn get_border_radius(&self, theme: &crate::theme::ThemeData) -> BorderRadius {
let radius = match self.variant {
BarVariant::Floating => {
self.border_radius.unwrap_or(theme.radii.lg * theme.typography.base_size)
}
_ => self.border_radius.unwrap_or(0.0),
};
BorderRadius::all(radius)
}
fn get_margin(&self) -> f32 {
match self.variant {
BarVariant::Floating => 8.0,
_ => 0.0,
}
}
}
impl Default for Bar {
fn default() -> Self {
Self::new()
}
}
impl Widget for Bar {
fn id(&self) -> WidgetId {
self.base.id
}
fn type_name(&self) -> &'static str {
"bar"
}
fn element_id(&self) -> Option<&str> {
self.base.element_id.as_deref()
}
fn classes(&self) -> &ClassList {
&self.base.classes
}
fn state(&self) -> WidgetState {
self.base.state
}
fn intrinsic_size(&self, _ctx: &LayoutContext) -> Size {
let margin = self.get_margin() * 2.0;
if self.position.is_horizontal() {
Size::new(f32::MAX, self.thickness + margin)
} else {
Size::new(self.thickness + margin, f32::MAX)
}
}
fn layout(&mut self, constraints: Constraints, ctx: &LayoutContext) -> LayoutResult {
let margin = self.get_margin();
let size = if self.position.is_horizontal() {
Size::new(
constraints.max_width,
self.thickness + margin * 2.0,
)
} else {
Size::new(
self.thickness + margin * 2.0,
constraints.max_height,
)
};
self.base.bounds.size = size;
let content_rect = Rect::new(
self.base.bounds.x() + margin + self.padding.left,
self.base.bounds.y() + margin + self.padding.top,
size.width - margin * 2.0 - self.padding.left - self.padding.right,
size.height - margin * 2.0 - self.padding.top - self.padding.bottom,
);
if self.position.is_horizontal() {
self.layout_horizontal(content_rect, ctx);
} else {
self.layout_vertical(content_rect, ctx);
}
LayoutResult::new(size)
}
fn paint(&self, painter: &mut Painter, rect: Rect, ctx: &PaintContext) {
let theme = ctx.style_ctx.theme;
let margin = self.get_margin();
let radius = self.get_border_radius(theme);
let bg_color = self.get_background_color(theme);
let bar_rect = Rect::new(
rect.x() + margin,
rect.y() + margin,
rect.width() - margin * 2.0,
rect.height() - margin * 2.0,
);
if self.variant == BarVariant::Floating {
let shadow_rect = Rect::new(
bar_rect.x() + 2.0,
bar_rect.y() + 4.0,
bar_rect.width(),
bar_rect.height(),
);
painter.fill_rounded_rect(shadow_rect, Color::BLACK.with_alpha(0.15), radius);
}
if bg_color != Color::TRANSPARENT {
painter.fill_rounded_rect(bar_rect, bg_color, radius);
}
if self.variant == BarVariant::Solid {
let border_color = theme.colors.border;
match self.position {
BarPosition::Top => {
painter.fill_rect(
Rect::new(bar_rect.x(), bar_rect.y() + bar_rect.height() - 1.0, bar_rect.width(), 1.0),
border_color,
);
}
BarPosition::Bottom => {
painter.fill_rect(
Rect::new(bar_rect.x(), bar_rect.y(), bar_rect.width(), 1.0),
border_color,
);
}
BarPosition::Left => {
painter.fill_rect(
Rect::new(bar_rect.x() + bar_rect.width() - 1.0, bar_rect.y(), 1.0, bar_rect.height()),
border_color,
);
}
BarPosition::Right => {
painter.fill_rect(
Rect::new(bar_rect.x(), bar_rect.y(), 1.0, bar_rect.height()),
border_color,
);
}
}
}
if self.variant == BarVariant::Floating {
painter.stroke_rect(bar_rect, theme.colors.border.with_alpha(0.3), 1.0);
}
if let Some(start) = &self.start {
start.paint(painter, start.bounds(), ctx);
}
if let Some(center) = &self.center {
center.paint(painter, center.bounds(), ctx);
}
if let Some(end) = &self.end {
end.paint(painter, end.bounds(), ctx);
}
}
fn handle_event(&mut self, event: &Event, ctx: &mut EventContext) -> EventResult {
if let Some(end) = &mut self.end {
if end.handle_event(event, ctx) == EventResult::Handled {
return EventResult::Handled;
}
}
if let Some(center) = &mut self.center {
if center.handle_event(event, ctx) == EventResult::Handled {
return EventResult::Handled;
}
}
if let Some(start) = &mut self.start {
if start.handle_event(event, ctx) == EventResult::Handled {
return EventResult::Handled;
}
}
EventResult::Ignored
}
fn bounds(&self) -> Rect {
self.base.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.base.bounds = bounds;
}
fn children(&self) -> &[Box<dyn Widget>] {
&[]
}
fn children_mut(&mut self) -> &mut [Box<dyn Widget>] {
&mut []
}
}
impl Bar {
fn layout_horizontal(&mut self, content_rect: Rect, ctx: &LayoutContext) {
let available_width = content_rect.width();
let content_height = content_rect.height();
let child_constraints = Constraints {
min_width: 0.0,
min_height: 0.0,
max_width: available_width,
max_height: content_height,
};
let start_width = if let Some(start) = &mut self.start {
let result = start.layout(child_constraints, ctx);
start.set_bounds(Rect::new(
content_rect.x(),
content_rect.y() + (content_height - result.size.height) / 2.0,
result.size.width,
result.size.height,
));
result.size.width
} else {
0.0
};
let end_width = if let Some(end) = &mut self.end {
let result = end.layout(child_constraints, ctx);
end.set_bounds(Rect::new(
content_rect.x() + available_width - result.size.width,
content_rect.y() + (content_height - result.size.height) / 2.0,
result.size.width,
result.size.height,
));
result.size.width
} else {
0.0
};
if let Some(center) = &mut self.center {
let center_constraints = Constraints {
min_width: 0.0,
min_height: 0.0,
max_width: available_width - start_width - end_width - self.gap * 2.0,
max_height: content_height,
};
let result = center.layout(center_constraints, ctx);
let center_x = content_rect.x() + (available_width - result.size.width) / 2.0;
center.set_bounds(Rect::new(
center_x,
content_rect.y() + (content_height - result.size.height) / 2.0,
result.size.width,
result.size.height,
));
}
}
fn layout_vertical(&mut self, content_rect: Rect, ctx: &LayoutContext) {
let content_width = content_rect.width();
let available_height = content_rect.height();
let child_constraints = Constraints {
min_width: 0.0,
min_height: 0.0,
max_width: content_width,
max_height: available_height,
};
let start_height = if let Some(start) = &mut self.start {
let result = start.layout(child_constraints, ctx);
start.set_bounds(Rect::new(
content_rect.x() + (content_width - result.size.width) / 2.0,
content_rect.y(),
result.size.width,
result.size.height,
));
result.size.height
} else {
0.0
};
let end_height = if let Some(end) = &mut self.end {
let result = end.layout(child_constraints, ctx);
end.set_bounds(Rect::new(
content_rect.x() + (content_width - result.size.width) / 2.0,
content_rect.y() + available_height - result.size.height,
result.size.width,
result.size.height,
));
result.size.height
} else {
0.0
};
if let Some(center) = &mut self.center {
let center_constraints = Constraints {
min_width: 0.0,
min_height: 0.0,
max_width: content_width,
max_height: available_height - start_height - end_height - self.gap * 2.0,
};
let result = center.layout(center_constraints, ctx);
let center_y = content_rect.y() + (available_height - result.size.height) / 2.0;
center.set_bounds(Rect::new(
content_rect.x() + (content_width - result.size.width) / 2.0,
center_y,
result.size.width,
result.size.height,
));
}
}
}