diff --git a/src/ui/chat/tree/widgets.rs b/src/ui/chat/tree/widgets.rs index 0c0e585..ce08dbe 100644 --- a/src/ui/chat/tree/widgets.rs +++ b/src/ui/chat/tree/widgets.rs @@ -30,7 +30,7 @@ pub fn msg(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget { Segment::new(Padding::new(Text::new(msg.nick())).right(1)), // TODO Minimum content width // TODO Minimizing and maximizing messages - Segment::new(Text::new(msg.content()).wrap(true)), + Segment::new(Text::new(msg.content()).wrap(true)).priority(1), ]) .into() } diff --git a/src/ui/widgets/join.rs b/src/ui/widgets/join.rs index 89c8945..99690d2 100644 --- a/src/ui/widgets/join.rs +++ b/src/ui/widgets/join.rs @@ -6,6 +6,7 @@ use super::{BoxedWidget, Widget}; pub struct Segment { widget: BoxedWidget, expanding: bool, + priority: Option, } impl Segment { @@ -13,32 +14,141 @@ impl Segment { Self { widget: widget.into(), expanding: false, + priority: None, } } + /// Expand this segment into the remaining space after all segment minimum + /// sizes have been determined. The remaining space is split up evenly. pub fn expanding(mut self, active: bool) -> Self { self.expanding = active; self } + + /// The size of segments with a priority is calculated in order of + /// increasing priority, using the remaining available space as maximum + /// space for the widget during size calculations. + /// + /// Widgets without priority are processed first without size restrictions. + pub fn priority(mut self, priority: u8) -> Self { + self.priority = Some(priority); + self + } } -fn expand(amounts: &mut [(u16, bool)], total: u16) { - let any_expanding = amounts.iter().any(|(_, expanding)| *expanding); - if !any_expanding { +struct SizedSegment { + idx: usize, + size: Size, + expanding: bool, + priority: Option, +} + +impl SizedSegment { + pub fn new(idx: usize, segment: &Segment) -> Self { + Self { + idx, + size: Size::ZERO, + expanding: segment.expanding, + priority: segment.priority, + } + } +} + +fn sizes_horiz( + segments: &[Segment], + frame: &mut Frame, + max_width: Option, + max_height: Option, +) -> Vec { + let mut sized = segments + .iter() + .enumerate() + .map(|(i, s)| SizedSegment::new(i, s)) + .collect::>(); + sized.sort_by_key(|s| s.priority); + + let mut total_width = 0; + for s in &mut sized { + let available_width = max_width + .filter(|_| s.priority.is_some()) + .map(|w| w.saturating_sub(total_width)); + s.size = segments[s.idx] + .widget + .size(frame, available_width, max_height); + total_width += s.size.width; + } + + sized +} + +fn sizes_vert( + segments: &[Segment], + frame: &mut Frame, + max_width: Option, + max_height: Option, +) -> Vec { + let mut sized = segments + .iter() + .enumerate() + .map(|(i, s)| SizedSegment::new(i, s)) + .collect::>(); + sized.sort_by_key(|s| s.priority); + + let mut total_height = 0; + for s in &mut sized { + let available_height = max_height + .filter(|_| s.priority.is_some()) + .map(|w| w.saturating_sub(total_height)); + s.size = segments[s.idx] + .widget + .size(frame, max_width, available_height); + total_height += s.size.height; + } + + sized +} + +fn expand_horiz(segments: &mut [SizedSegment], available_width: u16) { + if !segments.iter().any(|s| s.expanding) { return; } - // Weirdly, rustc needs this type annotation while rust-analyzer manages to - // derive the correct type in an inlay hint. - let actual: u16 = amounts.iter().map(|(a, _)| *a).sum(); - if actual < total { - let mut remaining = total - actual; - while remaining > 0 { - for (amount, expanding) in amounts.iter_mut() { - if *expanding { - if remaining > 0 { - *amount += 1; - remaining -= 1; + // Interestingly, rustc needs this type annotation while rust-analyzer + // manages to derive the correct type in an inlay hint. + let current_width = segments.iter().map(|s| s.size.width).sum::(); + if current_width < available_width { + let mut remaining_width = available_width - current_width; + while remaining_width > 0 { + for segment in segments.iter_mut() { + if segment.expanding { + if remaining_width > 0 { + segment.size.width += 1; + remaining_width -= 1; + } else { + break; + } + } + } + } + } +} + +fn expand_vert(segments: &mut [SizedSegment], available_height: u16) { + if !segments.iter().any(|s| s.expanding) { + return; + } + + // Interestingly, rustc needs this type annotation while rust-analyzer + // manages to derive the correct type in an inlay hint. + let current_height = segments.iter().map(|s| s.size.height).sum::(); + if current_height < available_height { + let mut remaining_height = available_height - current_height; + while remaining_height > 0 { + for segment in segments.iter_mut() { + if segment.expanding { + if remaining_height > 0 { + segment.size.height += 1; + remaining_height -= 1; } else { break; } @@ -61,34 +171,27 @@ impl HJoin { #[async_trait] impl Widget for HJoin { - fn size(&self, frame: &mut Frame, _max_width: Option, max_height: Option) -> Size { - let mut size = Size::ZERO; - for segment in &self.segments { - let widget_size = segment.widget.size(frame, None, max_height); - size.width += widget_size.width; - size.height = size.height.max(widget_size.height); - } - size + fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + let sizes = sizes_horiz(&self.segments, frame, max_width, max_height); + let width = sizes.iter().map(|s| s.size.width).sum::(); + let height = sizes.iter().map(|s| s.size.height).max().unwrap_or(0); + Size::new(width, height) } async fn render(self: Box, frame: &mut Frame) { let size = frame.size(); - let mut widths = self - .segments - .iter() - .map(|s| { - let width = s.widget.size(frame, None, Some(size.height)).width; - (width, s.expanding) - }) - .collect::>(); - expand(&mut widths, size.width); + let mut sizes = sizes_horiz(&self.segments, frame, Some(size.width), Some(size.height)); + expand_horiz(&mut sizes, size.width); + + sizes.sort_by_key(|s| s.idx); let mut x = 0; - for (segment, (width, _)) in self.segments.into_iter().zip(widths.into_iter()) { - frame.push(Pos::new(x, 0), Size::new(width, size.height)); + for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) { + frame.push(Pos::new(x, 0), Size::new(sized.size.width, size.height)); segment.widget.render(frame).await; frame.pop(); - x += width as i32; + + x += sized.size.width as i32; } } } @@ -106,34 +209,27 @@ impl VJoin { #[async_trait] impl Widget for VJoin { - fn size(&self, frame: &mut Frame, max_width: Option, _max_height: Option) -> Size { - let mut size = Size::ZERO; - for segment in &self.segments { - let widget_size = segment.widget.size(frame, max_width, None); - size.width = size.width.max(widget_size.width); - size.height += widget_size.height; - } - size + fn size(&self, frame: &mut Frame, max_width: Option, max_height: Option) -> Size { + let sizes = sizes_vert(&self.segments, frame, max_width, max_height); + let width = sizes.iter().map(|s| s.size.width).max().unwrap_or(0); + let height = sizes.iter().map(|s| s.size.height).sum::(); + Size::new(width, height) } async fn render(self: Box, frame: &mut Frame) { let size = frame.size(); - let mut heights = self - .segments - .iter() - .map(|s| { - let height = s.widget.size(frame, Some(size.width), None).height; - (height, s.expanding) - }) - .collect::>(); - expand(&mut heights, size.height); + let mut sizes = sizes_vert(&self.segments, frame, Some(size.width), Some(size.height)); + expand_vert(&mut sizes, size.height); + + sizes.sort_by_key(|s| s.idx); let mut y = 0; - for (segment, (height, _)) in self.segments.into_iter().zip(heights.into_iter()) { - frame.push(Pos::new(0, y), Size::new(size.width, height)); + for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) { + frame.push(Pos::new(0, y), Size::new(size.width, sized.size.height)); segment.widget.render(frame).await; frame.pop(); - y += height as i32; + + y += sized.size.height as i32; } } }