Start on UI, basic WM selector

This commit is contained in:
Gijs Burghoorn 2021-12-28 22:50:03 +01:00
parent 4eccce6d12
commit 25b421f8bb
4 changed files with 1031 additions and 5 deletions

457
Cargo.lock generated Normal file
View file

@ -0,0 +1,457 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "argh"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb41d85d92dfab96cb95ab023c265c5e4261bb956c0fb49ca06d90c570f1958"
dependencies = [
"argh_derive",
"argh_shared",
]
[[package]]
name = "argh_derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be69f70ef5497dd6ab331a50bd95c6ac6b8f7f17a7967838332743fbd58dc3b5"
dependencies = [
"argh_shared",
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "argh_shared"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f8c380fa28aa1b36107cd97f0196474bb7241bb95a453c5c01a15ac74b2eac"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossterm"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
dependencies = [
"bitflags",
"crossterm_winapi 0.8.0",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
dependencies = [
"bitflags",
"crossterm_winapi 0.9.0",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
dependencies = [
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "lemors"
version = "0.1.0"
dependencies = [
"argh",
"bitflags",
"cassowary",
"crossterm 0.22.1",
"rand",
"serde",
"tui",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "lock_api"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "signal-hook"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "syn"
version = "1.0.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "tui"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23"
dependencies = [
"bitflags",
"cassowary",
"crossterm 0.20.0",
"termion",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View file

@ -1,8 +1,15 @@
[package]
name = "lemors"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
edition = "2021"
[dependencies]
tui = { version = "0.16.0", features = [ "crossterm" ] }
bitflags = "1.3"
cassowary = "0.3"
unicode-segmentation = "1.2"
unicode-width = "0.1"
crossterm = { version = "0.22" }
serde = { version = "1", features = ["derive"]}
rand = "0.8"
argh = "0.1"

View file

@ -1,3 +1,200 @@
fn main() {
println!("Hello, world!");
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::{error::Error, io};
use tui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout},
style::{Color, Style},
widgets::{Block, Borders, Paragraph},
Frame, Terminal,
};
use unicode_width::UnicodeWidthStr;
mod window_manager_selector;
use window_manager_selector::{WindowManager, WindowManagerSelectorWidget};
enum InputMode {
WindowManager,
Username,
Password,
Normal,
}
/// App holds the state of the application
struct App {
/// The widget used for selection of the window manager
window_manager_widget: WindowManagerSelectorWidget,
/// Current value of the Username
username: String,
/// Current value of the Password
password: String,
/// Current input mode
input_mode: InputMode,
}
impl Default for App {
fn default() -> App {
App {
window_manager_widget: WindowManagerSelectorWidget::new(vec![
WindowManager::new("bspwm", "sxhkd & ; exec bspwm"),
WindowManager::new("i3", "/usr/bin/i3"),
WindowManager::new("awesome", "/usr/bin/awesome"),
]),
username: String::new(),
password: String::new(),
input_mode: InputMode::Normal,
}
}
}
fn main() -> Result<(), Box<dyn Error>> {
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// create app and run it
let app = App::default();
let res = run_app(&mut terminal, app);
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("{:?}", err)
}
Ok(())
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, &app))?;
if let Event::Key(key) = event::read()? {
match app.input_mode {
InputMode::Normal => match key.code {
KeyCode::Enter => {
app.input_mode = InputMode::WindowManager;
}
KeyCode::Char(x) if x == 'q' => {
return Ok(());
}
_ => {}
},
InputMode::WindowManager => match key.code {
KeyCode::Enter | KeyCode::Tab => {
app.input_mode = InputMode::Username;
}
KeyCode::Esc => {
app.input_mode = InputMode::Normal;
}
KeyCode::Left => {
app.window_manager_widget.left();
}
KeyCode::Right => {
app.window_manager_widget.right();
}
_ => {}
},
InputMode::Username => match key.code {
KeyCode::Enter | KeyCode::Tab => {
app.input_mode = InputMode::Password;
}
KeyCode::Esc => {
app.input_mode = InputMode::Normal;
}
KeyCode::Char(c) => {
app.username.push(c);
}
_ => {}
},
InputMode::Password => match key.code {
KeyCode::Enter | KeyCode::Tab => {
todo!()
}
KeyCode::Esc => {
app.input_mode = InputMode::Normal;
}
KeyCode::Char(c) => {
app.password.push(c);
}
_ => {}
},
}
}
}
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.horizontal_margin(2)
.vertical_margin(1)
.constraints(
[
Constraint::Length(1),
Constraint::Length(2),
Constraint::Length(1),
Constraint::Length(2),
Constraint::Length(3),
Constraint::Length(2),
Constraint::Length(3),
Constraint::Min(0),
]
.as_ref(),
)
.split(f.size());
app.window_manager_widget.render(
f,
chunks[2],
matches!(app.input_mode, InputMode::WindowManager),
);
let username = Paragraph::new(app.username.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::WindowManager => Style::default(),
InputMode::Username => Style::default().fg(Color::Yellow),
InputMode::Password => Style::default(),
})
.block(Block::default().borders(Borders::ALL).title("Input"));
f.render_widget(username, chunks[4]);
let password = Paragraph::new(app.password.as_ref())
.style(match app.input_mode {
InputMode::Normal => Style::default(),
InputMode::WindowManager => Style::default(),
InputMode::Username => Style::default(),
InputMode::Password => Style::default().fg(Color::Yellow),
})
.block(Block::default().borders(Borders::ALL).title("Password"));
f.render_widget(password, chunks[6]);
match app.input_mode {
InputMode::Normal | InputMode::WindowManager => {}
InputMode::Username => f.set_cursor(
chunks[4].x + app.username.width() as u16 + 1,
chunks[4].y + 1,
),
InputMode::Password => f.set_cursor(
chunks[6].x + app.password.width() as u16 + 1,
chunks[6].y + 1,
),
}
}

View file

@ -0,0 +1,365 @@
use tui::{
layout::{Alignment, Rect},
style::{Color, Style},
terminal::Frame,
text::{Span, Spans, Text},
widgets::{Block, Paragraph},
};
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 PREV_NEXT_ARROWS: [&str; 2] = ["<", ">"];
const ARROWS_COLOR: [Color; 2] = [Color::DarkGray, Color::Yellow];
const PREV_NEXT_PADDING: usize = 1;
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 {
title: String,
cmd: String,
}
#[derive(Debug)]
struct WindowManagerSelector {
selected: Option<usize>,
window_managers: Vec<WindowManager>,
}
pub struct WindowManagerSelectorWidget(WindowManagerSelector);
impl WindowManager {
pub fn new(title: impl ToString, cmd: impl ToString) -> WindowManager {
let title = title.to_string();
let cmd = cmd.to_string();
Self { title, cmd }
}
}
impl WindowManagerSelector {
pub fn new(window_managers: Vec<WindowManager>) -> Self {
Self {
selected: if window_managers.len() == 0 {
None
} else {
Some(0)
},
window_managers,
}
}
#[inline]
fn len(&self) -> usize {
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()
}
#[inline]
fn prev_index(&self, index: usize) -> usize {
if index == 0 {
self.len() - 1
} else {
index - 1
}
}
fn go_next(&mut self) {
self.selected = self.selected.map(|index| self.next_index(index));
}
fn go_prev(&mut self) {
self.selected = self.selected.map(|index| self.prev_index(index));
}
fn next(&self) -> Option<&WindowManager> {
self.selected.map_or(None, |index| {
debug_assert!(self.len() > 0);
self.window_managers.get(self.next_index(index))
})
}
fn prev(&self) -> Option<&WindowManager> {
self.selected.map_or(None, |index| {
debug_assert!(self.len() > 0);
self.window_managers.get(self.prev_index(index))
})
}
pub fn current(&self) -> Option<&WindowManager> {
self.selected.map_or(None, |index| {
debug_assert!(self.len() > 0);
self.window_managers.get(index)
})
}
}
impl WindowManagerSelectorWidget {
pub fn new(window_managers: Vec<WindowManager>) -> Self {
Self(WindowManagerSelector::new(window_managers))
}
fn wm_width(window_manager: &WindowManager) -> usize {
// TODO: Take into account not Monospace fonts
window_manager.title.len()
}
fn find_width(prev_width: usize, current_width: usize, next_width: usize) -> usize {
prev_width
+ current_width
+ next_width
+ 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
}
pub fn render(
&self,
frame: &mut Frame<impl tui::backend::Backend>,
area: Rect,
is_focused: bool,
) {
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(
// Left + Right +
// LeftPad + RightPad +
// LeftWM + RightWM +
// LeftWMPad + RightWMPad +
// MiddleWM = 9
9,
);
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
}
}
msg.push(Span::styled(
&current.title,
Style::default().fg(CURRENT_COLOR[is_focused]),
)); // 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
}
}
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(
NO_WINDOW_MANAGERS_STRING,
Style::default().fg(NO_WINDOW_MANAGERS_STRING_COLOR[is_focused]),
));
}
let text = Text::from(Spans::from(msg));
let widget = Paragraph::new(text)
.block(Block::default())
.alignment(Alignment::Center);
frame.render_widget(widget, area);
}
pub fn left(&mut self) {
let Self(ref mut selector) = self;
selector.go_prev();
}
pub fn right(&mut self) {
let Self(ref mut selector) = self;
selector.go_next();
}
}
#[cfg(test)]
mod tests {
use super::*;
mod window_manager_selector {
use super::*;
#[test]
fn empty_creation() {
let mut selector = WindowManagerSelector::new(vec![]);
assert_eq!(selector.current(), None);
selector.go_next();
assert_eq!(selector.current(), None);
selector.go_prev();
assert_eq!(selector.current(), None);
let mut selector = WindowManagerSelector::new(vec![]);
assert_eq!(selector.current(), None);
selector.go_prev();
assert_eq!(selector.current(), None);
selector.go_next();
assert_eq!(selector.current(), None);
}
#[test]
fn single_creation() {
let wm = WindowManager::new("abc", "/abc");
let mut selector = WindowManagerSelector::new(vec![wm.clone()]);
assert_eq!(selector.current(), Some(&wm));
selector.go_next();
assert_eq!(selector.current(), Some(&wm));
selector.go_prev();
assert_eq!(selector.current(), Some(&wm));
let mut selector = WindowManagerSelector::new(vec![wm.clone()]);
assert_eq!(selector.current(), Some(&wm));
selector.go_prev();
assert_eq!(selector.current(), Some(&wm));
selector.go_next();
assert_eq!(selector.current(), Some(&wm));
let mut selector = WindowManagerSelector::new(vec![wm.clone()]);
assert_eq!(selector.current(), Some(&wm));
selector.go_next();
assert_eq!(selector.current(), Some(&wm));
selector.go_next();
assert_eq!(selector.current(), Some(&wm));
let mut selector = WindowManagerSelector::new(vec![wm.clone()]);
assert_eq!(selector.current(), Some(&wm));
selector.go_prev();
assert_eq!(selector.current(), Some(&wm));
selector.go_prev();
assert_eq!(selector.current(), Some(&wm));
}
#[test]
fn multiple_creation() {
let wm1 = WindowManager::new("abc", "/abc");
let wm2 = WindowManager::new("def", "/def");
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_prev();
assert_eq!(selector.current(), Some(&wm1));
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));
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));
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");
let wm4 = WindowManager::new("jkl", "/jkl");
let mut selector = WindowManagerSelector::new(vec![
wm1.clone(),
wm2.clone(),
wm3.clone(),
wm4.clone(),
]);
assert_eq!(selector.current(), Some(&wm1));
selector.go_prev();
assert_eq!(selector.current(), Some(&wm4));
let mut selector = WindowManagerSelector::new(vec![
wm1.clone(),
wm2.clone(),
wm3.clone(),
wm4.clone(),
]);
assert_eq!(selector.current(), Some(&wm1));
selector.go_next();
assert_eq!(selector.current(), Some(&wm2));
selector.go_next();
assert_eq!(selector.current(), Some(&wm3));
selector.go_next();
assert_eq!(selector.current(), Some(&wm4));
selector.go_next();
assert_eq!(selector.current(), Some(&wm1));
}
}
}