use ratatui::{
layout::Position,
style::Modifier,
text::{Line, Span},
};
use super::{InlineChoice, InlineChoiceOption};
use crate::tui::{
composer_chrome::{wrap_footer_parts, SELECTION_MARKER_ACTIVE, SELECTION_MARKER_INACTIVE},
display_width,
panel_text::indented_wrapped_lines,
render::{truncate_to_display_width, wrap_line_at_whitespace},
view_composer::ComposerFrame,
Theme,
};
pub(in crate::tui) fn inline_choice_frame(
choice: &InlineChoice,
width: usize,
return_to_parent: bool,
) -> ComposerFrame {
let width = width.max(1);
let mut lines = indented_wrapped_lines(
&choice.title,
0,
width,
Theme::text().add_modifier(Modifier::BOLD),
);
if !choice.description.is_empty() {
lines.extend(indented_wrapped_lines(
&choice.description,
0,
width,
Theme::dim(),
));
}
lines.push(Line::default());
let mut focused_row = 0;
let mut previous_wrapped = false;
for (index, option) in choice.options.iter().enumerate() {
let group = option_lines(option, index == choice.active, width);
let wrapped = group.len() > 1 + usize::from(!option.detail.is_empty());
if index > 0 && (previous_wrapped || wrapped) {
lines.push(Line::default());
}
if index == choice.active {
focused_row = lines.len();
}
lines.extend(group);
previous_wrapped = wrapped;
}
let shortcuts = choice
.options
.iter()
.filter(|option| option.available)
.map(|option| option.shortcut.to_string())
.collect::<Vec<_>>()
.join("/");
let shortcut_hint = format!("{shortcuts} choose");
lines.push(Line::default());
for part in wrap_footer_parts(
[
"↑↓ move",
"Enter/Space choose",
&shortcut_hint,
if return_to_parent {
"Esc back"
} else {
"Esc cancel"
},
],
width,
) {
lines.extend(indented_wrapped_lines(
&part,
0,
width,
Theme::dim(),
));
}
ComposerFrame::new(
lines,
Position {
x: 0,
y: u16::try_from(focused_row).unwrap_or(u16::MAX),
},
)
}
fn option_lines(option: &InlineChoiceOption, focused: bool, width: usize) -> Vec<Line<'static>> {
let focused = focused && option.available;
let marker = if focused {
SELECTION_MARKER_ACTIVE
} else if option.available {
SELECTION_MARKER_INACTIVE
} else {
"·"
};
let style = if focused {
Theme::text().add_modifier(Modifier::BOLD)
} else if option.available {
Theme::text()
} else {
Theme::dim()
};
let prefix = format!("{marker} {} ", option.shortcut);
let prefix = truncate_to_display_width(&prefix, width.saturating_sub(1));
let indent = display_width(&prefix);
let mut lines = Vec::new();
for (index, part) in wrap_line_at_whitespace(&option.label, width - indent)
.into_iter()
.enumerate()
{
let mut spans = if index == 0 {
let mut chars = prefix.chars();
vec![
Span::styled(
chars.next().map(String::from).unwrap_or_default(),
if focused {
Theme::input_prompt()
} else {
Theme::dim()
},
),
Span::styled(chars.as_str().to_owned(), Theme::dim()),
]
} else {
vec![Span::raw(" ".repeat(indent))]
};
spans.push(Span::styled(part.trim_start().to_owned(), style));
lines.push(Line::from(spans));
}
if !option.detail.is_empty() {
lines.extend(indented_wrapped_lines(
&option.detail,
indent,
width,
Theme::dim(),
));
}
lines
}