fix(progress): equalize dual progress bar heights

The bottom (partial) bar carried a block title (the current filename),
which forces a 1-row top inset in ratatui's `Block::inner` even though
its top border is dropped to join the seam with the full bar. That left
the partial bar with one inner row while the full bar kept two, so the
two gauges rendered at unequal heights.

- Move the filename from the partial bar's title into its gauge label.
- Skip setting an empty title so no phantom top-positioned title triggers
  the inset.
- Put the panel title on the top (full) bar for multi-file transfers.
- Bump the two-bar popup height to fit the joined panel.

Also bump Cargo.lock and adapt the embedded terminal to the new vt100
`screen_mut()` API.
This commit is contained in:
Christian Visintin
2026-06-08 15:36:47 +02:00
parent 1dafc76850
commit f3085161e6
6 changed files with 1344 additions and 915 deletions

2194
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,18 +21,26 @@ impl TransferProgressBar {
color: Color,
sides: BorderSides,
) -> Self {
Self {
component: Gauge::default()
.borders(
Borders::default()
.modifiers(BorderType::Rounded)
.sides(sides),
)
.foreground(color)
.label(label)
.progress(prog)
.title(Title::from(title.into()).alignment(HorizontalAlignment::Center)),
let mut component = Gauge::default()
.borders(
Borders::default()
.modifiers(BorderType::Rounded)
.sides(sides),
)
.foreground(color)
.label(label)
.progress(prog);
// Only set a title when there's actually text. An empty title still
// registers as a top-positioned title in the block, which forces a 1-row
// top inset even when the TOP border is omitted (see `Block::inner`). On
// the bottom bar — whose top edge is dropped to join the seam — that
// inset would shrink its inner area to 1 row while the top bar keeps 2,
// making the two gauges visibly unequal in height.
let title = title.into();
if !title.is_empty() {
component = component.title(Title::from(title).alignment(HorizontalAlignment::Center));
}
Self { component }
}
}

View File

@@ -91,7 +91,7 @@ impl Component for TerminalComponent {
// update the terminal size if it has changed
if self.size != (width, height) {
self.size = (width, height);
self.parser.set_size(height, width);
self.parser.screen_mut().set_size(height, width);
}
let title = self
@@ -236,12 +236,14 @@ impl Component for TerminalComponent {
}
Cmd::Scroll(Direction::Down) => {
self.scroll = self.scroll.saturating_sub(8);
self.parser.set_scrollback(self.scroll);
self.parser.screen_mut().set_scrollback(self.scroll);
CmdResult::NoChange
}
Cmd::Scroll(Direction::Up) => {
self.parser.set_scrollback(self.scroll.saturating_add(8));
self.parser
.screen_mut()
.set_scrollback(self.scroll.saturating_add(8));
let scrollback = self.parser.screen().scrollback();
self.scroll = scrollback;
@@ -275,7 +277,7 @@ impl Component for TerminalComponent {
}
Cmd::Submit => {
self.scroll = 0; // Reset scroll on submit
self.parser.set_scrollback(self.scroll);
self.parser.screen_mut().set_scrollback(self.scroll);
if cfg!(target_family = "unix") {
self.parser.process(b"\n");

View File

@@ -181,11 +181,16 @@ impl FileTransferActivity {
&mut self,
filename: String,
) {
// Update the partial bar with the current file progress
// Update the partial bar with the current file progress. The filename
// goes into the gauge *label*, not a block title: in a multi-file
// transfer the partial bar omits its top border to join the seam with the
// full bar, but any top-positioned title forces a 1-row top inset in
// `Block::inner`, which would shrink the partial bar to one inner row
// while the full bar keeps two — making the two gauges unequal in height.
ui_result(self.app.attr(
&Id::TransferProgressBarPartial,
Attribute::Text,
AttrValue::String(self.transfer.progress.to_string()),
AttrValue::String(format!("{filename}{}", self.transfer.progress)),
));
ui_result(self.app.attr(
&Id::TransferProgressBarPartial,

View File

@@ -221,7 +221,7 @@ impl FileTransferActivity {
self.app.view(&Id::TransferProgressBarPartial, f, popup);
} else {
let popup =
Popup(Size::Percentage(50), Size::Unit(6)).draw_in(f.area());
Popup(Size::Percentage(50), Size::Unit(7)).draw_in(f.area());
f.render_widget(Clear, popup);
let chunks = Layout::default()
.direction(Direction::Vertical)

View File

@@ -440,12 +440,22 @@ impl FileTransferActivity {
BorderSides::BOTTOM | BorderSides::LEFT | BorderSides::RIGHT,
)
};
// The root name titles the panel. For a multi-file transfer the title
// lives on the top (full) bar, whose TOP border carries it; the bottom
// (partial) bar stays untitled so it isn't pushed down by a top inset and
// both gauges keep the same inner height. A single file shows only the
// partial bar, so the title goes there instead.
let (full_title, partial_title) = if self.is_single_file_transfer() {
("", root_name.as_str())
} else {
(root_name.as_str(), "")
};
ui_result(self.app.remount(
Id::TransferProgressBarPartial,
Box::new(components::TransferProgressBar::new(
0.0,
"",
root_name.as_str(),
partial_title,
partial_color,
partial_sides,
)),
@@ -454,7 +464,7 @@ impl FileTransferActivity {
ui_result(self.app.remount(
Id::TransferProgressBarFull,
Box::new(components::TransferProgressBar::new(
0.0, "", "", full_color, full_sides,
0.0, "", full_title, full_color, full_sides,
)),
vec![],
));