use std::cmp::Ordering; use async_trait::async_trait; use crate::{AsyncWidget, Frame, Pos, Size, Widget, WidthDb}; // The following algorithm has three goals, listed in order of importance: // // 1. Use the available space // 2. Avoid shrinking segments where possible // 3. Match the given weights as closely as possible // // Its input is a list of weighted segments where each segment wants to use a // certain amount of space. The weights signify how the available space would be // assigned if goal 2 was irrelevant. // // First, the algorithm must decide whether it must grow or shrink segments. // Because goal 2 has a higher priority than goal 3, it never makes sense to // shrink a segment in order to make another larger. In both cases, a segment's // actual size is compared to its allotment, i. e. what size it should be based // on its weight. // // Growth // ====== // // If segments must be grown, an important observation can be made: If all // segments are smaller than their allotment, then each segment can be assigned // its allotment without violating goal 2, thereby fulfilling goal 3. // // Another important observation can be made: If a segment is at least as large // as its allotment, it must never be grown as that would violate goal 3. // // Based on these two observations, the growth algorithm first repeatedly // removes all segments that are at least as large as their allotment. It then // resizes the remaining segments to their allotments. // // Shrinkage // ========= // // If segments must be shrunk, an important observation can be made: If all // segments are larger than their allotment, then each segment can be assigned // its allotment, thereby fulfilling goal 3. Since goal 1 is more important than // goal 2, we know that some elements must be shrunk. // // Another important observation can be made: If a segment is at least as small // as its allotment, it must never be shrunk as that would violate goal 3. // // Based on these two observations, the shrinkage algorithm first repeatedly // removes all segments that are at least as small as their allotment. It then // resizes the remaining segments to their allotments. #[derive(Debug)] struct Segment { major: u16, minor: u16, weight: f32, growing: bool, shrinking: bool, } impl Segment { fn new(major_minor: (u16, u16), segment: &JoinSegment) -> Self { Self { major: major_minor.0, minor: major_minor.1, weight: segment.weight, growing: segment.growing, shrinking: segment.shrinking, } } } fn total_size(segments: &[&mut Segment]) -> u16 { let mut total = 0_u16; for segment in segments { total = total.saturating_add(segment.major); } total } fn total_weight(segments: &[&mut Segment]) -> f32 { segments.iter().map(|s| s.weight).sum() } fn balance(segments: &mut [Segment], available: u16) { let segments = segments.iter_mut().collect::>(); match total_size(&segments).cmp(&available) { Ordering::Less => grow(segments, available), Ordering::Greater => shrink(segments, available), Ordering::Equal => {} } } fn grow(mut segments: Vec<&mut Segment>, mut available: u16) { assert!(available >= total_size(&segments)); // Only grow segments that can be grown. segments.retain(|s| { if s.growing { return true; } available = available.saturating_sub(s.major); false }); // Repeatedly remove all segments that do not need to grow, i. e. that are // at least as large as their allotment. loop { let mut total_weight = total_weight(&segments); // If there are no segments with a weight > 0, space is distributed // evenly among all remaining segments. if total_weight <= 0.0 { for segment in &mut segments { segment.weight = 1.0; } total_weight = segments.len() as f32; } let mut removed = 0; segments.retain(|s| { let allotment = s.weight / total_weight * available as f32; if (s.major as f32) < allotment { return true; // May need to grow } removed += s.major; false }); available -= removed; if removed == 0 { break; // All remaining segments are smaller than their allotments } } let total_weight = segments.iter().map(|s| s.weight).sum::(); if total_weight <= 0.0 { return; // No more segments left } // Size each remaining segment according to its allotment. let mut used = 0; for segment in &mut segments { let allotment = segment.weight / total_weight * available as f32; segment.major = allotment.floor() as u16; used += segment.major; } // Distribute remaining unused space from left to right. // // The rounding error on each segment is at most 1, so we only need to loop // over the segments once. let remaining = available - used; assert!(remaining as usize <= segments.len()); for segment in segments.into_iter().take(remaining.into()) { segment.major += 1; } } fn shrink(mut segments: Vec<&mut Segment>, mut available: u16) { assert!(available <= total_size(&segments)); // Only shrink segments that can be shrunk. segments.retain(|s| { if s.shrinking { return true; } available = available.saturating_sub(s.major); false }); // Repeatedly remove all segments that do not need to shrink, i. e. that are // at least as small as their allotment. loop { let mut total_weight = total_weight(&segments); // If there are no segments with a weight > 0, space is distributed // evenly among all remaining segments. if total_weight <= 0.0 { for segment in &mut segments { segment.weight = 1.0; } total_weight = segments.len() as f32; } let mut removed = 0; segments.retain(|s| { let allotment = s.weight / total_weight * available as f32; if (s.major as f32) > allotment { return true; // May need to shrink } // The segment size subtracted from `available` is always smaller // than or equal to its allotment. Since `available` is the sum of // all allotments, it can never go below 0. assert!(s.major <= available); removed += s.major; false }); available -= removed; if removed == 0 { break; // All segments want more than their weight allows. } } let total_weight = segments.iter().map(|s| s.weight).sum::(); if total_weight <= 0.0 { return; // No more segments left } // Size each remaining segment according to its allotment. let mut used = 0; for segment in &mut segments { let allotment = segment.weight / total_weight * available as f32; segment.major = allotment.floor() as u16; used += segment.major; } // Distribute remaining unused space from left to right. // // The rounding error on each segment is at most 1, so we only need to loop // over the segments once. let remaining = available - used; assert!(remaining as usize <= segments.len()); for segment in segments.into_iter().take(remaining.into()) { segment.major += 1; } } #[derive(Debug, Clone, Copy)] pub struct JoinSegment { pub inner: I, weight: f32, pub growing: bool, pub shrinking: bool, } impl JoinSegment { pub fn new(inner: I) -> Self { Self { inner, weight: 1.0, growing: true, shrinking: true, } } pub fn weight(&self) -> f32 { self.weight } pub fn set_weight(&mut self, weight: f32) { assert!(weight >= 0.0); self.weight = weight; } pub fn with_weight(mut self, weight: f32) -> Self { self.set_weight(weight); self } pub fn with_growing(mut self, enabled: bool) -> Self { self.growing = enabled; self } pub fn with_shrinking(mut self, enabled: bool) -> Self { self.shrinking = enabled; self } pub fn with_fixed(self, fixed: bool) -> Self { self.with_growing(!fixed).with_shrinking(!fixed) } } fn to_mm(horizontal: bool, w: T, h: T) -> (T, T) { if horizontal { (w, h) } else { (h, w) } } fn from_mm(horizontal: bool, major: T, minor: T) -> (T, T) { if horizontal { (major, minor) } else { (minor, major) } } fn size>( horizontal: bool, widthdb: &mut WidthDb, segment: &JoinSegment, major: Option, minor: Option, ) -> Result<(u16, u16), E> { if horizontal { let size = segment.inner.size(widthdb, major, minor)?; Ok((size.width, size.height)) } else { let size = segment.inner.size(widthdb, minor, major)?; Ok((size.height, size.width)) } } fn size_with_balanced>( horizontal: bool, widthdb: &mut WidthDb, segment: &JoinSegment, balanced: &Segment, minor: Option, ) -> Result<(u16, u16), E> { size(horizontal, widthdb, segment, Some(balanced.major), minor) } async fn size_async>( horizontal: bool, widthdb: &mut WidthDb, segment: &JoinSegment, major: Option, minor: Option, ) -> Result<(u16, u16), E> { if horizontal { let size = segment.inner.size(widthdb, major, minor).await?; Ok((size.width, size.height)) } else { let size = segment.inner.size(widthdb, minor, major).await?; Ok((size.height, size.width)) } } async fn size_async_with_balanced>( horizontal: bool, widthdb: &mut WidthDb, segment: &JoinSegment, balanced: &Segment, minor: Option, ) -> Result<(u16, u16), E> { size_async(horizontal, widthdb, segment, Some(balanced.major), minor).await } fn sum_major_max_minor(segments: &[Segment]) -> (u16, u16) { let mut major = 0_u16; let mut minor = 0_u16; for segment in segments { major = major.saturating_add(segment.major); minor = minor.max(segment.minor); } (major, minor) } #[derive(Debug, Clone)] pub struct Join { horizontal: bool, segments: Vec>, } impl Join { pub fn horizontal(segments: Vec>) -> Self { Self { horizontal: true, segments, } } pub fn vertical(segments: Vec>) -> Self { Self { horizontal: false, segments, } } } impl Widget for Join where I: Widget, { fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height); let mut segments = Vec::with_capacity(self.segments.len()); for segment in &self.segments { let major_minor = size(self.horizontal, widthdb, segment, None, max_minor)?; segments.push(Segment::new(major_minor, segment)); } if let Some(available) = max_major { balance(&mut segments, available); let mut new_segments = Vec::with_capacity(self.segments.len()); for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) { let major_minor = size_with_balanced(self.horizontal, widthdb, segment, &balanced, max_minor)?; new_segments.push(Segment::new(major_minor, segment)); } segments = new_segments; } let (major, minor) = sum_major_max_minor(&segments); let (width, height) = from_mm(self.horizontal, major, minor); Ok(Size::new(width, height)) } fn draw(self, frame: &mut Frame) -> Result<(), E> { let frame_size = frame.size(); let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height); let widthdb = frame.widthdb(); let mut segments = Vec::with_capacity(self.segments.len()); for segment in &self.segments { let major_minor = size(self.horizontal, widthdb, segment, None, Some(max_minor))?; segments.push(Segment::new(major_minor, segment)); } balance(&mut segments, max_major); let mut major = 0_i32; for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) { let (x, y) = from_mm(self.horizontal, major, 0); let (w, h) = from_mm(self.horizontal, balanced.major, max_minor); frame.push(Pos::new(x, y), Size::new(w, h)); segment.inner.draw(frame)?; frame.pop(); major += balanced.major as i32; } Ok(()) } } #[async_trait] impl AsyncWidget for Join where I: AsyncWidget + Send + Sync, { async fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height); let mut segments = Vec::with_capacity(self.segments.len()); for segment in &self.segments { let major_minor = size_async(self.horizontal, widthdb, segment, None, max_minor).await?; segments.push(Segment::new(major_minor, segment)); } if let Some(available) = max_major { balance(&mut segments, available); let mut new_segments = Vec::with_capacity(self.segments.len()); for (segment, balanced) in self.segments.iter().zip(segments.into_iter()) { let major_minor = size_async_with_balanced( self.horizontal, widthdb, segment, &balanced, max_minor, ) .await?; new_segments.push(Segment::new(major_minor, segment)); } segments = new_segments; } let (major, minor) = sum_major_max_minor(&segments); let (width, height) = from_mm(self.horizontal, major, minor); Ok(Size::new(width, height)) } async fn draw(self, frame: &mut Frame) -> Result<(), E> { let frame_size = frame.size(); let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height); let widthdb = frame.widthdb(); let mut segments = Vec::with_capacity(self.segments.len()); for segment in &self.segments { let major_minor = size_async(self.horizontal, widthdb, segment, None, Some(max_minor)).await?; segments.push(Segment::new(major_minor, segment)); } balance(&mut segments, max_major); let mut major = 0_i32; for (segment, balanced) in self.segments.into_iter().zip(segments.into_iter()) { let (x, y) = from_mm(self.horizontal, major, 0); let (w, h) = from_mm(self.horizontal, balanced.major, max_minor); frame.push(Pos::new(x, y), Size::new(w, h)); segment.inner.draw(frame).await?; frame.pop(); major += balanced.major as i32; } Ok(()) } } macro_rules! mk_join { ( pub struct $name:ident { $( pub $arg:ident: $type:ident [$n:expr], )+ } ) => { #[derive(Debug, Clone, Copy)] pub struct $name< $($type),+ >{ horizontal: bool, $( pub $arg: JoinSegment<$type>, )+ } impl< $($type),+ > $name< $($type),+ >{ pub fn horizontal( $($arg: JoinSegment<$type>),+ ) -> Self { Self { horizontal: true, $( $arg, )+ } } pub fn vertical( $($arg: JoinSegment<$type>),+ ) -> Self { Self { horizontal: false, $( $arg, )+ } } } impl Widget for $name< $($type),+ > where $( $type: Widget, )+ { fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height); let mut segments = [ $( Segment::new( size(self.horizontal, widthdb, &self.$arg, None, max_minor)?, &self.$arg, ), )+ ]; if let Some(available) = max_major { balance(&mut segments, available); let new_segments = [ $( Segment::new( size_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor)?, &self.$arg, ), )+ ]; segments = new_segments; } let (major, minor) = sum_major_max_minor(&segments); let (width, height) = from_mm(self.horizontal, major, minor); Ok(Size::new(width, height)) } #[allow(unused_assignments)] fn draw(self, frame: &mut Frame) -> Result<(), E> { let frame_size = frame.size(); let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height); let widthdb = frame.widthdb(); let mut segments = [ $( Segment::new( size(self.horizontal, widthdb, &self.$arg, None, Some(max_minor))?, &self.$arg, ), )+ ]; balance(&mut segments, max_major); let mut major = 0_i32; $( { let balanced = &segments[$n]; let (x, y) = from_mm(self.horizontal, major, 0); let (w, h) = from_mm(self.horizontal, balanced.major, max_minor); frame.push(Pos::new(x, y), Size::new(w, h)); self.$arg.inner.draw(frame)?; frame.pop(); major += balanced.major as i32; } )* Ok(()) } } #[async_trait] impl AsyncWidget for $name< $($type),+ > where E: Send, $( $type: AsyncWidget + Send + Sync, )+ { async fn size( &self, widthdb: &mut WidthDb, max_width: Option, max_height: Option, ) -> Result { let (max_major, max_minor) = to_mm(self.horizontal, max_width, max_height); let mut segments = [ $( Segment::new( size_async(self.horizontal, widthdb, &self.$arg, None, max_minor).await?, &self.$arg, ), )+ ]; if let Some(available) = max_major { balance(&mut segments, available); let new_segments = [ $( Segment::new( size_async_with_balanced(self.horizontal, widthdb, &self.$arg, &segments[$n], max_minor).await?, &self.$arg, ), )+ ]; segments = new_segments; } let (major, minor) = sum_major_max_minor(&segments); let (width, height) = from_mm(self.horizontal, major, minor); Ok(Size::new(width, height)) } #[allow(unused_assignments)] async fn draw(self, frame: &mut Frame) -> Result<(), E> { let frame_size = frame.size(); let (max_major, max_minor) = to_mm(self.horizontal, frame_size.width, frame_size.height); let widthdb = frame.widthdb(); let mut segments = [ $( Segment::new( size_async(self.horizontal, widthdb, &self.$arg, None, Some(max_minor)).await?, &self.$arg, ), )+ ]; balance(&mut segments, max_major); let mut major = 0_i32; $( { let balanced = &segments[$n]; let (x, y) = from_mm(self.horizontal, major, 0); let (w, h) = from_mm(self.horizontal, balanced.major, max_minor); frame.push(Pos::new(x, y), Size::new(w, h)); self.$arg.inner.draw(frame).await?; frame.pop(); major += balanced.major as i32; } )* Ok(()) } } }; } mk_join! { pub struct Join2 { pub first: I1 [0], pub second: I2 [1], } } mk_join! { pub struct Join3 { pub first: I1 [0], pub second: I2 [1], pub third: I3 [2], } } mk_join! { pub struct Join4 { pub first: I1 [0], pub second: I2 [1], pub third: I3 [2], pub fourth: I4 [3], } } mk_join! { pub struct Join5 { pub first: I1 [0], pub second: I2 [1], pub third: I3 [2], pub fourth: I4 [3], pub fifth: I5 [4], } } mk_join! { pub struct Join6 { pub first: I1 [0], pub second: I2 [1], pub third: I3 [2], pub fourth: I4 [3], pub fifth: I5 [4], pub sixth: I6 [5], } } mk_join! { pub struct Join7 { pub first: I1 [0], pub second: I2 [1], pub third: I3 [2], pub fourth: I4 [3], pub fifth: I5 [4], pub sixth: I6 [5], pub seventh: I7 [6], } }