From 70e4bd7cd8ec68ed4550c4bcd241e1b1b6644a9c Mon Sep 17 00:00:00 2001 From: daniel Date: Sun, 22 Feb 2026 18:37:12 +0000 Subject: [PATCH] fix: progress bars rendering as binary on/off instead of proportional fill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FillPortion only distributes space among siblings — a single child always takes 100% regardless of the portion value. Added a second container so the fill and remaining space split proportionally, with conditional logic to handle the 0% and 100% edge cases. Co-Authored-By: Claude Opus 4.6 --- .../patch-01kj3a82xept7aw2yb2dtkzefk.md | 13 ++ crates/silo/src/screen/initiative_detail.rs | 116 ++++++++++-------- crates/silo/src/screen/project_detail.rs | 58 +++++---- crates/silo/src/widget/progress_bar.rs | 60 +++++---- crates/silo/src/widget/project_card.rs | 58 +++++---- 5 files changed, 179 insertions(+), 126 deletions(-) create mode 100644 .boop/changelogs/patch-01kj3a82xept7aw2yb2dtkzefk.md diff --git a/.boop/changelogs/patch-01kj3a82xept7aw2yb2dtkzefk.md b/.boop/changelogs/patch-01kj3a82xept7aw2yb2dtkzefk.md new file mode 100644 index 0000000..a2d101c --- /dev/null +++ b/.boop/changelogs/patch-01kj3a82xept7aw2yb2dtkzefk.md @@ -0,0 +1,13 @@ +### Fixed progress bars showing as fully filled or empty + +Progress bars throughout the initiative and project detail screens were rendering as binary — either completely empty or completely full — instead of showing actual proportional progress. A project at 15% completion would display an entirely filled bar. + +The root cause was that `FillPortion` in Iced only distributes space among sibling elements. Each progress bar had a single child container inside the track, so `FillPortion(15)` behaved identically to `FillPortion(100)` — always taking 100% of the parent. + +The fix adds a second container as a sibling to split the space proportionally, with conditional logic to omit the unused portion at 0% and 100% so both edge cases render correctly. + +Affected locations: +- Reusable `ProgressBar` widget +- Initiative detail screen (main progress bar and per-project mini bars) +- Project detail screen progress section +- Project card progress bar \ No newline at end of file diff --git a/crates/silo/src/screen/initiative_detail.rs b/crates/silo/src/screen/initiative_detail.rs index 7cf10e8..76281e3 100644 --- a/crates/silo/src/screen/initiative_detail.rs +++ b/crates/silo/src/screen/initiative_detail.rs @@ -329,35 +329,43 @@ fn view_progress_section<'a>( let track_bg = palette.card; let fill_bg = palette.accent; - // Large progress bar - ensure FillPortion is at least 1 to avoid layout issues + // Large progress bar - conditionally add fill/remaining so 0% and 100% render correctly let fill_portion = ((percent * 100.0) as u16).max(1); - let progress_bar = container( - container("") - .width(Length::FillPortion(fill_portion)) - .height(Length::Fill) - .style(move |_| container::Style { - background: if percent > 0.0 { - Some(Background::Color(fill_bg)) - } else { - None - }, - border: Border { - radius: Radius::from(appearance::CORNER_RADIUS), + let remaining_portion = (((1.0 - percent) * 100.0) as u16).max(1); + let mut bar_row = iced::widget::Row::new().height(Length::Fill); + if percent > 0.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(fill_portion)) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(Background::Color(fill_bg)), + border: Border { + radius: Radius::from(appearance::CORNER_RADIUS), + ..Default::default() + }, ..Default::default() - }, + }), + ); + } + if percent < 1.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(remaining_portion)) + .height(Length::Fill), + ); + } + let progress_bar = container(bar_row) + .width(Length::Fill) + .height(Length::Fixed(12.0)) + .style(move |_| container::Style { + background: Some(Background::Color(track_bg)), + border: Border { + radius: Radius::from(appearance::CORNER_RADIUS), ..Default::default() - }), - ) - .width(Length::Fill) - .height(Length::Fixed(12.0)) - .style(move |_| container::Style { - background: Some(Background::Color(track_bg)), - border: Border { - radius: Radius::from(appearance::CORNER_RADIUS), + }, ..Default::default() - }, - ..Default::default() - }); + }); let content = column![ row![ @@ -446,35 +454,43 @@ fn view_project_card<'a>( palette.accent }; - // Mini progress bar - ensure FillPortion is at least 1 to avoid layout issues + // Mini progress bar - conditionally add fill/remaining so 0% and 100% render correctly let fill_portion = ((progress * 100.0) as u16).max(1); - let progress_bar = container( - container("") - .width(Length::FillPortion(fill_portion)) - .height(Length::Fill) - .style(move |_| container::Style { - background: if progress > 0.0 { - Some(Background::Color(fill_bg)) - } else { - None - }, - border: Border { - radius: Radius::from(2.0), + let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1); + let mut bar_row = iced::widget::Row::new().height(Length::Fill); + if progress > 0.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(fill_portion)) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(Background::Color(fill_bg)), + border: Border { + radius: Radius::from(2.0), + ..Default::default() + }, ..Default::default() - }, + }), + ); + } + if progress < 1.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(remaining_portion)) + .height(Length::Fill), + ); + } + let progress_bar = container(bar_row) + .width(Length::Fill) + .height(Length::Fixed(4.0)) + .style(move |_| container::Style { + background: Some(Background::Color(track_bg)), + border: Border { + radius: Radius::from(2.0), ..Default::default() - }), - ) - .width(Length::Fill) - .height(Length::Fixed(4.0)) - .style(move |_| container::Style { - background: Some(Background::Color(track_bg)), - border: Border { - radius: Radius::from(2.0), + }, ..Default::default() - }, - ..Default::default() - }); + }); // Blocked indicator let blocked_indicator: Element<'a, Message> = if project.blocked { diff --git a/crates/silo/src/screen/project_detail.rs b/crates/silo/src/screen/project_detail.rs index fcb2304..29fd12e 100644 --- a/crates/silo/src/screen/project_detail.rs +++ b/crates/silo/src/screen/project_detail.rs @@ -308,35 +308,43 @@ fn view_progress_section<'a>(stats: &TaskStats, palette: &'a Palette) -> Element let track_bg = palette.card; let fill_bg = palette.accent; - // Progress bar track with fill - ensure FillPortion is at least 1 + // Progress bar track with fill - conditionally add fill/remaining so 0% and 100% render correctly let fill_portion = ((progress * 100.0) as u16).max(1); - let progress_bar = container( - container("") - .width(Length::FillPortion(fill_portion)) - .height(Length::Fill) - .style(move |_| container::Style { - background: if progress > 0.0 { - Some(Background::Color(fill_bg)) - } else { - None - }, - border: Border { - radius: Radius::from(appearance::CORNER_RADIUS_SMALL), + let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1); + let mut bar_row = iced::widget::Row::new().height(Length::Fill); + if progress > 0.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(fill_portion)) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(Background::Color(fill_bg)), + border: Border { + radius: Radius::from(appearance::CORNER_RADIUS_SMALL), + ..Default::default() + }, ..Default::default() - }, + }), + ); + } + if progress < 1.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(remaining_portion)) + .height(Length::Fill), + ); + } + let progress_bar = container(bar_row) + .width(Length::Fill) + .height(Length::Fixed(6.0)) + .style(move |_| container::Style { + background: Some(Background::Color(track_bg)), + border: Border { + radius: Radius::from(appearance::CORNER_RADIUS_SMALL), ..Default::default() - }), - ) - .width(Length::Fill) - .height(Length::Fixed(6.0)) - .style(move |_| container::Style { - background: Some(Background::Color(track_bg)), - border: Border { - radius: Radius::from(appearance::CORNER_RADIUS_SMALL), + }, ..Default::default() - }, - ..Default::default() - }); + }); // Stats text let stats_text = text(format!( diff --git a/crates/silo/src/widget/progress_bar.rs b/crates/silo/src/widget/progress_bar.rs index 87fdc2b..c3c477d 100644 --- a/crates/silo/src/widget/progress_bar.rs +++ b/crates/silo/src/widget/progress_bar.rs @@ -74,36 +74,44 @@ fn progress_bar_inner<'a, Message: 'a>( let track_bg = p.card; let fill_bg = p.accent; - // Use layered containers for track and fill - ensure FillPortion is at least 1 + // Use FillPortion with two siblings so they split space proportionally let fill_portion = ((value * 100.0) as u16).max(1); - container( - container("") - .width(Length::FillPortion(fill_portion)) - .height(Length::Fill) - .style(move |_| container::Style { - background: if value > 0.0 { - Some(Background::Color(fill_bg)) - } else { - None - }, - border: Border { - radius: Radius::from(CORNER_RADIUS_SMALL), + let remaining_portion = (((1.0 - value) * 100.0) as u16).max(1); + let mut bar_row = iced::widget::Row::new().height(Length::Fill); + if value > 0.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(fill_portion)) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(Background::Color(fill_bg)), + border: Border { + radius: Radius::from(CORNER_RADIUS_SMALL), + ..Default::default() + }, ..Default::default() - }, + }), + ); + } + if value < 1.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(remaining_portion)) + .height(Length::Fill), + ); + } + container(bar_row) + .width(Length::Fill) + .height(Length::Fixed(height)) + .style(move |_| container::Style { + background: Some(Background::Color(track_bg)), + border: Border { + radius: Radius::from(CORNER_RADIUS_SMALL), ..Default::default() - }), - ) - .width(Length::Fill) - .height(Length::Fixed(height)) - .style(move |_| container::Style { - background: Some(Background::Color(track_bg)), - border: Border { - radius: Radius::from(CORNER_RADIUS_SMALL), + }, ..Default::default() - }, - ..Default::default() - }) - .into() + }) + .into() } /// Convenience function for creating progress bars diff --git a/crates/silo/src/widget/project_card.rs b/crates/silo/src/widget/project_card.rs index 83a0d2c..d05daed 100644 --- a/crates/silo/src/widget/project_card.rs +++ b/crates/silo/src/widget/project_card.rs @@ -299,35 +299,43 @@ fn view_progress_bar<'a>(stats: &TaskStats, palette: &'a Palette) -> Element<'a, let track_bg = palette.card; let fill_bg = palette.accent; - // Progress bar track with fill - ensure FillPortion is at least 1 + // Progress bar track with fill - conditionally add fill/remaining so 0% and 100% render correctly let fill_portion = ((progress * 100.0) as u16).max(1); - let progress_bar = container( - container("") - .width(Length::FillPortion(fill_portion)) - .height(Length::Fill) - .style(move |_| container::Style { - background: if progress > 0.0 { - Some(Background::Color(fill_bg)) - } else { - None - }, - border: Border { - radius: Radius::from(CORNER_RADIUS_SMALL), + let remaining_portion = (((1.0 - progress) * 100.0) as u16).max(1); + let mut bar_row = iced::widget::Row::new().height(Length::Fill); + if progress > 0.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(fill_portion)) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(Background::Color(fill_bg)), + border: Border { + radius: Radius::from(CORNER_RADIUS_SMALL), + ..Default::default() + }, ..Default::default() - }, + }), + ); + } + if progress < 1.0 { + bar_row = bar_row.push( + container("") + .width(Length::FillPortion(remaining_portion)) + .height(Length::Fill), + ); + } + let progress_bar = container(bar_row) + .width(Length::Fill) + .height(Length::Fixed(4.0)) + .style(move |_| container::Style { + background: Some(Background::Color(track_bg)), + border: Border { + radius: Radius::from(CORNER_RADIUS_SMALL), ..Default::default() - }), - ) - .width(Length::Fill) - .height(Length::Fixed(4.0)) - .style(move |_| container::Style { - background: Some(Background::Color(track_bg)), - border: Border { - radius: Radius::from(CORNER_RADIUS_SMALL), + }, ..Default::default() - }, - ..Default::default() - }); + }); // Stats text let stats_text = text(format!(