mirror of
https://github.com/coastalwhite/lemurs
synced 2024-11-26 20:10:17 +00:00
Merge pull request #24 from coastalwhite/wm-selector
Update Window Manager selector
This commit is contained in:
commit
02876807fc
1 changed files with 143 additions and 120 deletions
|
@ -2,7 +2,7 @@ use crossterm::event::KeyCode;
|
|||
use std::path::PathBuf;
|
||||
use tui::{
|
||||
layout::{Alignment, Rect},
|
||||
style::{Color, Style},
|
||||
style::{Color, Modifier, Style},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Paragraph},
|
||||
|
@ -10,7 +10,7 @@ use tui::{
|
|||
|
||||
const NO_WINDOW_MANAGERS_STRING: &str = "No Window Managers Specified";
|
||||
const NO_WINDOW_MANAGERS_STRING_COLOR: [Color; 2] = [Color::LightRed, Color::Red];
|
||||
// const WINDOW_MANAGER_CUTOFF_WIDTH: usize = 16;
|
||||
const WM_CUTOFF_WIDTH: usize = 8;
|
||||
const PREV_NEXT_ARROWS: [&str; 2] = ["<", ">"];
|
||||
const ARROWS_COLOR: [Color; 2] = [Color::DarkGray, Color::Yellow];
|
||||
const PREV_NEXT_PADDING: usize = 1;
|
||||
|
@ -18,12 +18,6 @@ const PREV_NEXT_COLOR: [Color; 2] = [Color::DarkGray, Color::DarkGray];
|
|||
const WM_PADDING: usize = 2;
|
||||
const CURRENT_COLOR: [Color; 2] = [Color::Gray, Color::White];
|
||||
|
||||
// const MIN_WIDTH: usize = WINDOW_MANAGER_CUTOFF_WIDTH
|
||||
// + PREV_NEXT_ARROWS[0].len()
|
||||
// + PREV_NEXT_ARROWS[1].len()
|
||||
// + PREV_NEXT_PADDING * 2
|
||||
// + WM_PADDING * 2;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct WindowManager {
|
||||
pub title: String,
|
||||
|
@ -66,44 +60,57 @@ impl WindowManagerSelector {
|
|||
self.window_managers.len()
|
||||
}
|
||||
|
||||
// #[inline]
|
||||
// fn can_move(&self) -> bool {
|
||||
// self.selected.map_or(false, |s| s == 0)
|
||||
// }
|
||||
|
||||
#[inline]
|
||||
fn next_index(&self, index: usize) -> usize {
|
||||
(index + 1) % self.len()
|
||||
}
|
||||
fn next_index(&self, index: usize) -> Option<usize> {
|
||||
let next_index = index + 1;
|
||||
|
||||
#[inline]
|
||||
fn prev_index(&self, index: usize) -> usize {
|
||||
if index == 0 {
|
||||
self.len() - 1
|
||||
if next_index == self.len() {
|
||||
None
|
||||
} else {
|
||||
index - 1
|
||||
Some(next_index)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn prev_index(&self, index: usize) -> Option<usize> {
|
||||
if index == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(index - 1)
|
||||
}
|
||||
|
||||
fn go_next(&mut self) {
|
||||
self.selected = self.selected.map(|index| self.next_index(index));
|
||||
match self.selected.map(|index| self.next_index(index)) {
|
||||
None | Some(None) => {}
|
||||
Some(val) => self.selected = val,
|
||||
}
|
||||
}
|
||||
|
||||
fn go_prev(&mut self) {
|
||||
self.selected = self.selected.map(|index| self.prev_index(index));
|
||||
match self.selected.map(|index| self.prev_index(index)) {
|
||||
None | Some(None) => {}
|
||||
Some(val) => self.selected = val,
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&self) -> Option<&WindowManager> {
|
||||
self.selected.and_then(|index| {
|
||||
debug_assert!(self.len() > 0);
|
||||
self.window_managers.get(self.next_index(index))
|
||||
match self.next_index(index) {
|
||||
Some(next_index) => self.window_managers.get(next_index),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn prev(&self) -> Option<&WindowManager> {
|
||||
self.selected.and_then(|index| {
|
||||
debug_assert!(self.len() > 0);
|
||||
self.window_managers.get(self.prev_index(index))
|
||||
match self.prev_index(index) {
|
||||
Some(prev_index) => self.window_managers.get(prev_index),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -120,42 +127,13 @@ impl WindowManagerSelectorWidget {
|
|||
Self(WindowManagerSelector::new(window_managers))
|
||||
}
|
||||
|
||||
fn wm_width(window_manager: &WindowManager) -> usize {
|
||||
// TODO: Take into account not Monospace fonts
|
||||
window_manager.title.len()
|
||||
}
|
||||
|
||||
/// Get the character-width of the configuration with three window managers available
|
||||
fn find_width(prev_width: usize, current_width: usize, next_width: usize) -> usize {
|
||||
prev_width
|
||||
+ current_width
|
||||
+ next_width
|
||||
fn do_show_neighbours(area_width: usize) -> bool {
|
||||
WM_CUTOFF_WIDTH * 3
|
||||
+ PREV_NEXT_ARROWS[0].len()
|
||||
+ PREV_NEXT_ARROWS[1].len()
|
||||
+ PREV_NEXT_PADDING * 2
|
||||
+ WM_PADDING * 2
|
||||
}
|
||||
|
||||
fn do_show_prev(
|
||||
area_width: usize,
|
||||
prev_width: usize,
|
||||
current_width: usize,
|
||||
next_width: Option<usize>,
|
||||
) -> bool {
|
||||
debug_assert!(area_width >= Self::find_width(0, current_width, 0));
|
||||
|
||||
Self::find_width(prev_width, current_width, next_width.unwrap_or(0)) <= area_width
|
||||
}
|
||||
|
||||
fn do_show_next(
|
||||
area_width: usize,
|
||||
left_width: Option<usize>,
|
||||
middle_width: usize,
|
||||
right_width: usize,
|
||||
) -> bool {
|
||||
debug_assert!(area_width >= Self::find_width(0, middle_width, 0));
|
||||
|
||||
Self::find_width(left_width.unwrap_or(0), middle_width, right_width) <= area_width
|
||||
<= area_width
|
||||
}
|
||||
|
||||
fn left(&mut self) {
|
||||
|
@ -168,84 +146,129 @@ impl WindowManagerSelectorWidget {
|
|||
selector.go_next();
|
||||
}
|
||||
|
||||
fn cutoff_wm_title_with_padding(title: &str) -> (String, &str, String) {
|
||||
if title.len() >= WM_CUTOFF_WIDTH {
|
||||
return (String::new(), &title[..WM_CUTOFF_WIDTH], String::new());
|
||||
};
|
||||
|
||||
let length_difference = WM_CUTOFF_WIDTH - title.len();
|
||||
let padding = " ".repeat(length_difference / 2);
|
||||
if length_difference % 2 == 0 {
|
||||
(padding.clone(), title, padding)
|
||||
} else {
|
||||
let right_padding = " ".repeat(1 + length_difference / 2);
|
||||
(padding, title, right_padding)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn empty_style(is_focused: bool) -> Style {
|
||||
Style::default().fg(NO_WINDOW_MANAGERS_STRING_COLOR[if is_focused { 1 } else { 0 }])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arrow_style(is_focused: bool) -> Style {
|
||||
Style::default().fg(ARROWS_COLOR[if is_focused { 1 } else { 0 }])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn neighbour_wm_style(is_focused: bool) -> Style {
|
||||
Style::default().fg(PREV_NEXT_COLOR[if is_focused { 1 } else { 0 }])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn current_wm_style(is_focused: bool) -> Style {
|
||||
Style::default()
|
||||
.fg(CURRENT_COLOR[if is_focused { 1 } else { 0 }])
|
||||
.add_modifier(Modifier::UNDERLINED)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_wm_title(
|
||||
items: &mut Vec<Span>,
|
||||
window_manager: &WindowManager,
|
||||
is_focused: bool,
|
||||
is_current: bool,
|
||||
) {
|
||||
// TODO: Maybe if the strings empty, there should be no span generated
|
||||
let (left_padding, title, right_padding) =
|
||||
Self::cutoff_wm_title_with_padding(&window_manager.title);
|
||||
|
||||
let style = if is_current {
|
||||
Self::current_wm_style(is_focused)
|
||||
} else {
|
||||
Self::neighbour_wm_style(is_focused)
|
||||
};
|
||||
|
||||
items.push(Span::raw(left_padding));
|
||||
items.push(Span::styled(title.to_string(), style));
|
||||
items.push(Span::raw(right_padding));
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
frame: &mut Frame<impl tui::backend::Backend>,
|
||||
area: Rect,
|
||||
is_focused: bool,
|
||||
) {
|
||||
// TLDR: This code is a complete mess. Issue #1 should almost completely rewrite this code
|
||||
// so refactoring here doesn't make a lot of sense.
|
||||
|
||||
let Self(selector) = self;
|
||||
|
||||
// TODO: Optimize these calls
|
||||
let current = selector.current();
|
||||
let prev = selector.prev();
|
||||
let next = selector.next();
|
||||
|
||||
let is_focused = if is_focused { 1 } else { 0 };
|
||||
|
||||
let mut msg = Vec::with_capacity(
|
||||
let mut spans = Vec::with_capacity(
|
||||
// Left + Right +
|
||||
// LeftPad + RightPad +
|
||||
// LeftWM + RightWM +
|
||||
// LeftWM(3) + RightWM(3) +
|
||||
// LeftWMPad + RightWMPad +
|
||||
// MiddleWM = 9
|
||||
9,
|
||||
// MiddleWM(3) = 15
|
||||
15,
|
||||
);
|
||||
if let Some(current) = current {
|
||||
msg.push(Span::styled(
|
||||
PREV_NEXT_ARROWS[0],
|
||||
Style::default().fg(ARROWS_COLOR[is_focused]),
|
||||
)); // Left Arrow
|
||||
msg.push(Span::raw(" ".repeat(PREV_NEXT_PADDING))); // LeftPad
|
||||
if let Some(prev) = prev {
|
||||
if Self::do_show_prev(
|
||||
area.width.into(),
|
||||
Self::wm_width(prev),
|
||||
Self::wm_width(current),
|
||||
next.map(|next| Self::wm_width(next)),
|
||||
) {
|
||||
msg.push(Span::styled(
|
||||
&prev.title,
|
||||
Style::default().fg(PREV_NEXT_COLOR[is_focused]),
|
||||
)); // LeftWM
|
||||
msg.push(Span::raw(" ".repeat(WM_PADDING))); // LeftWMPad
|
||||
if let Some(current) = selector.current() {
|
||||
let do_show_neighbours = Self::do_show_neighbours(area.width.into());
|
||||
|
||||
// Showing left item
|
||||
if let Some(prev) = selector.prev() {
|
||||
spans.push(Span::styled(
|
||||
PREV_NEXT_ARROWS[0],
|
||||
Self::arrow_style(is_focused),
|
||||
)); // Left Arrow
|
||||
spans.push(Span::raw(" ".repeat(PREV_NEXT_PADDING))); // LeftPad
|
||||
|
||||
if do_show_neighbours {
|
||||
Self::add_wm_title(&mut spans, prev, is_focused, false); // LeftWM
|
||||
spans.push(Span::raw(" ".repeat(WM_PADDING))); // LeftWMPad
|
||||
}
|
||||
} else {
|
||||
spans.push(Span::raw(" ".repeat(
|
||||
PREV_NEXT_ARROWS[0].len() + PREV_NEXT_PADDING + WM_CUTOFF_WIDTH + WM_PADDING,
|
||||
)));
|
||||
}
|
||||
|
||||
msg.push(Span::styled(
|
||||
¤t.title,
|
||||
Style::default().fg(CURRENT_COLOR[is_focused]),
|
||||
)); // CurrentWM
|
||||
Self::add_wm_title(&mut spans, current, is_focused, true); // CurrentWM
|
||||
|
||||
if let Some(next) = next {
|
||||
if Self::do_show_next(
|
||||
area.width.into(),
|
||||
prev.map(|prev| Self::wm_width(prev)),
|
||||
Self::wm_width(current),
|
||||
Self::wm_width(next),
|
||||
) {
|
||||
msg.push(Span::raw(" ".repeat(WM_PADDING))); // RightWMPad
|
||||
msg.push(Span::styled(
|
||||
&next.title,
|
||||
Style::default().fg(PREV_NEXT_COLOR[is_focused]),
|
||||
)); // RightWM
|
||||
// Showing next item
|
||||
if let Some(next) = selector.next() {
|
||||
if do_show_neighbours {
|
||||
spans.push(Span::raw(" ".repeat(WM_PADDING))); // RightWMPad
|
||||
Self::add_wm_title(&mut spans, next, is_focused, false); // RightWM
|
||||
}
|
||||
|
||||
spans.push(Span::raw(" ".repeat(PREV_NEXT_PADDING))); // RightPad
|
||||
spans.push(Span::styled(
|
||||
PREV_NEXT_ARROWS[1],
|
||||
Self::arrow_style(is_focused),
|
||||
)); // Right Arrow
|
||||
} else {
|
||||
spans.push(Span::raw(" ".repeat(
|
||||
PREV_NEXT_ARROWS[0].len() + PREV_NEXT_PADDING + WM_CUTOFF_WIDTH + WM_PADDING,
|
||||
)));
|
||||
}
|
||||
msg.push(Span::raw(" ".repeat(PREV_NEXT_PADDING))); // RightPad
|
||||
msg.push(Span::styled(
|
||||
PREV_NEXT_ARROWS[1],
|
||||
Style::default().fg(ARROWS_COLOR[is_focused]),
|
||||
)); // Right Arrow
|
||||
} else {
|
||||
msg.push(Span::styled(
|
||||
spans.push(Span::styled(
|
||||
NO_WINDOW_MANAGERS_STRING,
|
||||
Style::default().fg(NO_WINDOW_MANAGERS_STRING_COLOR[is_focused]),
|
||||
Self::empty_style(is_focused),
|
||||
));
|
||||
}
|
||||
let text = Text::from(Spans::from(msg));
|
||||
|
||||
let text = Text::from(Spans::from(spans));
|
||||
let widget = Paragraph::new(text)
|
||||
.block(Block::default())
|
||||
.alignment(Alignment::Center);
|
||||
|
@ -279,6 +302,8 @@ mod tests {
|
|||
use super::*;
|
||||
#[test]
|
||||
fn empty_creation() {
|
||||
// On an empty selector the go_next and go_prev should do nothing.
|
||||
|
||||
let mut selector = WindowManagerSelector::new(vec![]);
|
||||
assert_eq!(selector.current(), None);
|
||||
selector.go_next();
|
||||
|
@ -342,22 +367,20 @@ mod tests {
|
|||
let mut selector = WindowManagerSelector::new(vec![wm1.clone(), wm2.clone()]);
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
selector.go_prev();
|
||||
assert_eq!(selector.current(), Some(&wm2));
|
||||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm2));
|
||||
|
||||
let mut selector = WindowManagerSelector::new(vec![wm1.clone(), wm2.clone()]);
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm2));
|
||||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
assert_eq!(selector.current(), Some(&wm2));
|
||||
|
||||
let mut selector = WindowManagerSelector::new(vec![wm1.clone(), wm2.clone()]);
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
selector.go_prev();
|
||||
assert_eq!(selector.current(), Some(&wm2));
|
||||
selector.go_prev();
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
|
||||
let wm3 = WindowManager::new("ghi", "/ghi".into());
|
||||
|
@ -371,7 +394,7 @@ mod tests {
|
|||
]);
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
selector.go_prev();
|
||||
assert_eq!(selector.current(), Some(&wm4));
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
|
||||
let mut selector = WindowManagerSelector::new(vec![
|
||||
wm1.clone(),
|
||||
|
@ -387,7 +410,7 @@ mod tests {
|
|||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm4));
|
||||
selector.go_next();
|
||||
assert_eq!(selector.current(), Some(&wm1));
|
||||
assert_eq!(selector.current(), Some(&wm4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue