menus
This commit is contained in:
@@ -11,15 +11,23 @@ use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{
|
||||
Block, Fill, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
|
||||
Widget, Wrap,
|
||||
Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget, Wrap,
|
||||
};
|
||||
use crate::animation::{Animation, AnimationLayer};
|
||||
use crate::input::InputMode;
|
||||
use crate::overlay::{OverlayStyle, render_overlay_frame};
|
||||
|
||||
/// Duration of the open and close animations, in seconds.
|
||||
const ANIM_SECS: f32 = 0.25;
|
||||
|
||||
/// Visual style for scroll overlays: warm amber on dark background.
|
||||
const SCROLL_STYLE: OverlayStyle = OverlayStyle {
|
||||
bg: Color::Rgb(30, 25, 10),
|
||||
border_fg: Color::Rgb(180, 160, 100),
|
||||
width_div: 2,
|
||||
height_fraction: 0.75,
|
||||
};
|
||||
|
||||
/// State threaded through a [`ScrollOverlayWidget`] render call.
|
||||
#[derive(Default)]
|
||||
pub struct ScrollOverlayState {
|
||||
@@ -123,12 +131,11 @@ impl Animation for ScrollCloseAnimation {
|
||||
fn input_mode_after(&self) -> InputMode { InputMode::Board }
|
||||
}
|
||||
|
||||
/// Shared rendering for the scroll overlay at a given animation progress.
|
||||
/// Renders the scroll overlay at a given animation progress using the shared
|
||||
/// overlay frame, then fills the content area with the scroll lines.
|
||||
///
|
||||
/// `progress` is 0.0 (invisible) → 1.0 (fully open); the window height is
|
||||
/// animated proportionally, always kept even so it expands symmetrically.
|
||||
/// When `state` is `Some`, `max_scroll` is written out for scroll-input clamping
|
||||
/// (only needed when fully open and interactive).
|
||||
/// When `state` is `Some`, `max_scroll` is written out so the caller can clamp
|
||||
/// future scroll-input (only needed when fully open and interactive).
|
||||
fn render_scroll_at(
|
||||
lines: &[ScrollLine],
|
||||
progress: f32,
|
||||
@@ -136,31 +143,18 @@ fn render_scroll_at(
|
||||
buf: &mut Buffer,
|
||||
state: Option<&mut ScrollOverlayState>,
|
||||
) {
|
||||
if progress <= 0.0 {
|
||||
let Some((content_area, scrollbar_area)) =
|
||||
render_overlay_frame(&SCROLL_STYLE, progress, area, buf)
|
||||
else {
|
||||
if let Some(s) = state { s.max_scroll = 0; }
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let bg = Color::Rgb(30, 25, 10);
|
||||
let text_style = Style::default().fg(Color::White).bg(bg);
|
||||
let key_style = Style::default().fg(Color::Yellow).bg(bg);
|
||||
let choice_style = Style::default().fg(Color::Rgb(200, 200, 100)).bg(bg);
|
||||
let border_style = Style::default().fg(Color::Rgb(180, 160, 100)).bg(bg);
|
||||
let text_style = Style::default().fg(Color::White).bg(SCROLL_STYLE.bg);
|
||||
let key_style = Style::default().fg(Color::Yellow).bg(SCROLL_STYLE.bg);
|
||||
let choice_style = Style::default().fg(Color::Rgb(200, 200, 100)).bg(SCROLL_STYLE.bg);
|
||||
|
||||
// Fixed proportional sizing. Height is forced even so each animation step
|
||||
// expands the window by exactly 2 rows (one on each side from the center).
|
||||
let outer_w = area.width / 2;
|
||||
let outer_h = (area.height * 3 / 4) & !1;
|
||||
|
||||
// Animate height: keep it even at every intermediate frame.
|
||||
let actual_h = ((outer_h as f32 * progress).round() as u16 & !1).max(2);
|
||||
|
||||
let left = area.x + area.width.saturating_sub(outer_w) / 2;
|
||||
let top = area.y + area.height.saturating_sub(actual_h) / 2;
|
||||
let overlay_rect = Rect { x: left, y: top, width: outer_w, height: actual_h };
|
||||
|
||||
// Build Lines without pre-wrapping — Paragraph handles wrapping at render time.
|
||||
// Lines whose source starts with whitespace are stripped and centered; others are left-aligned.
|
||||
// Build Lines — whitespace-leading lines are centered, others left-aligned.
|
||||
let mut ratatui_lines: Vec<Line> = Vec::new();
|
||||
let mut choice_letter = b'a';
|
||||
for item in lines {
|
||||
@@ -185,27 +179,6 @@ fn render_scroll_at(
|
||||
}
|
||||
}
|
||||
|
||||
// Dim the whole background so board glyphs don't show through.
|
||||
let dim_style = Style::default().fg(Color::Rgb(60, 60, 60)).bg(Color::Black);
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
if let Some(cell) = buf.cell_mut((x, y)) {
|
||||
cell.set_style(dim_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
Fill::new(" ").style(Style::default().bg(bg)).render(overlay_rect, buf);
|
||||
|
||||
let block = Block::bordered().border_style(border_style).style(Style::default().bg(bg));
|
||||
let inner = block.inner(overlay_rect);
|
||||
block.render(overlay_rect, buf);
|
||||
|
||||
let [content_area, scrollbar_area] = Layout::horizontal([
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(1),
|
||||
]).areas(inner);
|
||||
|
||||
// Measure wrapped content height, clamp scroll offset.
|
||||
let offset = state.as_ref().map(|s| s.offset).unwrap_or(0);
|
||||
let para = Paragraph::new(ratatui_lines).wrap(Wrap { trim: false });
|
||||
let content_lines = para.line_count(content_area.width) as u16;
|
||||
|
||||
Reference in New Issue
Block a user