use super::{BorderChars, Widget};
use crate::widgets::common::WindowView;
use crate::{Color, ColorPair, Result, Window};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BorderSide {
Top,
Right,
Bottom,
Left,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TitleAlignment {
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Gap {
Pixels(u16),
Percent(u8),
}
#[derive(Debug, Clone)]
pub struct BorderConfig {
pub sides: HashSet<BorderSide>,
pub chars: BorderChars,
pub color: ColorPair,
pub focused_color: Option<ColorPair>,
}
impl BorderConfig {
#[allow(dead_code)]
pub fn all_sides(chars: BorderChars, color: ColorPair) -> Self {
let mut sides = HashSet::new();
sides.insert(BorderSide::Top);
sides.insert(BorderSide::Right);
sides.insert(BorderSide::Bottom);
sides.insert(BorderSide::Left);
Self {
sides,
chars,
color,
focused_color: None,
}
}
pub fn no_sides(chars: BorderChars, color: ColorPair) -> Self {
Self {
sides: HashSet::new(),
chars,
color,
focused_color: None,
}
}
pub fn has_border(&self) -> bool {
!self.sides.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayoutDirection {
Vertical,
Horizontal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContentAlignment {
Normal,
AutoCenter,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Padding {
pub top: u16,
pub right: u16,
pub bottom: u16,
pub left: u16,
}
impl Padding {
pub fn uniform(amount: u16) -> Self {
Self {
top: amount,
right: amount,
bottom: amount,
left: amount,
}
}
pub fn symmetric(vertical: u16, horizontal: u16) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
pub fn custom(top: u16, right: u16, bottom: u16, left: u16) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn horizontal_total(&self) -> u16 {
self.left + self.right
}
pub fn vertical_total(&self) -> u16 {
self.top + self.bottom
}
}
impl Default for Padding {
fn default() -> Self {
Self::uniform(0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SizeSpec {
Auto,
Fixed(u16),
Fill { weight: u16 },
}
impl SizeSpec {
pub const fn fill() -> Self {
SizeSpec::Fill { weight: 1 }
}
}
impl Default for SizeSpec {
fn default() -> Self {
SizeSpec::Auto
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AxisConstraints {
pub min: u16,
pub max: Option<u16>,
}
impl Default for AxisConstraints {
fn default() -> Self {
Self { min: 0, max: None }
}
}
impl AxisConstraints {
pub const fn new(min: u16, max: Option<u16>) -> Self {
Self { min, max }
}
pub fn clamp(&self, v: u16) -> u16 {
let v = v.max(self.min);
match self.max {
Some(max) => v.min(max),
None => v,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ChildConstraints {
pub width: AxisConstraints,
pub height: AxisConstraints,
}
struct ContainerChild {
widget: Box<dyn Widget>,
width: SizeSpec,
height: SizeSpec,
constraints: ChildConstraints,
intrinsic_width: u16,
intrinsic_height: u16,
}
pub struct Container {
x: u16,
y: u16,
width: u16,
height: u16,
layout_direction: LayoutDirection,
padding: Padding,
gap: Option<Gap>,
row_gap: Option<Gap>,
column_gap: Option<Gap>,
content_alignment: ContentAlignment,
auto_size: bool,
fullscreen: bool,
border: BorderConfig,
background_color: Option<ColorPair>,
should_fill: bool,
title: Option<String>,
title_alignment: TitleAlignment,
focused: bool,
children: Vec<ContainerChild>,
}
impl Container {
pub fn new() -> Self {
let mut this = Self {
x: 0,
y: 0,
width: 0,
height: 0,
layout_direction: LayoutDirection::Vertical,
padding: Padding::uniform(0),
gap: None,
row_gap: None,
column_gap: None,
content_alignment: ContentAlignment::Normal,
auto_size: true,
fullscreen: false,
border: BorderConfig::no_sides(
BorderChars::single_line(),
ColorPair::new(Color::White, Color::Black),
),
background_color: None,
should_fill: false,
title: None,
title_alignment: TitleAlignment::Left,
focused: false,
children: Vec::new(),
};
this.recalculate_size();
this
}
pub fn vertical() -> Self {
Self::new().with_layout_direction(LayoutDirection::Vertical)
}
pub fn horizontal() -> Self {
Self::new().with_layout_direction(LayoutDirection::Horizontal)
}
pub fn fullscreen() -> Self {
Self {
fullscreen: true,
auto_size: false,
padding: Padding::uniform(0),
..Self::new()
}
}
pub fn with_position_and_size(mut self, x: u16, y: u16, width: u16, height: u16) -> Self {
self.x = x;
self.y = y;
self.width = width;
self.height = height;
self.auto_size = false;
self
}
pub fn with_layout_direction(mut self, direction: LayoutDirection) -> Self {
self.layout_direction = direction;
self.recalculate_size();
self
}
pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self.recalculate_size();
self
}
pub fn with_gap(mut self, gap: Gap) -> Self {
self.gap = Some(gap);
self.recalculate_size();
self
}
pub fn with_row_gap(mut self, gap: Gap) -> Self {
self.row_gap = Some(gap);
self.recalculate_size();
self
}
pub fn with_column_gap(mut self, gap: Gap) -> Self {
self.column_gap = Some(gap);
self.recalculate_size();
self
}
pub fn with_content_alignment(mut self, alignment: ContentAlignment) -> Self {
self.content_alignment = alignment;
self
}
pub fn with_border_sides(mut self, sides: Vec<BorderSide>) -> Self {
self.border.sides = sides.into_iter().collect();
self.recalculate_size();
self
}
pub fn with_border(mut self) -> Self {
let mut sides = HashSet::new();
sides.insert(BorderSide::Top);
sides.insert(BorderSide::Right);
sides.insert(BorderSide::Bottom);
sides.insert(BorderSide::Left);
self.border.sides = sides;
self.recalculate_size();
self
}
pub fn without_border(mut self) -> Self {
self.border.sides.clear();
self.recalculate_size();
self
}
pub fn with_border_chars(mut self, chars: BorderChars) -> Self {
self.border.chars = chars;
if self.border.sides.is_empty() {
self = self.with_border();
}
self.recalculate_size();
self
}
pub fn with_border_color(mut self, color: ColorPair) -> Self {
self.border.color = color;
if self.border.sides.is_empty() {
self = self.with_border();
}
self.recalculate_size();
self
}
pub fn with_focused_border_color(mut self, color: ColorPair) -> Self {
self.border.focused_color = Some(color);
if self.border.sides.is_empty() {
self = self.with_border();
}
self.recalculate_size();
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self.border.sides.insert(BorderSide::Top);
self.recalculate_size();
self
}
pub fn with_title_alignment(mut self, alignment: TitleAlignment) -> Self {
self.title_alignment = alignment;
self
}
pub fn with_background_color(mut self, color: ColorPair) -> Self {
self.background_color = Some(color);
self.should_fill = true;
self
}
pub fn with_fill(mut self, fill: bool) -> Self {
self.should_fill = fill;
self
}
pub fn add_child(mut self, child: impl Widget + 'static) -> Self {
let (w, h) = child.get_size();
let (width, height) = match self.layout_direction {
LayoutDirection::Vertical => (SizeSpec::fill(), SizeSpec::Auto),
LayoutDirection::Horizontal => (SizeSpec::Auto, SizeSpec::fill()),
};
self.children.push(ContainerChild {
widget: Box::new(child),
width,
height,
constraints: ChildConstraints::default(),
intrinsic_width: w,
intrinsic_height: h,
});
self.recalculate_size();
self
}
pub fn add_child_fill(self, child: impl Widget + 'static) -> Self {
self.add_child_fill_weight(child, 1)
}
pub fn add_child_fill_weight(mut self, child: impl Widget + 'static, weight: u16) -> Self {
let (w, h) = child.get_size();
let weight = weight.max(1);
let (width, height) = match self.layout_direction {
LayoutDirection::Vertical => (SizeSpec::fill(), SizeSpec::Fill { weight }),
LayoutDirection::Horizontal => (SizeSpec::Fill { weight }, SizeSpec::fill()),
};
self.children.push(ContainerChild {
widget: Box::new(child),
width,
height,
constraints: ChildConstraints::default(),
intrinsic_width: w,
intrinsic_height: h,
});
self.recalculate_size();
self
}
pub fn last_child_width_constraints(mut self, min: u16, max: Option<u16>) -> Self {
if let Some(last) = self.children.last_mut() {
last.constraints.width = AxisConstraints::new(min, max);
}
self
}
pub fn last_child_height_constraints(mut self, min: u16, max: Option<u16>) -> Self {
if let Some(last) = self.children.last_mut() {
last.constraints.height = AxisConstraints::new(min, max);
}
self
}
pub fn add_child_fixed_main(mut self, child: impl Widget + 'static, main: u16) -> Self {
let (w, h) = child.get_size();
let (width, height) = match self.layout_direction {
LayoutDirection::Vertical => (SizeSpec::fill(), SizeSpec::Fixed(main)),
LayoutDirection::Horizontal => (SizeSpec::Fixed(main), SizeSpec::fill()),
};
self.children.push(ContainerChild {
widget: Box::new(child),
width,
height,
constraints: ChildConstraints::default(),
intrinsic_width: w,
intrinsic_height: h,
});
self.recalculate_size();
self
}
pub fn children(&self) -> Vec<&dyn Widget> {
self.children.iter().map(|c| c.widget.as_ref()).collect()
}
pub fn layout_direction(&self) -> LayoutDirection {
self.layout_direction
}
pub fn is_focused(&self) -> bool {
self.focused
}
pub fn set_focused(mut self, focused: bool) -> Self {
self.focused = focused;
self
}
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn draw_frame(&self, window: &mut dyn Window) -> Result<()> {
self.draw_background(window)?;
self.draw_borders(window)?;
Ok(())
}
pub fn draw_contents(&self, window: &mut dyn Window) -> Result<()> {
self.draw_children(window)?;
Ok(())
}
fn border_left_width(&self) -> u16 {
if self.border.sides.contains(&BorderSide::Left) {
1
} else {
0
}
}
fn border_right_width(&self) -> u16 {
if self.border.sides.contains(&BorderSide::Right) {
1
} else {
0
}
}
fn border_top_height(&self) -> u16 {
if self.border.sides.contains(&BorderSide::Top) {
1
} else {
0
}
}
fn border_bottom_height(&self) -> u16 {
if self.border.sides.contains(&BorderSide::Bottom) {
1
} else {
0
}
}
pub fn content_area(&self) -> (u16, u16, u16, u16) {
let x = self.x + self.border_left_width() + self.padding.left;
let y = self.y + self.border_top_height() + self.padding.top;
let width = self.width.saturating_sub(
self.border_left_width()
+ self.padding.left
+ self.padding.right
+ self.border_right_width(),
);
let height = self.height.saturating_sub(
self.border_top_height()
+ self.padding.top
+ self.padding.bottom
+ self.border_bottom_height(),
);
(x, y, width, height)
}
pub fn padding(&self) -> Padding {
self.padding
}
pub fn border_config(&self) -> &BorderConfig {
&self.border
}
fn get_content_area(&self) -> (u16, u16, u16, u16) {
self.content_area()
}
fn recalculate_size(&mut self) {
if !self.auto_size || self.fullscreen {
return;
}
if self.children.is_empty() {
let outer_w = self
.border_left_width()
.saturating_add(self.padding.left)
.saturating_add(self.padding.right)
.saturating_add(self.border_right_width());
let outer_h = self
.border_top_height()
.saturating_add(self.padding.top)
.saturating_add(self.padding.bottom)
.saturating_add(self.border_bottom_height());
self.width = outer_w;
self.height = outer_h;
return;
}
let mut content_required_w: u16 = 0;
let mut content_required_h: u16 = 0;
let gap_pixels: u16 = match self.layout_direction {
LayoutDirection::Vertical => self.row_gap.or(self.gap),
LayoutDirection::Horizontal => self.column_gap.or(self.gap),
}
.map(|g| match g {
Gap::Pixels(n) => n,
Gap::Percent(_) => 1,
})
.unwrap_or(0);
match self.layout_direction {
LayoutDirection::Vertical => {
for (idx, child) in self.children.iter().enumerate() {
let (cw, ch) = (child.intrinsic_width, child.intrinsic_height);
content_required_w = content_required_w.max(cw);
content_required_h = content_required_h.saturating_add(ch);
if idx < self.children.len() - 1 {
content_required_h = content_required_h.saturating_add(gap_pixels);
}
}
}
LayoutDirection::Horizontal => {
for (idx, child) in self.children.iter().enumerate() {
let (cw, ch) = (child.intrinsic_width, child.intrinsic_height);
content_required_w = content_required_w.saturating_add(cw);
content_required_h = content_required_h.max(ch);
if idx < self.children.len() - 1 {
content_required_w = content_required_w.saturating_add(gap_pixels);
}
}
}
}
let outer_w = content_required_w
.saturating_add(self.border_left_width())
.saturating_add(self.padding.left)
.saturating_add(self.padding.right)
.saturating_add(self.border_right_width());
let outer_h = content_required_h
.saturating_add(self.border_top_height())
.saturating_add(self.padding.top)
.saturating_add(self.padding.bottom)
.saturating_add(self.border_bottom_height());
self.width = outer_w;
self.height = outer_h;
}
pub fn autosize_gap_pixels(&self) -> u16 {
let gap = match self.layout_direction {
LayoutDirection::Vertical => self.row_gap.or(self.gap),
LayoutDirection::Horizontal => self.column_gap.or(self.gap),
};
match gap {
Some(Gap::Pixels(n)) => n,
Some(Gap::Percent(_)) => 1,
None => 0,
}
}
fn resolve_gap(&self, available_space: u16) -> u16 {
let gap = match self.layout_direction {
LayoutDirection::Vertical => self.row_gap.or(self.gap),
LayoutDirection::Horizontal => self.column_gap.or(self.gap),
};
match gap {
Some(Gap::Pixels(n)) => n,
Some(Gap::Percent(pct)) => {
let percent = (pct as u16).min(100);
(available_space * percent / 100).max(1)
}
None => 0,
}
}
fn draw_borders(&self, window: &mut dyn Window) -> Result<()> {
if self.width == 0 || self.height == 0 {
return Ok(());
}
if !self.border.has_border() {
return Ok(());
}
let color = if self.focused {
self.border.focused_color.unwrap_or(self.border.color)
} else {
self.border.color
};
let chars = self.border.chars;
if self.border.sides.contains(&BorderSide::Top) {
self.draw_top_border(window, chars, color)?;
}
if self.border.sides.contains(&BorderSide::Bottom) {
self.draw_bottom_border(window, chars, color)?;
}
if self.border.sides.contains(&BorderSide::Left) {
self.draw_left_border(window, chars, color)?;
}
if self.border.sides.contains(&BorderSide::Right) {
self.draw_right_border(window, chars, color)?;
}
Ok(())
}
fn draw_top_border(
&self,
window: &mut dyn Window,
chars: BorderChars,
color: ColorPair,
) -> Result<()> {
let has_left = self.border.sides.contains(&BorderSide::Left);
let has_right = self.border.sides.contains(&BorderSide::Right);
let mut x = self.x;
if has_left {
window.write_str_colored(self.y, x, &chars.top_left.to_string(), color)?;
x += 1;
}
let available_width = self
.width
.saturating_sub(if has_left { 1 } else { 0 })
.saturating_sub(if has_right { 1 } else { 0 });
if let Some(title) = &self.title {
self.draw_title_in_border(window, title, x, available_width, chars, color)?;
} else {
for _ in 0..available_width {
window.write_str_colored(self.y, x, &chars.horizontal.to_string(), color)?;
x += 1;
}
}
if has_right {
window.write_str_colored(
self.y,
self.x + self.width - 1,
&chars.top_right.to_string(),
color,
)?;
}
Ok(())
}
fn draw_title_in_border(
&self,
window: &mut dyn Window,
title: &str,
start_x: u16,
available_width: u16,
chars: BorderChars,
color: ColorPair,
) -> Result<()> {
let title_max_width = available_width.saturating_sub(2); let display_title_owned =
crate::text::clip_to_cells(title, title_max_width, crate::text::TabPolicy::SingleCell);
let display_title = display_title_owned.as_str();
let title_width =
crate::text::cell_width(display_title, crate::text::TabPolicy::SingleCell);
let left_padding = match self.title_alignment {
TitleAlignment::Left => 1,
TitleAlignment::Center => {
if title_width < available_width {
(available_width - title_width) / 2
} else {
1
}
}
TitleAlignment::Right => available_width.saturating_sub(title_width + 1),
};
let mut x = start_x;
for _ in 0..left_padding {
window.write_str_colored(self.y, x, &chars.horizontal.to_string(), color)?;
x += 1;
}
if left_padding > 0 {
x -= 1;
window.write_str_colored(self.y, x, " ", color)?;
x += 1;
}
window.write_str_colored(self.y, x, display_title, color)?;
x += title_width;
if x < start_x + available_width {
window.write_str_colored(self.y, x, " ", color)?;
x += 1;
}
for _ in x..(start_x + available_width) {
window.write_str_colored(self.y, x, &chars.horizontal.to_string(), color)?;
x += 1;
}
Ok(())
}
fn draw_bottom_border(
&self,
window: &mut dyn Window,
chars: BorderChars,
color: ColorPair,
) -> Result<()> {
if self.height == 0 {
return Ok(());
}
let y = self.y + self.height - 1;
let has_left = self.border.sides.contains(&BorderSide::Left);
let has_right = self.border.sides.contains(&BorderSide::Right);
let mut x = self.x;
if has_left {
window.write_str_colored(y, x, &chars.bottom_left.to_string(), color)?;
x += 1;
}
let end_x = self.x + self.width - if has_right { 1 } else { 0 };
while x < end_x {
window.write_str_colored(y, x, &chars.horizontal.to_string(), color)?;
x += 1;
}
if has_right {
window.write_str_colored(
y,
self.x + self.width - 1,
&chars.bottom_right.to_string(),
color,
)?;
}
Ok(())
}
fn draw_left_border(
&self,
window: &mut dyn Window,
chars: BorderChars,
color: ColorPair,
) -> Result<()> {
let start_y = self.y
+ if self.border.sides.contains(&BorderSide::Top) {
1
} else {
0
};
let end_y = self.y + self.height
- if self.border.sides.contains(&BorderSide::Bottom) {
1
} else {
0
};
for y in start_y..end_y {
window.write_str_colored(y, self.x, &chars.vertical.to_string(), color)?;
}
Ok(())
}
fn draw_right_border(
&self,
window: &mut dyn Window,
chars: BorderChars,
color: ColorPair,
) -> Result<()> {
if self.width == 0 {
return Ok(());
}
let x = self.x + self.width - 1;
let start_y = self.y
+ if self.border.sides.contains(&BorderSide::Top) {
1
} else {
0
};
let end_y = self.y + self.height
- if self.border.sides.contains(&BorderSide::Bottom) {
1
} else {
0
};
for y in start_y..end_y {
window.write_str_colored(y, x, &chars.vertical.to_string(), color)?;
}
Ok(())
}
fn draw_background(&self, window: &mut dyn Window) -> Result<()> {
if !self.should_fill {
return Ok(());
}
if let Some(color) = self.background_color {
let (content_x, content_y, content_width, content_height) = self.get_content_area();
for y in content_y..(content_y + content_height) {
for x in content_x..(content_x + content_width) {
window.write_str_colored(y, x, " ", color)?;
}
}
}
Ok(())
}
fn draw_children(&self, window: &mut dyn Window) -> Result<()> {
let (content_x, content_y, content_width, content_height) = self.get_content_area();
let gap = self.resolve_gap(match self.layout_direction {
LayoutDirection::Vertical => content_height,
LayoutDirection::Horizontal => content_width,
});
let child_count = self.children.len();
let gap_total = if child_count > 1 {
gap.saturating_mul((child_count - 1) as u16)
} else {
0
};
let (available_main, fixed_main_total, total_weight_u32) = match self.layout_direction {
LayoutDirection::Vertical => {
let available = content_height.saturating_sub(gap_total);
let mut fixed_total: u16 = 0;
let mut total_weight: u32 = 0;
for c in self.children.iter() {
match c.height {
SizeSpec::Fill { weight } => {
total_weight = total_weight.saturating_add(weight.max(1) as u32)
}
SizeSpec::Fixed(h) => fixed_total = fixed_total.saturating_add(h),
SizeSpec::Auto => {
fixed_total = fixed_total.saturating_add(c.intrinsic_height)
}
}
}
(available, fixed_total, total_weight)
}
LayoutDirection::Horizontal => {
let available = content_width.saturating_sub(gap_total);
let mut fixed_total: u16 = 0;
let mut total_weight: u32 = 0;
for c in self.children.iter() {
match c.width {
SizeSpec::Fill { weight } => {
total_weight = total_weight.saturating_add(weight.max(1) as u32)
}
SizeSpec::Fixed(w) => fixed_total = fixed_total.saturating_add(w),
SizeSpec::Auto => {
fixed_total = fixed_total.saturating_add(c.intrinsic_width)
}
}
}
(available, fixed_total, total_weight)
}
};
let remaining_main = available_main.saturating_sub(fixed_main_total);
let mut weighted_remainder: u16 = 0;
if total_weight_u32 > 0 {
let mut base_sum: u16 = 0;
match self.layout_direction {
LayoutDirection::Vertical => {
for c in self.children.iter() {
if let SizeSpec::Fill { weight } = c.height {
let w = weight.max(1) as u32;
let base = ((remaining_main as u32).saturating_mul(w)
/ total_weight_u32) as u16;
base_sum = base_sum.saturating_add(base);
}
}
}
LayoutDirection::Horizontal => {
for c in self.children.iter() {
if let SizeSpec::Fill { weight } = c.width {
let w = weight.max(1) as u32;
let base = ((remaining_main as u32).saturating_mul(w)
/ total_weight_u32) as u16;
base_sum = base_sum.saturating_add(base);
}
}
}
}
weighted_remainder = remaining_main.saturating_sub(base_sum);
}
let mut current_x = content_x;
let mut current_y = content_y;
let mut fill_assigned: u16 = 0;
let mut weighted_remainder_assigned: u16 = 0;
for (idx, child) in self.children.iter().enumerate() {
let (intrinsic_w, intrinsic_h) = (child.intrinsic_width, child.intrinsic_height);
let remaining_width = content_width.saturating_sub(current_x - content_x);
let remaining_height = content_height.saturating_sub(current_y - content_y);
let (mut resolved_w, mut resolved_h) = match self.layout_direction {
LayoutDirection::Vertical => {
let w = match child.width {
SizeSpec::Fill { .. } => remaining_width,
SizeSpec::Fixed(w) => w,
SizeSpec::Auto => intrinsic_w,
};
let h = match child.height {
SizeSpec::Fill { weight } => {
let weight = weight.max(1) as u32;
let base = if total_weight_u32 > 0 {
((remaining_main as u32).saturating_mul(weight) / total_weight_u32)
as u16
} else {
0
};
let extra = if weighted_remainder_assigned < weighted_remainder {
1
} else {
0
};
fill_assigned = fill_assigned.saturating_add(1);
weighted_remainder_assigned =
weighted_remainder_assigned.saturating_add(extra);
base.saturating_add(extra)
}
SizeSpec::Fixed(h) => h,
SizeSpec::Auto => intrinsic_h,
};
(w, h)
}
LayoutDirection::Horizontal => {
let h = match child.height {
SizeSpec::Fill { .. } => remaining_height,
SizeSpec::Fixed(h) => h,
SizeSpec::Auto => intrinsic_h,
};
let w = match child.width {
SizeSpec::Fill { weight } => {
let weight = weight.max(1) as u32;
let base = if total_weight_u32 > 0 {
((remaining_main as u32).saturating_mul(weight) / total_weight_u32)
as u16
} else {
0
};
let extra = if weighted_remainder_assigned < weighted_remainder {
1
} else {
0
};
fill_assigned = fill_assigned.saturating_add(1);
weighted_remainder_assigned =
weighted_remainder_assigned.saturating_add(extra);
base.saturating_add(extra)
}
SizeSpec::Fixed(w) => w,
SizeSpec::Auto => intrinsic_w,
};
(w, h)
}
};
resolved_w = child
.constraints
.width
.clamp(resolved_w)
.min(remaining_width);
resolved_h = child
.constraints
.height
.clamp(resolved_h)
.min(remaining_height);
let mut child_view = WindowView {
window,
x_offset: current_x,
y_offset: current_y,
scroll_x: 0,
scroll_y: 0,
width: resolved_w,
height: resolved_h,
};
child.widget.draw(&mut child_view)?;
match self.layout_direction {
LayoutDirection::Vertical => {
current_y += resolved_h;
if idx < self.children.len() - 1 {
current_y += gap;
}
}
LayoutDirection::Horizontal => {
current_x += resolved_w;
if idx < self.children.len() - 1 {
current_x += gap;
}
}
}
}
Ok(())
}
}
impl Default for Container {
fn default() -> Self {
Self::new()
}
}
impl Widget for Container {
fn draw(&self, window: &mut dyn Window) -> Result<()> {
self.draw_background(window)?;
self.draw_borders(window)?;
self.draw_children(window)?;
Ok(())
}
fn get_size(&self) -> (u16, u16) {
(self.width, self.height)
}
fn get_position(&self) -> (u16, u16) {
(self.x, self.y)
}
}