use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::Stylize as _,
symbols,
text::{Line, Span},
widgets::{Block, Borders, List, StatefulWidget, Widget},
};
use crate::{
app::App,
model::Pane,
ui::{
is_portrait,
widget::{BgClear, InfoPane, OutputPane, Tab, TabStyle, tabs_line},
},
};
impl Widget for &mut App<'_> {
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
let portrait = is_portrait(area);
let default_style = self.theme.border_inactive_style();
let highlighted_style = self.theme.border_active_style();
let [main, help] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Fill(1), Constraint::Max(1)])
.areas(area);
BgClear::new(self.theme.background_color()).render(area, buf);
let main_direction = if portrait {
Direction::Vertical
} else {
Direction::Horizontal
};
let [params, output] = Layout::default()
.direction(main_direction)
.constraints([Constraint::Min(5), Constraint::Fill(3)])
.areas(main);
{
let style = if matches!(self.current_pane, Pane::Params) {
highlighted_style
} else {
default_style
};
let items = self.params.iter().map(|param| {
if param.enabled {
Line::from(vec![
Span::raw(¶m.name),
Span::raw(": "),
Span::styled(param.describe_value(), self.theme.text_param_color()),
])
} else {
Line::styled(param.describe(), self.theme.text_param_disabled_color())
}
});
let mut block = Block::default()
.borders(Borders::ALL)
.border_set(symbols::border::ROUNDED)
.border_style(style)
.title_top(
Line::from("Params")
.fg(self.theme.pane_title_color())
.left_aligned(),
);
if portrait {
block = block.title_top(
Line::from("effy")
.fg(self.theme.pane_title_color())
.bold()
.centered(),
);
}
StatefulWidget::render(
List::new(items)
.block(block)
.style(highlighted_style)
.highlight_style(self.theme.list_highlight_style()),
params,
buf,
&mut self.params_list_state,
)
};
{
let pane_active = matches!(self.current_pane, Pane::Info | Pane::Output);
let border_style = if pane_active {
highlighted_style
} else {
default_style
};
let info_active = matches!(self.active_out_pane, Pane::Info);
let tabs = [
Tab {
label: "Info",
active: info_active,
},
Tab {
label: "Output",
active: !info_active,
},
];
let tabs_style = TabStyle::from_theme(&self.theme, pane_active);
let mut block = Block::default()
.borders(Borders::ALL)
.border_set(symbols::border::ROUNDED)
.border_style(border_style)
.title_top(tabs_line(&tabs, tabs_style).left_aligned());
if !portrait {
block = block.title_top(
Line::from("effy")
.fg(self.theme.pane_title_color())
.bold()
.centered(),
);
}
if matches!(self.active_out_pane, Pane::Output) {
OutputPane::new(block).render(output, buf, &mut self.out_state);
} else {
InfoPane::new(block).render(output, buf, &mut self.info_state);
}
};
{
let key_style = self.theme.key_style();
let raw_style = self.theme.text_color();
let mut parts = vec![
Span::styled("Tab", key_style),
Span::styled(": switch tab ", raw_style),
Span::styled("s/", key_style),
Span::styled("C", key_style.underlined()),
Span::styled("-s", key_style),
Span::styled(": render ", raw_style),
Span::styled("↑/↓/k/j", key_style),
Span::styled(": navigate ", raw_style),
];
if matches!(self.current_pane, Pane::Params)
&& let Some(selected) = self.params_list_state.selected()
&& let Some(param) = self.params.get(selected)
{
parts.append(&mut vec![
Span::styled("←/→/h/l", key_style),
Span::styled(": toggle parameter ", raw_style),
]);
if param.data.is_editable() {
parts.append(&mut vec![
Span::styled("Enter", key_style),
Span::styled(": edit ", raw_style),
]);
}
}
parts.append(&mut vec![
Span::styled("q/Esc", key_style),
Span::styled(
if self.save_ongoing {
": stop render "
} else {
": quit "
},
raw_style,
),
Span::styled("F1/?", key_style),
Span::styled(": help", raw_style),
]);
Line::from(parts).render(help, buf);
};
}
}