Add priorities to VJoin/HJoin Segments

This commit is contained in:
Joscha 2022-08-01 17:11:13 +02:00
parent f3b804347d
commit a50f81f9b3
2 changed files with 151 additions and 55 deletions

View file

@ -30,7 +30,7 @@ pub fn msg<M: Msg>(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget {
Segment::new(Padding::new(Text::new(msg.nick())).right(1)), Segment::new(Padding::new(Text::new(msg.nick())).right(1)),
// TODO Minimum content width // TODO Minimum content width
// TODO Minimizing and maximizing messages // TODO Minimizing and maximizing messages
Segment::new(Text::new(msg.content()).wrap(true)), Segment::new(Text::new(msg.content()).wrap(true)).priority(1),
]) ])
.into() .into()
} }

View file

@ -6,6 +6,7 @@ use super::{BoxedWidget, Widget};
pub struct Segment { pub struct Segment {
widget: BoxedWidget, widget: BoxedWidget,
expanding: bool, expanding: bool,
priority: Option<u8>,
} }
impl Segment { impl Segment {
@ -13,32 +14,141 @@ impl Segment {
Self { Self {
widget: widget.into(), widget: widget.into(),
expanding: false, 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 { pub fn expanding(mut self, active: bool) -> Self {
self.expanding = active; self.expanding = active;
self 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) { struct SizedSegment {
let any_expanding = amounts.iter().any(|(_, expanding)| *expanding); idx: usize,
if !any_expanding { size: Size,
expanding: bool,
priority: Option<u8>,
}
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<u16>,
max_height: Option<u16>,
) -> Vec<SizedSegment> {
let mut sized = segments
.iter()
.enumerate()
.map(|(i, s)| SizedSegment::new(i, s))
.collect::<Vec<_>>();
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<u16>,
max_height: Option<u16>,
) -> Vec<SizedSegment> {
let mut sized = segments
.iter()
.enumerate()
.map(|(i, s)| SizedSegment::new(i, s))
.collect::<Vec<_>>();
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; return;
} }
// Weirdly, rustc needs this type annotation while rust-analyzer manages to // Interestingly, rustc needs this type annotation while rust-analyzer
// derive the correct type in an inlay hint. // manages to derive the correct type in an inlay hint.
let actual: u16 = amounts.iter().map(|(a, _)| *a).sum(); let current_width = segments.iter().map(|s| s.size.width).sum::<u16>();
if actual < total { if current_width < available_width {
let mut remaining = total - actual; let mut remaining_width = available_width - current_width;
while remaining > 0 { while remaining_width > 0 {
for (amount, expanding) in amounts.iter_mut() { for segment in segments.iter_mut() {
if *expanding { if segment.expanding {
if remaining > 0 { if remaining_width > 0 {
*amount += 1; segment.size.width += 1;
remaining -= 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::<u16>();
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 { } else {
break; break;
} }
@ -61,34 +171,27 @@ impl HJoin {
#[async_trait] #[async_trait]
impl Widget for HJoin { impl Widget for HJoin {
fn size(&self, frame: &mut Frame, _max_width: Option<u16>, max_height: Option<u16>) -> Size { fn size(&self, frame: &mut Frame, max_width: Option<u16>, max_height: Option<u16>) -> Size {
let mut size = Size::ZERO; let sizes = sizes_horiz(&self.segments, frame, max_width, max_height);
for segment in &self.segments { let width = sizes.iter().map(|s| s.size.width).sum::<u16>();
let widget_size = segment.widget.size(frame, None, max_height); let height = sizes.iter().map(|s| s.size.height).max().unwrap_or(0);
size.width += widget_size.width; Size::new(width, height)
size.height = size.height.max(widget_size.height);
}
size
} }
async fn render(self: Box<Self>, frame: &mut Frame) { async fn render(self: Box<Self>, frame: &mut Frame) {
let size = frame.size(); 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::<Vec<_>>();
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; let mut x = 0;
for (segment, (width, _)) in self.segments.into_iter().zip(widths.into_iter()) { for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) {
frame.push(Pos::new(x, 0), Size::new(width, size.height)); frame.push(Pos::new(x, 0), Size::new(sized.size.width, size.height));
segment.widget.render(frame).await; segment.widget.render(frame).await;
frame.pop(); frame.pop();
x += width as i32;
x += sized.size.width as i32;
} }
} }
} }
@ -106,34 +209,27 @@ impl VJoin {
#[async_trait] #[async_trait]
impl Widget for VJoin { impl Widget for VJoin {
fn size(&self, frame: &mut Frame, max_width: Option<u16>, _max_height: Option<u16>) -> Size { fn size(&self, frame: &mut Frame, max_width: Option<u16>, max_height: Option<u16>) -> Size {
let mut size = Size::ZERO; let sizes = sizes_vert(&self.segments, frame, max_width, max_height);
for segment in &self.segments { let width = sizes.iter().map(|s| s.size.width).max().unwrap_or(0);
let widget_size = segment.widget.size(frame, max_width, None); let height = sizes.iter().map(|s| s.size.height).sum::<u16>();
size.width = size.width.max(widget_size.width); Size::new(width, height)
size.height += widget_size.height;
}
size
} }
async fn render(self: Box<Self>, frame: &mut Frame) { async fn render(self: Box<Self>, frame: &mut Frame) {
let size = frame.size(); 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::<Vec<_>>();
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; let mut y = 0;
for (segment, (height, _)) in self.segments.into_iter().zip(heights.into_iter()) { for (segment, sized) in self.segments.into_iter().zip(sizes.into_iter()) {
frame.push(Pos::new(0, y), Size::new(size.width, height)); frame.push(Pos::new(0, y), Size::new(size.width, sized.size.height));
segment.widget.render(frame).await; segment.widget.render(frame).await;
frame.pop(); frame.pop();
y += height as i32;
y += sized.size.height as i32;
} }
} }
} }