use crate::graphics::{Color, FontDesc, GraphicsContext, Rect, Rectangle};
use crate::ui::{Component, TextComponent};
use anyhow::Result;
fn rect_to_rect(rect: Rectangle) -> Rect {
Rect::new(
rect.x as i32,
rect.y as i32,
rect.width as u32,
rect.height as u32,
)
}
pub struct StatusPanel {
title: TextComponent,
lines: Vec<TextComponent>,
background: Rectangle,
background_color: Color,
bounds: Rectangle,
}
impl StatusPanel {
#[allow(dead_code)]
pub fn calculate_bounds(title: &str, lines: &[&str]) -> Rectangle {
let title_component =
TextComponent::auto_sized(title, 0, 0).with_font(FontDesc::new("DejaVu Sans", 40.0));
let title_bounds = title_component.bounds();
let mut max_width = title_bounds.width + 20; let mut total_height = title_bounds.height + 20;
for line_text in lines {
let line = TextComponent::auto_sized(line_text, 0, 0)
.with_font(FontDesc::new("DejaVu Sans", 32.0));
let line_bounds = line.bounds();
max_width = max_width.max(line_bounds.width + 20);
total_height += line_bounds.height + 5; }
total_height += 15;
Rectangle {
x: 0,
y: 0,
width: max_width,
height: total_height,
}
}
pub fn new(title: &str, x: i16, y: i16, _width: u16, _height: u16) -> Self {
let title_component = TextComponent::auto_sized(title, x + 10, y + 10)
.with_font(FontDesc::new("DejaVu Sans", 40.0))
.with_color(Color::WHITE);
let background = Rectangle {
x,
y,
width: 100,
height: 60,
};
let mut panel = Self {
title: title_component,
lines: Vec::new(),
background,
background_color: Color::rgba(0.0, 0.0, 0.0, 0.8),
bounds: background,
};
panel.recalculate_size();
panel
}
fn recalculate_size(&mut self) {
let title_bounds = self.title.bounds();
let mut max_width = title_bounds.width + 20; let mut total_height = title_bounds.height + 20;
for line in &self.lines {
let line_bounds = line.bounds();
max_width = max_width.max(line_bounds.width + 20);
total_height += line_bounds.height + 5; }
total_height += 15;
self.background.width = max_width;
self.background.height = total_height;
self.bounds = self.background;
}
pub fn add_line(&mut self, text: &str) {
let mut current_y = self.bounds.y + self.title.bounds().height as i16 + 15;
for line in &self.lines {
current_y += line.bounds().height as i16 + 5;
}
let line = TextComponent::auto_sized(text, self.bounds.x + 10, current_y)
.with_font(FontDesc::new("DejaVu Sans", 32.0))
.with_color(Color::rgba(0.9, 0.9, 0.9, 1.0));
self.lines.push(line);
self.recalculate_size();
}
pub fn with_background_color(mut self, color: Color) -> Self {
self.background_color = color;
self
}
#[allow(dead_code)]
pub fn clear_lines(&mut self) {
self.lines.clear();
}
#[allow(dead_code)]
pub fn update_line(&mut self, index: usize, text: &str) {
if let Some(line) = self.lines.get_mut(index) {
line.set_text(text);
}
}
}
impl Component for StatusPanel {
fn render(&self, graphics: &mut GraphicsContext) -> Result<()> {
graphics.fill_rectangle(rect_to_rect(self.background), self.background_color)?;
std::iter::once(&self.title)
.chain(self.lines.iter())
.try_for_each(|component| component.render(graphics))
}
fn bounds(&self) -> Rectangle {
self.bounds
}
fn update(&mut self, _delta_time: f64) -> bool {
false
}
}
pub struct NotificationComponent {
text: TextComponent,
icon_area: Rectangle,
background: Rectangle,
background_color: Color,
fade_timer: f64,
lifetime: f64,
bounds: Rectangle,
}
impl NotificationComponent {
#[allow(dead_code)]
pub fn calculate_bounds(message: &str, min_width: u16) -> Rectangle {
let text =
TextComponent::auto_sized(message, 0, 0).with_font(FontDesc::new("DejaVu Sans", 36.0));
let text_bounds = text.bounds();
let required_width = (text_bounds.width + 80).max(min_width);
let height = (text_bounds.height + 30).max(60);
Rectangle {
x: 0,
y: 0,
width: required_width,
height,
}
}
pub fn new(message: &str, x: i16, y: i16, min_width: u16, lifetime_seconds: f64) -> Self {
let text = TextComponent::auto_sized(message, x + 60, y + 15)
.with_font(FontDesc::new("DejaVu Sans", 36.0))
.with_color(Color::WHITE)
.with_alignment(crate::graphics::Alignment::Left);
let text_bounds = text.bounds();
let required_width = (text_bounds.width + 80).max(min_width); let height = (text_bounds.height + 30).max(60);
let background = Rectangle {
x,
y,
width: required_width,
height,
};
let icon_area = Rectangle {
x: x + 10,
y: y + 10,
width: 40,
height: 40,
};
Self {
text,
icon_area,
background,
background_color: Color::rgba(0.2, 0.2, 0.2, 0.9),
fade_timer: 0.0,
lifetime: lifetime_seconds,
bounds: background,
}
}
pub fn with_background_color(mut self, color: Color) -> Self {
self.background_color = color;
self
}
pub fn is_expired(&self) -> bool {
self.fade_timer >= self.lifetime
}
pub fn get_alpha(&self) -> f64 {
let fade_start = self.lifetime - 1.0;
if self.fade_timer >= fade_start {
(self.lifetime - self.fade_timer).max(0.0)
} else {
1.0
}
}
fn apply_alpha_to_color(&self, color: Color) -> Color {
let alpha = self.get_alpha();
Color {
r: color.r,
g: color.g,
b: color.b,
a: color.a * alpha,
}
}
}
impl Component for NotificationComponent {
fn render(&self, graphics: &mut GraphicsContext) -> Result<()> {
if self.get_alpha() <= 0.0 {
return Ok(());
}
let bg_color = self.apply_alpha_to_color(self.background_color);
let icon_color = self.apply_alpha_to_color(Color::rgba(0.5, 0.5, 0.5, 1.0));
[(self.background, bg_color), (self.icon_area, icon_color)]
.iter()
.try_for_each(|(rect, color)| graphics.fill_rectangle(rect_to_rect(*rect), *color))?;
self.text.render(graphics)
}
fn bounds(&self) -> Rectangle {
self.bounds
}
fn update(&mut self, delta_time: f64) -> bool {
self.fade_timer += delta_time;
true }
fn should_remove(&self) -> bool {
self.is_expired()
}
fn render_priority(&self) -> i32 {
100 }
}
pub struct ProgressBarComponent {
label: TextComponent,
bar_background: Rectangle,
bar_foreground: Rectangle,
progress: f64, background_color: Color,
foreground_color: Color,
bounds: Rectangle,
}
impl ProgressBarComponent {
pub fn new(label: &str, x: i16, y: i16, min_width: u16) -> Self {
let label_component = TextComponent::auto_sized(label, x + 5, y + 5)
.with_font(FontDesc::new("DejaVu Sans", 32.0))
.with_color(Color::WHITE);
let label_bounds = label_component.bounds();
let required_width = (label_bounds.width + 10).max(min_width);
let height = label_bounds.height + 30;
let bounds = Rectangle {
x,
y,
width: required_width,
height,
};
let bar_y = y + label_bounds.height as i16 + 10;
let bar_background = Rectangle {
x: x + 5,
y: bar_y,
width: required_width - 10,
height: 20, };
let bar_foreground = Rectangle {
x: x + 5,
y: bar_y,
width: 0, height: 20,
};
Self {
label: label_component,
bar_background,
bar_foreground,
progress: 0.0,
background_color: Color::rgba(0.3, 0.3, 0.3, 1.0),
foreground_color: Color::rgba(0.2, 0.8, 0.2, 1.0),
bounds,
}
}
pub fn set_progress(&mut self, progress: f64) {
self.progress = progress.clamp(0.0, 1.0);
self.bar_foreground.width = (self.bar_background.width as f64 * self.progress) as u16;
}
#[allow(dead_code)]
pub fn set_label(&mut self, label: &str) {
self.label.set_text(label);
}
pub fn with_colors(mut self, background: Color, foreground: Color) -> Self {
self.background_color = background;
self.foreground_color = foreground;
self
}
}
impl Component for ProgressBarComponent {
fn render(&self, graphics: &mut GraphicsContext) -> Result<()> {
self.label.render(graphics)?;
graphics.fill_rectangle(rect_to_rect(self.bar_background), self.background_color)?;
if self.bar_foreground.width > 0 {
graphics.fill_rectangle(rect_to_rect(self.bar_foreground), self.foreground_color)?;
}
Ok(())
}
fn bounds(&self) -> Rectangle {
self.bounds
}
fn update(&mut self, _delta_time: f64) -> bool {
false }
}