use crate::interactive::{
DisplayOptions,
state::{AppState, Cursor, FocussedPane},
widgets::{
COLOR_MARKED, Entries, EntriesProps, Footer, FooterProps, GlobPane, GlobPaneProps, Header,
HelpPane, HelpPaneProps, MarkPane, MarkPaneProps,
},
};
use Constraint::*;
use FocussedPane::*;
use std::borrow::Borrow;
use tui::buffer::Buffer;
use tui::{
layout::{Constraint, Direction, Layout, Rect},
style::Modifier,
style::{Color, Style},
};
pub struct MainWindowProps<'a> {
pub current_path: String,
pub entries_traversed: u64,
pub total_bytes: u128,
pub start: std::time::Instant,
pub elapsed: Option<std::time::Duration>,
pub display: DisplayOptions,
pub state: &'a AppState,
pub config: &'a dua::Config,
}
#[derive(Default)]
pub struct MainWindow {
pub help_pane: Option<HelpPane>,
pub entries_pane: Entries,
pub mark_pane: Option<MarkPane>,
pub glob_pane: Option<GlobPane>,
}
impl MainWindow {
pub fn render<'a>(
&mut self,
props: impl Borrow<MainWindowProps<'a>>,
area: Rect,
buffer: &mut Buffer,
cursor: &mut Cursor,
) {
let MainWindowProps {
current_path,
entries_traversed,
total_bytes,
start,
elapsed,
display,
state,
config,
} = props.borrow();
let (entries_style, help_style, mark_style, glob_style) = pane_border_style(state.focussed);
let (header_area, content_area, footer_area) = main_window_layout(area);
let header_bg_color = header_background_color(self.is_anything_marked(), state.focussed);
Header.render(header_bg_color, header_area, buffer);
let (entries_area, help_pane, mark_pane) = {
let (left_pane, right_pane) = content_layout(content_area);
match (&mut self.help_pane, &mut self.mark_pane) {
(Some(pane), None) => (left_pane, Some((right_pane, pane)), None),
(None, Some(pane)) => (left_pane, None, Some((right_pane, pane))),
(Some(help), Some(mark)) => {
let (top_area, bottom_area) = right_pane_layout(right_pane);
(left_pane, Some((top_area, help)), Some((bottom_area, mark)))
}
(None, None) => (content_area, None, None),
}
};
let (entries_area, glob_pane) = match &mut self.glob_pane {
Some(glob_pane) => {
let regions = Layout::default()
.direction(Direction::Vertical)
.constraints([Max(256), Length(3)].as_ref())
.split(entries_area);
(regions[0], Some((regions[1], glob_pane)))
}
None => (entries_area, None),
};
if let Some((mark_area, pane)) = mark_pane {
let props = MarkPaneProps {
border_style: mark_style,
format: display.byte_format,
};
pane.render(props, mark_area, buffer);
}
if let Some((help_area, pane)) = help_pane {
let props = HelpPaneProps {
border_style: help_style,
has_focus: matches!(state.focussed, Help),
esc_navigates_back: config.keys.esc_navigates_back,
};
pane.render(props, help_area, buffer);
}
let marked = self.mark_pane.as_ref().map(|p| p.marked());
let props = EntriesProps {
current_path: current_path.clone(),
display: *display,
entries: &state.entries,
marked,
selected: state.navigation().selected,
border_style: entries_style,
is_focussed: matches!(state.focussed, Main),
sort_mode: state.sorting,
show_columns: &state.show_columns,
};
self.entries_pane.render(props, entries_area, buffer);
if let Some((glob_area, pane)) = glob_pane {
let props = GlobPaneProps {
border_style: glob_style,
has_focus: matches!(state.focussed, Glob),
};
pane.render(props, glob_area, buffer, cursor);
}
Footer.render(
FooterProps {
total_bytes: *total_bytes,
format: display.byte_format,
entries_traversed: *entries_traversed,
message: state.message.clone(),
traversal_start: *start,
elapsed: *elapsed,
sort_mode: state.sorting,
pending_exit: state.pending_exit,
esc_navigates_back: config.keys.esc_navigates_back,
},
footer_area,
buffer,
);
}
fn is_anything_marked(&self) -> bool {
self.mark_pane
.as_ref()
.map(|p| p.marked())
.is_none_or(|m| m.is_empty())
}
}
fn right_pane_layout(right_pane: Rect) -> (Rect, Rect) {
let regions = Layout::default()
.direction(Direction::Vertical)
.constraints([Percentage(50), Percentage(50)].as_ref())
.split(right_pane);
(regions[0], regions[1])
}
fn content_layout(content_area: Rect) -> (Rect, Rect) {
let regions = Layout::default()
.direction(Direction::Horizontal)
.constraints([Percentage(50), Percentage(50)].as_ref())
.split(content_area);
(regions[0], regions[1])
}
fn header_background_color(is_marked: bool, focused_pane: FocussedPane) -> Color {
match (is_marked, focused_pane) {
(false, Mark) => Color::LightRed,
(false, _) => COLOR_MARKED,
(_, _) => Color::White,
}
}
fn main_window_layout(area: Rect) -> (Rect, Rect, Rect) {
let regions = Layout::default()
.direction(Direction::Vertical)
.constraints([Length(1), Max(256), Length(1)].as_ref())
.split(area);
(regions[0], regions[1], regions[2])
}
fn pane_border_style(focused_pane: FocussedPane) -> (Style, Style, Style, Style) {
let grey = Style {
fg: Color::DarkGray.into(),
bg: Color::Reset.into(),
add_modifier: Modifier::empty(),
..Style::default()
};
let bold = Style::default().add_modifier(Modifier::BOLD);
match focused_pane {
Main => (bold, grey, grey, grey),
Help => (grey, bold, grey, grey),
Mark => (grey, grey, bold, grey),
Glob => (grey, grey, grey, bold),
}
}