use crate::context::Context;
use crate::render::Buffer;
use crate::render::style::{Color, Style};
use crate::util::Rect;
use crate::ui::{
Widget, Container, BaseWidget, WidgetId, WidgetState, UIEvent, UIResult,
Layout, LinearLayout, LayoutConstraints,
next_widget_id
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderStyle {
None,
Single,
Double,
Rounded,
}
pub struct Panel {
base: BaseWidget,
children: Vec<Box<dyn Widget>>,
layout: Box<dyn Layout>,
layout_constraints: Vec<LayoutConstraints>,
border_style: BorderStyle,
title: Option<String>,
canvas: Buffer,
hdividers: Vec<u16>,
vdividers: Vec<(u16, u16, u16)>,
}
impl Default for Panel {
fn default() -> Self {
Self::new()
}
}
impl Panel {
pub fn new() -> Self {
Self {
base: BaseWidget::new(next_widget_id()),
children: Vec::new(),
layout: Box::new(LinearLayout::vertical()),
layout_constraints: Vec::new(),
border_style: BorderStyle::None,
title: None,
canvas: Buffer::empty(Rect::new(0, 0, 0, 0)),
hdividers: Vec::new(),
vdividers: Vec::new(),
}
}
pub fn with_bounds(mut self, bounds: Rect) -> Self {
self.base.bounds = bounds;
self.canvas = Buffer::empty(Rect::new(0, 0, bounds.width, bounds.height));
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.base.style = style;
self
}
pub fn with_layout(mut self, layout: Box<dyn Layout>) -> Self {
self.layout = layout;
self
}
pub fn with_border(mut self, border_style: BorderStyle) -> Self {
self.border_style = border_style;
self
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = Some(title.to_string());
self
}
pub fn set_title(&mut self, title: Option<String>) {
if self.title != title {
self.title = title;
self.mark_dirty();
}
}
pub fn title(&self) -> Option<&str> {
self.title.as_deref()
}
pub fn set_border_style(&mut self, border_style: BorderStyle) {
if self.border_style != border_style {
self.border_style = border_style;
self.mark_dirty();
}
}
pub fn add_child_with_constraints(&mut self, child: Box<dyn Widget>, constraints: LayoutConstraints) {
self.children.push(child);
self.layout_constraints.push(constraints);
self.mark_dirty();
}
pub fn content_area(&self) -> Rect {
let bounds = self.bounds();
let mut content = bounds;
if self.border_style != BorderStyle::None {
content.x += 1;
content.y += 1;
content.width = content.width.saturating_sub(2);
content.height = content.height.saturating_sub(2);
}
if self.title.is_some() {
content.y += 1;
content.height = content.height.saturating_sub(1);
}
content
}
pub fn enable_canvas(&mut self, width: u16, height: u16) {
self.canvas = Buffer::empty(Rect::new(0, 0, width, height));
self.mark_dirty();
}
pub fn set_char(&mut self, x: u16, y: u16, sym: &str, fg: Color, bg: Color) {
let area = self.canvas.area();
if x < area.width && y < area.height {
let style = Style::default().fg(fg).bg(bg);
self.canvas.get_mut(x, y).set_symbol(sym).set_style(style);
self.mark_dirty();
}
}
pub fn set_str(&mut self, x: u16, y: u16, s: &str, fg: Color, bg: Color) {
let style = Style::default().fg(fg).bg(bg);
self.canvas.set_string(x, y, s, style);
self.mark_dirty();
}
pub fn clear_canvas(&mut self) {
self.canvas.reset();
self.mark_dirty();
}
pub fn canvas_mut(&mut self) -> &mut Buffer {
&mut self.canvas
}
pub fn set_color_str(&mut self, x: u16, y: u16, s: &str, fg: Color, bg: Color) {
self.set_str(x, y, s, fg, bg);
}
pub fn set_hidden(&mut self, hidden: bool) {
self.set_visible(!hidden);
}
pub fn is_hidden(&self) -> bool {
!self.state().visible
}
pub fn set_pos(&mut self, x: u16, y: u16) {
let mut bounds = self.bounds();
bounds.x = x;
bounds.y = y;
self.set_bounds(bounds);
}
pub fn with_hdivider(mut self, y: u16) -> Self {
self.hdividers.push(y);
self
}
pub fn with_vdivider(mut self, x: u16, y_start: u16, y_end: u16) -> Self {
self.vdividers.push((x, y_start, y_end));
self
}
pub fn add_hdivider(&mut self, y: u16) {
self.hdividers.push(y);
self.mark_dirty();
}
pub fn add_vdivider(&mut self, x: u16, y_start: u16, y_end: u16) {
self.vdividers.push((x, y_start, y_end));
self.mark_dirty();
}
pub fn clear_dividers(&mut self) {
self.hdividers.clear();
self.vdividers.clear();
self.mark_dirty();
}
}
impl Widget for Panel {
fn id(&self) -> WidgetId { self.base.id }
fn bounds(&self) -> Rect { self.base.bounds }
fn set_bounds(&mut self, bounds: Rect) {
self.base.bounds = bounds;
self.base.state.dirty = true;
}
fn state(&self) -> &WidgetState { &self.base.state }
fn state_mut(&mut self) -> &mut WidgetState { &mut self.base.state }
fn as_any(&self) -> &dyn std::any::Any { self }
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
fn update(&mut self, dt: f32, ctx: &mut Context) -> UIResult<()> {
for child in &mut self.children {
child.update(dt, ctx)?;
}
Ok(())
}
fn layout_children(&mut self) {
Container::layout_recursive(self);
}
fn render(&self, buffer: &mut Buffer, ctx: &Context) -> UIResult<()> {
if !self.state().visible {
return Ok(());
}
let bounds = self.bounds();
if bounds.width == 0 || bounds.height == 0 {
return Ok(());
}
let style = self.base.style;
if self.border_style != BorderStyle::None {
self.render_border(buffer, style)?;
}
if let Some(ref title) = self.title {
self.render_title(buffer, title, style)?;
}
if !self.hdividers.is_empty() || !self.vdividers.is_empty() {
self.render_dividers(buffer, style)?;
}
if self.canvas.area().width > 0 && self.canvas.area().height > 0 {
self.render_canvas(buffer, &self.canvas)?;
}
for child in &self.children {
child.render(buffer, ctx)?;
}
Ok(())
}
fn handle_event(&mut self, event: &UIEvent, ctx: &mut Context) -> UIResult<bool> {
if !self.state().enabled {
return Ok(false);
}
for child in &mut self.children {
if child.handle_event(event, ctx)? {
return Ok(true);
}
}
Ok(false)
}
fn preferred_size(&self, available: Rect) -> Rect {
available
}
}
impl Container for Panel {
fn add_child(&mut self, child: Box<dyn Widget>) {
self.layout_constraints.push(LayoutConstraints::default());
self.children.push(child);
self.mark_dirty();
}
fn remove_child(&mut self, id: WidgetId) -> Option<Box<dyn Widget>> {
if let Some(index) = self.children.iter().position(|child| child.id() == id) {
self.layout_constraints.remove(index);
self.mark_dirty();
Some(self.children.remove(index))
} else {
None
}
}
fn get_child(&self, id: WidgetId) -> Option<&dyn Widget> {
self.children.iter().find(|child| child.id() == id).map(|c| c.as_ref())
}
fn get_child_mut(&mut self, id: WidgetId) -> Option<&mut dyn Widget> {
self.children.iter_mut().find(|child| child.id() == id).map(|c| c.as_mut())
}
fn children(&self) -> &[Box<dyn Widget>] {
&self.children
}
fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
&mut self.children
}
fn layout(&mut self) {
let content_area = self.content_area();
while self.layout_constraints.len() < self.children.len() {
self.layout_constraints.push(LayoutConstraints::default());
}
self.layout.layout(&mut self.children, content_area, &self.layout_constraints);
for child in &mut self.children {
child.mark_dirty();
}
}
}
impl Panel {
#[inline]
fn in_buffer(buf: &Buffer, x: u16, y: u16) -> bool {
let a = buf.area();
x >= a.x && x < a.x + a.width && y >= a.y && y < a.y + a.height
}
fn render_border(&self, buffer: &mut Buffer, style: Style) -> UIResult<()> {
let bounds = self.bounds();
if bounds.width < 2 || bounds.height < 2 {
return Ok(());
}
let (top_left, top_right, bottom_left, bottom_right, horizontal, vertical) = match self.border_style {
BorderStyle::Single => ("┌", "┐", "└", "┘", "─", "│"),
BorderStyle::Double => ("╔", "╗", "╚", "╝", "═", "║"),
BorderStyle::Rounded => ("╭", "╮", "╰", "╯", "─", "│"),
BorderStyle::None => return Ok(()),
};
let border_style = style;
let bottom_y = bounds.y + bounds.height - 1;
let right_x = bounds.x + bounds.width - 1;
for x in (bounds.x + 1)..right_x {
if Self::in_buffer(buffer, x, bounds.y) {
buffer.get_mut(x, bounds.y).set_symbol(horizontal).set_style(border_style);
}
if Self::in_buffer(buffer, x, bottom_y) {
buffer.get_mut(x, bottom_y).set_symbol(horizontal).set_style(border_style);
}
}
for y in (bounds.y + 1)..bottom_y {
if Self::in_buffer(buffer, bounds.x, y) {
buffer.get_mut(bounds.x, y).set_symbol(vertical).set_style(border_style);
}
if Self::in_buffer(buffer, right_x, y) {
buffer.get_mut(right_x, y).set_symbol(vertical).set_style(border_style);
}
}
if Self::in_buffer(buffer, bounds.x, bounds.y) {
buffer.get_mut(bounds.x, bounds.y).set_symbol(top_left).set_style(border_style);
}
if Self::in_buffer(buffer, right_x, bounds.y) {
buffer.get_mut(right_x, bounds.y).set_symbol(top_right).set_style(border_style);
}
if Self::in_buffer(buffer, bounds.x, bottom_y) {
buffer.get_mut(bounds.x, bottom_y).set_symbol(bottom_left).set_style(border_style);
}
if Self::in_buffer(buffer, right_x, bottom_y) {
buffer.get_mut(right_x, bottom_y).set_symbol(bottom_right).set_style(border_style);
}
Ok(())
}
fn render_title(&self, buffer: &mut Buffer, title: &str, style: Style) -> UIResult<()> {
let bounds = self.bounds();
if title.is_empty() || bounds.width < 4 {
return Ok(());
}
let title_y = bounds.y;
if !Self::in_buffer(buffer, bounds.x, title_y) {
return Ok(());
}
let available_width = if self.border_style != BorderStyle::None {
bounds.width.saturating_sub(4)
} else {
bounds.width
};
let title_x = if self.border_style != BorderStyle::None {
bounds.x + 2
} else {
bounds.x
};
let display_title = if title.len() > available_width as usize {
&title[..available_width as usize]
} else {
title
};
buffer.set_string(title_x, title_y, display_title, style);
Ok(())
}
fn divider_chars(&self) -> (&str, &str, &str, &str, &str, &str, &str) {
match self.border_style {
BorderStyle::Double => ("═", "║", "╠", "╣", "╦", "╩", "╬"),
_ => ("─", "│", "├", "┤", "┬", "┴", "┼"),
}
}
fn render_dividers(&self, buffer: &mut Buffer, style: Style) -> UIResult<()> {
let bounds = self.bounds();
let has_border = self.border_style != BorderStyle::None;
let (h_char, v_char, left_tee, right_tee, top_tee, bottom_tee, cross) = self.divider_chars();
let right_x = bounds.x + bounds.width - 1;
let bottom_y = bounds.y + bounds.height - 1;
let hdivider_set: std::collections::HashSet<u16> = self.hdividers.iter().copied().collect();
for &dy in &self.hdividers {
let y = bounds.y + dy;
if y <= bounds.y || y >= bottom_y {
continue; }
for x in (bounds.x + 1)..right_x {
if Self::in_buffer(buffer, x, y) {
buffer.get_mut(x, y).set_symbol(h_char).set_style(style);
}
}
if has_border && Self::in_buffer(buffer, bounds.x, y) {
buffer.get_mut(bounds.x, y).set_symbol(left_tee).set_style(style);
}
if has_border && Self::in_buffer(buffer, right_x, y) {
buffer.get_mut(right_x, y).set_symbol(right_tee).set_style(style);
}
}
for &(dx, dy_start, dy_end) in &self.vdividers {
let x = bounds.x + dx;
let y_start = bounds.y + dy_start;
let y_end = bounds.y + dy_end;
if x <= bounds.x || x >= right_x {
continue; }
for y in y_start..=y_end {
if !Self::in_buffer(buffer, x, y) {
continue;
}
let on_top_border = y == bounds.y;
let on_bottom_border = y == bottom_y;
let on_hdivider = hdivider_set.contains(&(y - bounds.y));
let is_start = y == y_start;
let _is_end = y == y_end;
let sym = if on_top_border && has_border {
top_tee
} else if on_bottom_border && has_border {
bottom_tee
} else if on_hdivider {
let extends_up = y > y_start;
let extends_down = y < y_end;
if extends_up && extends_down {
cross
} else if extends_down || is_start {
top_tee
} else {
bottom_tee
}
} else {
v_char
};
buffer.get_mut(x, y).set_symbol(sym).set_style(style);
}
}
Ok(())
}
fn render_canvas(&self, buffer: &mut Buffer, canvas: &Buffer) -> UIResult<()> {
let content = self.content_area();
let canvas_area = canvas.area();
for y in 0..canvas_area.height.min(content.height) {
for x in 0..canvas_area.width.min(content.width) {
let dst_x = content.x + x;
let dst_y = content.y + y;
if !Self::in_buffer(buffer, dst_x, dst_y) {
continue;
}
let src_cell = canvas.get(x, y);
let has_content = !src_cell.symbol.is_empty() && src_cell.symbol != " ";
let has_styled_bg = src_cell.bg != Color::Reset;
if has_content || has_styled_bg {
let dst_cell = buffer.get_mut(dst_x, dst_y);
dst_cell.set_symbol(&src_cell.symbol).set_style(src_cell.style());
}
}
}
Ok(())
}
}