main: reimplement text wrapping across lists

This commit is contained in:
2026-06-07 08:27:36 +02:00
parent 7ac5fdbaea
commit 1075103f9e
+70 -21
View File
@@ -115,7 +115,30 @@ impl App {
}
}
fn format_line<'a>(text: &str, prefix: &'a str, prefix_style: Style, max_width: usize) -> Text<'a> {
fn format_line<'a>(entry: &ConversationEntry, max_width: usize) -> Text<'a> {
let prefix = match entry {
ConversationEntry::Eva(_) => "Eva: ",
ConversationEntry::User(_) => "Argee: ",
ConversationEntry::ShipComputer(_) => "Ship Computer: ",
_ => "",
};
let style = match entry {
ConversationEntry::Eva(_) => Style::new().fg(style::Color::Cyan),
ConversationEntry::User(_) => Style::new().fg(style::Color::Magenta),
ConversationEntry::ShipComputer(_) => Style::new().fg(style::Color::Green),
ConversationEntry::StageDirection(_) => Style::new().fg(style::Color::Yellow),
ConversationEntry::SystemMessage(_) => Style::new().fg(style::Color::DarkGray),
};
let text = match entry {
ConversationEntry::Eva(text) => text,
ConversationEntry::ShipComputer(text) => text,
ConversationEntry::StageDirection(text) => text,
ConversationEntry::SystemMessage(text) => text,
ConversationEntry::User(text) => text
};
let avail_width = max_width - prefix.len();
let indent = " ".repeat(prefix.len());
let wrap_options = textwrap::Options::new(avail_width).initial_indent(prefix).subsequent_indent(&indent);
@@ -124,7 +147,7 @@ impl App {
.enumerate()
.map(|(idx, s)| {
if idx == 0 {
Line::from_iter([Span::from(prefix).style(prefix_style), Span::from(s[prefix.len()..].to_string())])
Line::from_iter([Span::from(prefix).style(style), Span::from(s[prefix.len()..].to_string())])
} else {
Line::from(s.to_string())
}
@@ -136,13 +159,7 @@ impl App {
fn draw_conversation(&mut self, frame: &mut Frame, area: Rect) {
let width = area.width.into();
let items: Vec<Text> = self.scene.conversation().iter().rev().map(|entry| {
match entry {
ConversationEntry::User(text) => Self::format_line(text, "Argee: ", Style::new().fg(style::Color::Magenta), width),
ConversationEntry::Eva(text) => Self::format_line(text, "Eva: ", Style::new().fg(style::Color::Cyan), width),
ConversationEntry::ShipComputer(text) => Self::format_line(text, "Ship Computer: ", Style::new().fg(style::Color::Green), width),
ConversationEntry::StageDirection(text) => Self::format_line(text, "", Style::new(), width).style(ratatui::style::Color::Yellow),
ConversationEntry::SystemMessage(text) => Self::format_line(text, "", Style::new(), width).style(ratatui::style::Color::DarkGray)
}
Self::format_line(entry, width)
}).collect();
// TODO: Would be nice to be able to scroll a longer conversation with the scroll wheel, or with page up/down
frame.render_stateful_widget(
@@ -166,12 +183,31 @@ impl App {
.block(borders);
frame.render_widget(list, area);
} else {
let wrap_options = textwrap::Options::new(area.width as usize).subsequent_indent("...");
let options: Vec<Text> = self.scene.reply_options().iter().map(|option| {
let mut contents: Vec<Line> = vec![];
if let Some(direction) = &option.stage_direction {
let padded = format!("({})", direction);
let mut wrapped_direction: Vec<Line> = textwrap::wrap(&padded, wrap_options.clone())
.iter()
.map(|x| { Line::from(x.to_string()).fg(style::Color::Yellow)}).collect();
contents.append(&mut wrapped_direction);
}
let mut text: Vec<Line> = textwrap::wrap(&option.text, wrap_options.clone())
.iter()
.map(|x| { Line::from(x.to_string())}).collect();
contents.append(&mut text);
Text::from_iter(contents)
}).collect();
frame.render_stateful_widget(
List::new(self.scene.reply_options().clone())
List::new(options)
.block(borders)
.style(ratatui::style::Color::White)
.highlight_symbol("> ")
.highlight_style(style::Style::new().bold().fg(style::Color::Cyan)),
.highlight_symbol("> ".fg(Color::Cyan))
.highlight_style(style::Style::new().bold().bg(style::Color::DarkGray))
.repeat_highlight_symbol(true),
area,
&mut self.reply_state
);
@@ -199,24 +235,37 @@ impl App {
fn draw_status(&self, frame: &mut Frame, area: Rect) {
let minutes_remaining = self.direction.time_remaining.num_seconds() / 60;
let time_style = if minutes_remaining == 0 {
Style::new().fg(ratatui::style::Color::Red).bold().rapid_blink()
} else if minutes_remaining <= 5 {
Style::new().fg(ratatui::style::Color::Red).bold().slow_blink()
} else if minutes_remaining <= 10 {
let negative = self.direction.time_remaining.abs() != self.direction.time_remaining;
let time_style = if minutes_remaining <= 0 || negative {
Style::new().fg(ratatui::style::Color::LightRed).bold()
} else if minutes_remaining < 5 {
Style::new().fg(ratatui::style::Color::LightRed).bold()
} else if minutes_remaining < 10 {
ratatui::style::Color::Red.into()
} else if minutes_remaining <= 25 {
} else if minutes_remaining < 25 {
ratatui::style::Color::Yellow.into()
} else if minutes_remaining <= 60 {
} else if minutes_remaining < 60 {
ratatui::style::Color::Green.into()
} else {
ratatui::style::Color::Blue.into()
};
let formatted_time = if negative {
format!("-{:0>2}:{:0>2}:{:0>2}", self.direction.time_remaining.num_hours().abs(), self.direction.time_remaining.num_minutes().abs()% 60, self.direction.time_remaining.num_seconds().abs() % 60)
} else {
format!("{:0>2}:{:0>2}:{:0>2}", self.direction.time_remaining.num_hours(), self.direction.time_remaining.num_minutes() % 60, self.direction.time_remaining.num_seconds() % 60)
};
let status_line = Line::from_iter([
Span::from(format!("Episode {}", self.direction.episode_number)).style(ratatui::style::Color::LightBlue),
Span::from(" | ").style(ratatui::style::Color::DarkGray),
// FIXME: Looks weird with negative numbers, and it doesn't actually blink in the vscode terminal.
Span::from(format!("Time Remaining: {:0>2}:{:0>2}:{:0>2}", self.direction.time_remaining.num_hours(), self.direction.time_remaining.num_minutes() % 60, self.direction.time_remaining.num_seconds() % 60)).style(time_style)
Span::from(format!("{} tracks", self.direction.current_playlist.len())).style(ratatui::style::Color::LightBlue),
Span::from(" | ").style(ratatui::style::Color::DarkGray),
Span::from(format!("Time Remaining: {}", formatted_time)).style(time_style),
Span::from(" | ").style(ratatui::style::Color::DarkGray),
]);
frame.render_widget(status_line, area);
}