mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/welcome): stabilize intro logo sizing
This commit is contained in:
@@ -4796,6 +4796,17 @@ impl ChatWidget<'_> {
|
||||
self.render_request_cache_dirty.set(true);
|
||||
}
|
||||
|
||||
fn update_welcome_height_hint(&self, height: u16) {
|
||||
for cell in self.history_cells.iter() {
|
||||
if let Some(welcome) = cell
|
||||
.as_any()
|
||||
.downcast_ref::<crate::history_cell::AnimatedWelcomeCell>()
|
||||
{
|
||||
welcome.set_available_height(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_frozen_cell(cell: &dyn HistoryCell) -> bool {
|
||||
cell.as_any().downcast_ref::<FrozenHistoryCell>().is_some()
|
||||
}
|
||||
@@ -37334,6 +37345,8 @@ impl WidgetRef for &ChatWidget<'_> {
|
||||
height: history_area.height,
|
||||
};
|
||||
|
||||
self.update_welcome_height_hint(content_area.height);
|
||||
|
||||
// Reset the full history region to the baseline theme background once per frame.
|
||||
// Individual cells only repaint when their visuals differ (e.g., assistant tint),
|
||||
// which keeps overdraw minimal while ensuring stale characters disappear.
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn intro_art_size_for_width(width: u16) -> IntroArtSize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intro_art_size_for_area(width: u16, height: u16) -> IntroArtSize {
|
||||
pub(crate) fn intro_art_size_for_area(width: u16, height: u16) -> IntroArtSize {
|
||||
if width >= LARGE_MIN_WIDTH && height >= LARGE_MIN_HEIGHT {
|
||||
IntroArtSize::Large
|
||||
} else if width >= MEDIUM_MIN_WIDTH && height >= MEDIUM_MIN_HEIGHT {
|
||||
@@ -53,23 +53,14 @@ pub fn intro_art_height(size: IntroArtSize) -> u16 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_intro_animation_with_size(
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
t: f32,
|
||||
size: IntroArtSize,
|
||||
version: &str,
|
||||
) {
|
||||
render_intro_animation_with_size_and_alpha(area, buf, t, 1.0, size, version);
|
||||
}
|
||||
|
||||
pub fn render_intro_animation_with_size_and_alpha(
|
||||
pub(crate) fn render_intro_animation_with_size_and_alpha_offset(
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
t: f32,
|
||||
alpha: f32,
|
||||
size: IntroArtSize,
|
||||
version: &str,
|
||||
row_offset: u16,
|
||||
) {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
return;
|
||||
@@ -83,7 +74,24 @@ pub fn render_intro_animation_with_size_and_alpha(
|
||||
let scan_p = smoothstep(0.55, 0.85, t);
|
||||
let frame = (t * 60.0) as u32;
|
||||
|
||||
let lines = welcome_lines(size, version);
|
||||
let mut lines = welcome_lines(size, version);
|
||||
let full_width = lines.iter().map(|line| line.chars().count()).max().unwrap_or(0);
|
||||
let start = row_offset as usize;
|
||||
if start >= lines.len() {
|
||||
return;
|
||||
}
|
||||
let end = (start + area.height as usize).min(lines.len());
|
||||
if start > 0 || end < lines.len() {
|
||||
lines = lines[start..end].iter().cloned().collect();
|
||||
if full_width > 0 {
|
||||
for line in &mut lines {
|
||||
let len = line.chars().count();
|
||||
if len < full_width {
|
||||
line.push_str(&" ".repeat(full_width - len));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (char_mask, anim_mask, shadow_mask, w, h) =
|
||||
lines_masks(&lines, |ch| ANIMATED_CHARS.contains(&ch));
|
||||
if w == 0 || h == 0 {
|
||||
@@ -616,7 +624,15 @@ mod tests {
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
let mut buf = Buffer::empty(rect);
|
||||
|
||||
render_intro_animation_with_size(rect, &mut buf, 1.0, IntroArtSize::Large, &version);
|
||||
render_intro_animation_with_size_and_alpha_offset(
|
||||
rect,
|
||||
&mut buf,
|
||||
1.0,
|
||||
1.0,
|
||||
IntroArtSize::Large,
|
||||
&version,
|
||||
0,
|
||||
);
|
||||
|
||||
let rendered = buffer_to_strings(&buf, rect);
|
||||
assert_eq!(trim_lines(rendered), trim_lines(expected));
|
||||
@@ -631,7 +647,15 @@ mod tests {
|
||||
let rect = Rect::new(0, 0, width, height);
|
||||
let mut buf = Buffer::empty(rect);
|
||||
|
||||
render_intro_animation_with_size(rect, &mut buf, 1.0, IntroArtSize::Medium, &version);
|
||||
render_intro_animation_with_size_and_alpha_offset(
|
||||
rect,
|
||||
&mut buf,
|
||||
1.0,
|
||||
1.0,
|
||||
IntroArtSize::Medium,
|
||||
&version,
|
||||
0,
|
||||
);
|
||||
|
||||
let rendered = buffer_to_strings(&buf, rect);
|
||||
assert_eq!(trim_lines(rendered), trim_lines(expected));
|
||||
@@ -645,7 +669,15 @@ mod tests {
|
||||
let rect = Rect::new(0, 0, width, 1);
|
||||
let mut buf = Buffer::empty(rect);
|
||||
|
||||
render_intro_animation_with_size(rect, &mut buf, 1.0, IntroArtSize::Small, &version);
|
||||
render_intro_animation_with_size_and_alpha_offset(
|
||||
rect,
|
||||
&mut buf,
|
||||
1.0,
|
||||
1.0,
|
||||
IntroArtSize::Small,
|
||||
&version,
|
||||
0,
|
||||
);
|
||||
|
||||
let rendered = buffer_to_strings(&buf, rect);
|
||||
assert_eq!(trim_lines(rendered), trim_lines(expected));
|
||||
|
||||
@@ -7,7 +7,7 @@ pub(crate) struct AnimatedWelcomeCell {
|
||||
completed: Cell<bool>,
|
||||
fade_start: RefCell<Option<Instant>>,
|
||||
faded_out: Cell<bool>,
|
||||
locked_height: Cell<Option<u16>>,
|
||||
available_height: Cell<Option<u16>>,
|
||||
variant: Cell<Option<crate::glitch_animation::IntroArtSize>>,
|
||||
version_label: String,
|
||||
hidden: Cell<bool>,
|
||||
@@ -20,13 +20,17 @@ impl AnimatedWelcomeCell {
|
||||
completed: Cell::new(false),
|
||||
fade_start: RefCell::new(None),
|
||||
faded_out: Cell::new(false),
|
||||
locked_height: Cell::new(None),
|
||||
available_height: Cell::new(None),
|
||||
variant: Cell::new(None),
|
||||
version_label: format!("v{}", code_version::version()),
|
||||
hidden: Cell::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_available_height(&self, height: u16) {
|
||||
self.available_height.set(Some(height));
|
||||
}
|
||||
|
||||
fn fade_start(&self) -> Option<Instant> {
|
||||
*self.fade_start.borrow()
|
||||
}
|
||||
@@ -71,13 +75,16 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
// Lock height to the largest art that fits the current width so we can
|
||||
// grow back when vertical space returns (instead of permanently
|
||||
// shrinking after a tight layout). Height is recomputed on every
|
||||
// measurement so width changes immediately refresh the locked value.
|
||||
let variant_for_width = crate::glitch_animation::intro_art_size_for_width(width);
|
||||
let h = crate::glitch_animation::intro_art_height(variant_for_width);
|
||||
self.locked_height.set(Some(h));
|
||||
let height_hint = self.available_height.get();
|
||||
let variant = height_hint
|
||||
.map(|height| {
|
||||
crate::glitch_animation::intro_art_size_for_area(
|
||||
width,
|
||||
height.saturating_sub(3),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| crate::glitch_animation::intro_art_size_for_width(width));
|
||||
let h = crate::glitch_animation::intro_art_height(variant);
|
||||
h.saturating_add(3)
|
||||
}
|
||||
|
||||
@@ -86,6 +93,10 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
}
|
||||
|
||||
fn custom_render(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.custom_render_with_skip(area, buf, 0);
|
||||
}
|
||||
|
||||
fn custom_render_with_skip(&self, area: Rect, buf: &mut Buffer, skip_rows: u16) {
|
||||
if self.hidden.get() {
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +113,11 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
}
|
||||
}
|
||||
|
||||
let current_variant = crate::glitch_animation::intro_art_size_for_area(area.width, area.height);
|
||||
let height_hint = self.available_height.get().unwrap_or(area.height);
|
||||
let current_variant = crate::glitch_animation::intro_art_size_for_area(
|
||||
area.width,
|
||||
height_hint.saturating_sub(3),
|
||||
);
|
||||
let previous_variant = self.variant.get();
|
||||
let variant_changed = previous_variant.map_or(false, |v| v != current_variant);
|
||||
|
||||
@@ -117,17 +132,30 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
|
||||
let variant_for_render = current_variant;
|
||||
|
||||
let art_height = crate::glitch_animation::intro_art_height(current_variant).min(area.height);
|
||||
let art_height = crate::glitch_animation::intro_art_height(current_variant);
|
||||
let full_height = art_height.saturating_add(3);
|
||||
// Prefer the logo low in the cell so spare lines sit above it. Keep a
|
||||
// small bottom gap (up to 2 rows) when there's room; otherwise center.
|
||||
let slack = area.height.saturating_sub(art_height);
|
||||
let slack = full_height.saturating_sub(art_height);
|
||||
let bottom_pad = slack.min(2);
|
||||
let top_pad = slack.saturating_sub(bottom_pad);
|
||||
let art_top = top_pad;
|
||||
let art_bottom = art_top.saturating_add(art_height);
|
||||
let vis_top = skip_rows;
|
||||
let vis_bottom = skip_rows.saturating_add(area.height);
|
||||
let intersection_top = art_top.max(vis_top);
|
||||
let intersection_bottom = art_bottom.min(vis_bottom);
|
||||
if intersection_bottom <= intersection_top {
|
||||
return;
|
||||
}
|
||||
let row_offset = intersection_top.saturating_sub(art_top);
|
||||
let y_offset = intersection_top.saturating_sub(vis_top);
|
||||
let visible_height = intersection_bottom.saturating_sub(intersection_top);
|
||||
let positioned_area = Rect {
|
||||
x: area.x,
|
||||
y: area.y.saturating_add(top_pad),
|
||||
y: area.y.saturating_add(y_offset),
|
||||
width: area.width,
|
||||
height: art_height,
|
||||
height: visible_height,
|
||||
};
|
||||
|
||||
let fade_duration = Duration::from_millis(800);
|
||||
@@ -137,13 +165,14 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
if fade_elapsed < fade_duration && !self.faded_out.get() {
|
||||
let fade_progress = fade_elapsed.as_secs_f32() / fade_duration.as_secs_f32();
|
||||
let alpha = 1.0 - fade_progress;
|
||||
crate::glitch_animation::render_intro_animation_with_size_and_alpha(
|
||||
crate::glitch_animation::render_intro_animation_with_size_and_alpha_offset(
|
||||
positioned_area,
|
||||
buf,
|
||||
1.0,
|
||||
alpha,
|
||||
current_variant,
|
||||
&self.version_label,
|
||||
row_offset,
|
||||
);
|
||||
} else {
|
||||
self.faded_out.set(true);
|
||||
@@ -163,12 +192,14 @@ impl HistoryCell for AnimatedWelcomeCell {
|
||||
1.0
|
||||
};
|
||||
|
||||
crate::glitch_animation::render_intro_animation_with_size(
|
||||
crate::glitch_animation::render_intro_animation_with_size_and_alpha_offset(
|
||||
positioned_area,
|
||||
buf,
|
||||
progress,
|
||||
1.0,
|
||||
variant_for_render,
|
||||
&self.version_label,
|
||||
row_offset,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user