diff options
author | Jeff Zhao <jeff.no.zhao@gmail.com> | 2022-05-26 20:46:53 -0400 |
---|---|---|
committer | Jeff Zhao <jeff.no.zhao@gmail.com> | 2022-05-26 20:46:53 -0400 |
commit | 9b48413d2f016479f1c06d4d789ae71bdd1ed0e2 (patch) | |
tree | 9f09928660367f6dc01b5a9209d52ee5b8eb5c69 | |
parent | 34170bbec4a67dfc3c85ccf890ea0189fe2728ae (diff) |
add vsplit view
-rw-r--r-- | config/joshuto.toml | 3 | ||||
-rw-r--r-- | src/config/general/display_raw.rs | 16 | ||||
-rw-r--r-- | src/config/option/display_option.rs | 12 | ||||
-rw-r--r-- | src/ui/views/mod.rs | 2 | ||||
-rw-r--r-- | src/ui/views/tui_folder_view.rs | 6 | ||||
-rw-r--r-- | src/ui/views/tui_view.rs | 13 | ||||
-rw-r--r-- | src/ui/views/tui_vsplit_view.rs | 195 | ||||
-rw-r--r-- | src/ui/widgets/tui_dirlist.rs | 9 | ||||
-rw-r--r-- | src/ui/widgets/tui_dirlist_detailed.rs | 12 |
9 files changed, 258 insertions, 10 deletions
diff --git a/config/joshuto.toml b/config/joshuto.toml index 00d37ff..fad71a9 100644 --- a/config/joshuto.toml +++ b/config/joshuto.toml @@ -4,6 +4,9 @@ use_trash = true watch_files = true [display] +# default, vsplit +mode = "default" + automatically_count_files = false collapse_preview = true # ratios for parent view (optional), current view and preview diff --git a/src/config/general/display_raw.rs b/src/config/general/display_raw.rs index 0d44a97..0ea7a78 100644 --- a/src/config/general/display_raw.rs +++ b/src/config/general/display_raw.rs @@ -3,7 +3,7 @@ use std::convert::From; use serde_derive::Deserialize; use tui::layout::Constraint; -use crate::config::option::{DisplayOption, LineNumberStyle}; +use crate::config::option::{DisplayMode, DisplayOption, LineNumberStyle}; use super::sort_raw::SortOptionRaw; @@ -11,6 +11,10 @@ pub const fn default_column_ratio() -> (usize, usize, usize) { (1, 3, 4) } +fn default_mode() -> String { + "default".to_string() +} + const fn default_true() -> bool { true } @@ -21,6 +25,9 @@ const fn default_scroll_offset() -> usize { #[derive(Clone, Debug, Deserialize)] pub struct DisplayOptionRaw { + #[serde(default = "default_mode")] + pub mode: String, + #[serde(default)] pub automatically_count_files: bool, @@ -55,6 +62,7 @@ pub struct DisplayOptionRaw { impl std::default::Default for DisplayOptionRaw { fn default() -> Self { Self { + mode: default_mode(), automatically_count_files: false, collapse_preview: true, column_ratio: None, @@ -71,6 +79,11 @@ impl std::default::Default for DisplayOptionRaw { impl From<DisplayOptionRaw> for DisplayOption { fn from(raw: DisplayOptionRaw) -> Self { + let mode = match raw.mode.as_str() { + "vsplit" => DisplayMode::VSplit, + _ => DisplayMode::Default, + }; + let column_ratio = match raw.column_ratio { Some(s) if s.len() == 3 => (s[0], s[1], s[2]), Some(s) if s.len() == 2 => (0, s[0], s[1]), @@ -97,6 +110,7 @@ impl From<DisplayOptionRaw> for DisplayOption { }; Self { + _mode: mode, _automatically_count_files: raw.automatically_count_files, _collapse_preview: raw.collapse_preview, _scroll_offset: raw.scroll_offset, diff --git a/src/config/option/display_option.rs b/src/config/option/display_option.rs index 1749ece..c8c4d01 100644 --- a/src/config/option/display_option.rs +++ b/src/config/option/display_option.rs @@ -4,12 +4,19 @@ use tui::layout::Constraint; use crate::config::option::SortOption; +#[derive(Clone, Copy, Debug)] +pub enum DisplayMode { + Default, + VSplit, +} + pub const fn default_column_ratio() -> (usize, usize, usize) { (1, 3, 4) } #[derive(Clone, Debug)] pub struct DisplayOption { + pub _mode: DisplayMode, pub _automatically_count_files: bool, pub _collapse_preview: bool, pub _scroll_offset: usize, @@ -32,6 +39,10 @@ pub enum LineNumberStyle { } impl DisplayOption { + pub fn mode(&self) -> DisplayMode { + self._mode + } + pub fn automatically_count_files(&self) -> bool { self._automatically_count_files } @@ -106,6 +117,7 @@ impl std::default::Default for DisplayOption { ]; Self { + _mode: DisplayMode::Default, _automatically_count_files: false, _collapse_preview: true, column_ratio, diff --git a/src/ui/views/mod.rs b/src/ui/views/mod.rs index 6af230c..645694b 100644 --- a/src/ui/views/mod.rs +++ b/src/ui/views/mod.rs @@ -2,10 +2,12 @@ mod tui_command_menu; mod tui_folder_view; mod tui_textfield; mod tui_view; +mod tui_vsplit_view; mod tui_worker_view; pub use self::tui_command_menu::TuiCommandMenu; pub use self::tui_folder_view::*; pub use self::tui_textfield::TuiTextField; pub use self::tui_view::TuiView; +pub use self::tui_vsplit_view::*; pub use self::tui_worker_view::TuiWorkerView; diff --git a/src/ui/views/tui_folder_view.rs b/src/ui/views/tui_folder_view.rs index 966a48e..986612f 100644 --- a/src/ui/views/tui_folder_view.rs +++ b/src/ui/views/tui_folder_view.rs @@ -106,14 +106,14 @@ impl<'a> Widget for TuiFolderView<'a> { Constraint::Ratio(0, _) => (), _ => { if let Some(list) = curr_tab.parent_list_ref().as_ref() { - TuiDirList::new(list).render(layout_rect[0], buf); + TuiDirList::new(list, true).render(layout_rect[0], buf); } } } // render current view if let Some(list) = curr_list.as_ref() { - TuiDirListDetailed::new(list, display_options).render(layout_rect[1], buf); + TuiDirListDetailed::new(list, display_options, true).render(layout_rect[1], buf); let rect = Rect { x: 0, y: area.height - 1, @@ -141,7 +141,7 @@ impl<'a> Widget for TuiFolderView<'a> { } if let Some(list) = child_list.as_ref() { - TuiDirList::new(list).render(layout_rect[2], buf); + TuiDirList::new(list, true).render(layout_rect[2], buf); } else if curr_entry.is_some() { let preview_area = calculate_preview(self.context, layout_rect[2]); if let Some(preview_area) = preview_area { diff --git a/src/ui/views/tui_view.rs b/src/ui/views/tui_view.rs index 2178475..d50c1a6 100644 --- a/src/ui/views/tui_view.rs +++ b/src/ui/views/tui_view.rs @@ -3,6 +3,8 @@ use tui::layout::Rect; use tui::widgets::Widget; use super::TuiFolderView; +use super::TuiVSplitView; +use crate::config::option::DisplayMode; use crate::context::AppContext; pub struct TuiView<'a> { @@ -21,6 +23,15 @@ impl<'a> TuiView<'a> { impl<'a> Widget for TuiView<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - TuiFolderView::new(self.context).render(area, buf); + let config = self.context.config_ref(); + let display_options = config.display_options_ref(); + match display_options.mode() { + DisplayMode::Default => { + TuiFolderView::new(self.context).render(area, buf); + } + DisplayMode::VSplit => { + TuiVSplitView::new(self.context).render(area, buf); + } + } } } diff --git a/src/ui/views/tui_vsplit_view.rs b/src/ui/views/tui_vsplit_view.rs new file mode 100644 index 0000000..e6b0a7d --- /dev/null +++ b/src/ui/views/tui_vsplit_view.rs @@ -0,0 +1,195 @@ +use tui::buffer::Buffer; +use tui::layout::{Constraint, Direction, Layout, Rect}; +use tui::style::{Color, Style}; +use tui::text::Span; +use tui::widgets::{Block, Borders, Paragraph, Widget, Wrap}; + +use crate::context::AppContext; +use crate::ui::widgets::{TuiDirListDetailed, TuiFooter, TuiTabBar, TuiTopBar}; + +const TAB_VIEW_WIDTH: u16 = 15; + +pub struct TuiVSplitView<'a> { + pub context: &'a AppContext, + pub show_bottom_status: bool, +} + +impl<'a> TuiVSplitView<'a> { + pub fn new(context: &'a AppContext) -> Self { + Self { + context, + show_bottom_status: true, + } + } +} + +impl<'a> Widget for TuiVSplitView<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + let tab_context = self.context.tab_context_ref(); + let tab_index = tab_context.index; + + let config = self.context.config_ref(); + let display_options = config.display_options_ref(); + let constraints = &[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]; + + let layout_rect = if display_options.show_borders() { + let area = Rect { + y: area.top() + 1, + height: area.height - 2, + ..area + }; + + let layout = calculate_layout_with_borders(area, constraints); + + let block = Block::default().borders(Borders::ALL); + let inner = block.inner(area); + block.render(area, buf); + + let layout_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(inner); + + let block = Block::default().borders(Borders::RIGHT); + block.render(layout_rect[0], buf); + + let block = Block::default().borders(Borders::LEFT); + block.render(layout_rect[1], buf); + + layout + } else { + let area = Rect { + y: area.top() + 1, + height: area.height - 2, + ..area + }; + calculate_layout(area, constraints) + }; + + if let Some(curr_tab) = tab_context.tab_ref(tab_index) { + let curr_list = curr_tab.curr_list_ref(); + + let layout_rect = if tab_index % 2 == 0 { + layout_rect[0] + } else { + layout_rect[1] + }; + + // render current view + if let Some(list) = curr_list.as_ref() { + TuiDirListDetailed::new(list, display_options, true).render(layout_rect, buf); + let rect = Rect { + x: 0, + y: area.height - 1, + width: area.width, + height: 1, + }; + + if self.show_bottom_status { + /* draw the bottom status bar */ + if let Some(msg) = self.context.worker_context_ref().get_msg() { + let message_style = Style::default().fg(Color::Yellow); + let text = Span::styled(msg, message_style); + Paragraph::new(text) + .wrap(Wrap { trim: true }) + .render(rect, buf); + } else if let Some(msg) = self.context.message_queue_ref().current_message() { + let text = Span::styled(msg.content.as_str(), msg.style); + Paragraph::new(text) + .wrap(Wrap { trim: true }) + .render(rect, buf); + } else { + TuiFooter::new(list).render(rect, buf); + } + } + } + + let topbar_width = area.width; + let rect = Rect { + x: 0, + y: 0, + width: topbar_width, + height: 1, + }; + TuiTopBar::new(self.context, curr_tab.cwd()).render(rect, buf); + + // render tabs + if self.context.tab_context_ref().len() > 1 { + let topbar_width = area.width.saturating_sub(TAB_VIEW_WIDTH); + + let rect = Rect { + x: topbar_width, + y: 0, + width: TAB_VIEW_WIDTH, + height: 1, + }; + let name = if let Some(ostr) = curr_tab.cwd().file_name() { + ostr.to_str().unwrap_or("") + } else { + "" + }; + TuiTabBar::new( + name, + self.context.tab_context_ref().index, + self.context.tab_context_ref().len(), + ) + .render(rect, buf); + } + } + + let other_tab_index = if tab_index % 2 == 0 { + tab_index + 1 + } else { + tab_index - 1 + }; + + if let Some(curr_tab) = tab_context.tab_ref(other_tab_index) { + let curr_list = curr_tab.curr_list_ref(); + + let layout_rect = if other_tab_index % 2 == 0 { + layout_rect[0] + } else { + layout_rect[1] + }; + + if let Some(list) = curr_list.as_ref() { + TuiDirListDetailed::new(list, display_options, false).render(layout_rect, buf); + } + } + } +} + +fn calculate_layout(area: Rect, constraints: &[Constraint; 2]) -> Vec<Rect> { + let mut layout_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(area); + + layout_rect[0] = Rect { + width: layout_rect[0].width - 1, + ..layout_rect[0] + }; + layout_rect[1] = Rect { + width: layout_rect[1].width - 1, + ..layout_rect[1] + }; + layout_rect +} + +fn calculate_layout_with_borders(area: Rect, constraints: &[Constraint; 2]) -> Vec<Rect> { + let block = Block::default().borders(Borders::ALL); + let inner = block.inner(area); + + let layout_rect = Layout::default() + .direction(Direction::Horizontal) + .constraints(constraints.as_ref()) + .split(inner); + + let block = Block::default().borders(Borders::RIGHT); + let inner1 = block.inner(layout_rect[0]); + + let block = Block::default().borders(Borders::LEFT); + let inner2 = block.inner(layout_rect[1]); + + vec![inner1, inner2] +} diff --git a/src/ui/widgets/tui_dirlist.rs b/src/ui/widgets/tui_dirlist.rs index cffd8e3..54b5727 100644 --- a/src/ui/widgets/tui_dirlist.rs +++ b/src/ui/widgets/tui_dirlist.rs @@ -10,11 +10,12 @@ use crate::util::style; pub struct TuiDirList<'a> { dirlist: &'a JoshutoDirList, + pub focused: bool, } impl<'a> TuiDirList<'a> { - pub fn new(dirlist: &'a JoshutoDirList) -> Self { - Self { dirlist } + pub fn new(dirlist: &'a JoshutoDirList, focused: bool) -> Self { + Self { dirlist, focused } } } @@ -47,7 +48,9 @@ impl<'a> Widget for TuiDirList<'a> { .for_each(|(i, entry)| { let ix = skip_dist + i; - let style = if ix == curr_index { + let style = if !self.focused { + style::entry_style(entry) + } else if ix == curr_index { style::entry_style(entry).add_modifier(Modifier::REVERSED) } else { style::entry_style(entry) diff --git a/src/ui/widgets/tui_dirlist_detailed.rs b/src/ui/widgets/tui_dirlist_detailed.rs index a98f466..85d5c7f 100644 --- a/src/ui/widgets/tui_dirlist_detailed.rs +++ b/src/ui/widgets/tui_dirlist_detailed.rs @@ -19,12 +19,18 @@ const ELLIPSIS: &str = "…"; pub struct TuiDirListDetailed<'a> { dirlist: &'a JoshutoDirList, display_options: &'a DisplayOption, + pub focused: bool, } impl<'a> TuiDirListDetailed<'a> { - pub fn new(dirlist: &'a JoshutoDirList, display_options: &'a DisplayOption) -> Self { + pub fn new( + dirlist: &'a JoshutoDirList, + display_options: &'a DisplayOption, + focused: bool, + ) -> Self { Self { dirlist, display_options, + focused, } } } @@ -67,7 +73,9 @@ impl<'a> Widget for TuiDirListDetailed<'a> { .for_each(|(i, entry)| { let ix = skip_dist + i; - let style = if ix == curr_index { + let style = if !self.focused { + style::entry_style(entry) + } else if ix == curr_index { style::entry_style(entry).add_modifier(Modifier::REVERSED) } else { style::entry_style(entry) |