use crate::Error;
use crate::prelude::{ Terminal, TerminalConst, Widget};
use crate::terminal::{Rectangle, UpdateInfo, UpdateResult};
use crate::widgets::BoundingBox;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct Stacked<TOP, BOT> {
pub higher_widget: TOP,
pub lower_widget: BOT,
pub leftover_result: Option<UpdateResult>,
}
impl<TOP, BOT> Stacked<TOP, BOT> {
pub const fn new(top: TOP, bottom: BOT) -> Self {
Self { higher_widget: top, lower_widget: bottom, leftover_result: None }
}
pub fn draw_bottom(&self, update_info: UpdateInfo, mut terminal: impl Terminal) -> crate::Result<UpdateResult>
where
TOP: BoundingBox,
BOT: BoundingBox {
let lower_view = self.lower_view_rect(terminal.bounding_box())?;
let lower_view = terminal.view_mut(lower_view).ok_or(Error::OutOfBoundsCoordinate {
x: Some(lower_view.right()),
y: Some(lower_view.bottom())
})?;
self.lower_widget.draw(update_info, lower_view)
}
pub fn draw_top(&self, update_info: UpdateInfo, mut terminal: impl Terminal) -> crate::Result<UpdateResult>
where
TOP: BoundingBox,
BOT: BoundingBox {
let higher_view = self.higher_view_rect(terminal.bounding_box())?;
let higher_view = terminal.view_mut(higher_view).ok_or(Error::OutOfBoundsCoordinate {
x: Some(higher_view.right()),
y: Some(higher_view.bottom())
})?;
self.higher_widget.draw(update_info, higher_view)
}
pub fn draw_both(&self, update_info: UpdateInfo, mut terminal: impl Terminal) -> (crate::Result<UpdateResult>, crate::Result<UpdateResult>)
where
TOP: BoundingBox,
BOT: BoundingBox {
let res_higher = self.draw_top(update_info, &mut terminal);
let res_lower = self.draw_bottom(update_info, &mut terminal);
(res_higher, res_lower)
}
pub fn higher_view_rect(&self, bounds: Rectangle) -> crate::Result<Rectangle>
where
TOP: BoundingBox,
BOT: BoundingBox {
let lower_view = self.lower_widget.bounding_box(bounds)?;
let higher_view = self.higher_widget.bounding_box(bounds)?;
let higher_right = higher_view.right();
let lower_right = lower_view.right();
let right_max = higher_right.max(lower_right);
let higher_view_widened = higher_view.right_to(right_max);
if !bounds.contains_rect(higher_view_widened) {
return Err(Error::RequestRescale {
new_width: higher_view_widened.right(),
new_height: higher_view_widened.bottom(),
})
}
Ok(higher_view_widened)
}
pub fn lower_view_rect(&self, bounds: Rectangle) -> crate::Result<Rectangle>
where
TOP: BoundingBox,
BOT: BoundingBox {
let higher_view = self.higher_widget.bounding_box(bounds)?;
let lower_view_uncorrected = self.lower_widget.bounding_box(bounds)?;
let higher_right = higher_view.right();
let lower_right = lower_view_uncorrected.right();
let right_max = higher_right.max(lower_right);
let lower_view_corrected = lower_view_uncorrected.at(higher_view.left_bottom());
let lower_view_widened = lower_view_corrected.right_to(right_max);
let final_view = lower_view_widened;
if !bounds.contains_rect(final_view) {
return Err(Error::RequestRescale {
new_width: final_view.right(),
new_height: final_view.bottom(),
})
}
Ok(lower_view_corrected)
}
}
impl<TOP: BoundingBox, BOT: BoundingBox> Widget for Stacked<TOP, BOT> {
fn update(&mut self, update_info: UpdateInfo, terminal: impl TerminalConst) -> crate::Result<UpdateResult> {
let higher_view = self.higher_view_rect(terminal.bounding_box())?;
let lower_view = self.lower_view_rect(terminal.bounding_box())?;
let higher_view = terminal.view(higher_view).ok_or(Error::OutOfBoundsCoordinate {
x: Some(higher_view.right()),
y: Some(higher_view.bottom())
})?;
let higher_update = self.higher_widget.update(update_info, higher_view);
let lower_view = terminal.view(lower_view).ok_or(Error::OutOfBoundsCoordinate {
x: Some(lower_view.right()),
y: Some(lower_view.bottom())
})?;
let lower_update = self.lower_widget.update(update_info, lower_view);
let res_higher = higher_update?;
let res_lower = lower_update?;
self.leftover_result = Some(res_lower.min(res_higher));
Ok(res_lower.max(res_higher))
}
fn draw(&self, update_info: UpdateInfo, mut terminal: impl Terminal) -> crate::Result<UpdateResult> {
let res_higher = self.draw_top(update_info, &mut terminal)?;
let res_lower = self.draw_bottom(update_info, &mut terminal)?;
Ok(res_lower.max(res_higher))
}
}
impl<TOP: BoundingBox, BOT: BoundingBox> BoundingBox for Stacked<TOP, BOT> {
fn bounding_box(&self, terminal_rect: Rectangle) -> crate::Result<Rectangle> {
let top_height = self.higher_widget.bounding_box(terminal_rect)?.height();
let bottom_height = self.lower_widget.bounding_box(terminal_rect)?.height();
let top_width = self.higher_widget.bounding_box(terminal_rect)?.width();
let bottom_width = self.lower_widget.bounding_box(terminal_rect)?.width();
let width = top_width.max(bottom_width);
let height = top_height + bottom_height;
let left_top = self.higher_widget.bounding_box(terminal_rect)?.left_top();
let rect = Rectangle::of_size((width+1, height)).at(left_top);
if !terminal_rect.contains_rect(rect) {
return Err(Error::RequestRescale {
new_width: rect.width().max(width),
new_height: rect.height().max(height),
});
}
Ok(rect)
}
fn completely_covers(&self, bounds: Rectangle) -> bool {
let Ok(rect) = self.bounding_box(bounds) else {
return false;
};
let Ok(higher_view) = self.higher_view_rect(bounds) else {
return false;
};
let Ok(lower_view) = self.lower_view_rect(bounds) else {
return false;
};
rect.dimensions() == bounds.dimensions()
&& self.lower_widget.completely_covers(lower_view)
&& self.higher_widget.completely_covers(higher_view)
}
}