From 7e896d04bd38b09338ff1ee7398b79a1515176e2 Mon Sep 17 00:00:00 2001 From: Lucas Colombo Date: Fri, 20 Sep 2024 23:11:54 -0300 Subject: [PATCH] =?UTF-8?q?feat(cli):=20=E2=9C=A8=20switch=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 5 ++ Taskfile.yaml | 5 ++ examples/widget_switch.rs | 80 +++++++++++++++++++++ lib/cli/tui/README.md | 9 +++ lib/cli/tui/mod.rs | 5 ++ lib/cli/tui/widgets/gridselector/state.rs | 6 +- lib/cli/tui/widgets/switch/widget.rs | 88 +++++++++++++++++++++++ 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 examples/widget_switch.rs create mode 100644 lib/cli/tui/widgets/switch/widget.rs diff --git a/Cargo.toml b/Cargo.toml index c1edc45..b43eb58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,3 +110,8 @@ required-features = ["cli.tui.widgets", "tokio.rt"] name = "widget_grid_selector" path = "examples/widget_grid_selector.rs" required-features = ["cli.tui.widgets", "tokio.rt"] + +[[example]] +name = "widget_switch" +path = "examples/widget_switch.rs" +required-features = ["cli.tui.widgets", "tokio.rt"] diff --git a/Taskfile.yaml b/Taskfile.yaml index 4460995..c07638f 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -34,6 +34,11 @@ tasks: cmds: - cargo watch --features=cli.tui.widgets,tokio.rt -c -x "run --example widget_grid_selector" + example:switch: + desc: 🚀 run lool «example widget_switch» + cmds: + - cargo watch --features=cli.tui.widgets,tokio.rt -c -x "run --example widget_switch" + fmt: desc: 🎨 format lool cmds: diff --git a/examples/widget_switch.rs b/examples/widget_switch.rs new file mode 100644 index 0000000..f23d5c5 --- /dev/null +++ b/examples/widget_switch.rs @@ -0,0 +1,80 @@ +use { + lool::tui::{ + ratatui::{ + backend::CrosstermBackend, + crossterm::{ + event::{self}, + execute, + terminal::{ + disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, + }, + }, + layout::{Constraint, Layout}, + Terminal, + }, + widgets::{ + switch::Switch, + textarea::{Input, Key}, + }, + }, + ratatui::layout::Flex, + std::io, +}; + +fn main() -> io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + enable_raw_mode()?; + execute!(stdout, EnterAlternateScreen)?; + let backend = CrosstermBackend::new(stdout); + let mut term = Terminal::new(backend)?; + let mut switch_state = false; + + loop { + term.draw(|f| { + // Render the textarea + let switch = + Switch::with_status(switch_state).with_color_on(ratatui::style::Color::Blue); + + let [horiz] = Layout::horizontal([Constraint::Percentage(100)]) + .flex(Flex::Center) + .areas(f.area()); + + let [verti] = Layout::vertical([Constraint::Length(2)]).flex(Flex::Center).areas(horiz); + + let [centered] = + Layout::horizontal([Constraint::Length(14)]).flex(Flex::Center).areas(verti); + + f.render_widget(switch, centered); + })?; + match event::read()?.into() { + Input { key: Key::Esc, .. } + | Input { + key: Key::Char('c'), + shift: false, + ctrl: true, + alt: false, + } => break, + Input { + key: Key::Enter, + ctrl: false, + shift: false, + alt: false, + } + | Input { + key: Key::Char(' '), + .. + } => { + switch_state = !switch_state; + } + _ => {} + } + } + + disable_raw_mode()?; + execute!(term.backend_mut(), LeaveAlternateScreen)?; + term.show_cursor()?; + + Ok(()) +} diff --git a/lib/cli/tui/README.md b/lib/cli/tui/README.md index 99dcdf6..d451981 100644 --- a/lib/cli/tui/README.md +++ b/lib/cli/tui/README.md @@ -244,3 +244,12 @@ As a part of this library, the `Into` trait is implemented for `String The example at [`widget_grid_selector.rs`](/examples/widget_grid_selector.rs) demonstrates how to implement the `Into` trait for a custom type. + +## `Switch` Widget + +A simple stateless switch widget that can be used to show visual feedback of a boolean state. + +### Example + +See the [`widget_switch.rs`](/examples/widget_switch.rs) example for a full demonstration of how to +use the `Switch` widget. \ No newline at end of file diff --git a/lib/cli/tui/mod.rs b/lib/cli/tui/mod.rs index 946db2f..980033a 100644 --- a/lib/cli/tui/mod.rs +++ b/lib/cli/tui/mod.rs @@ -40,6 +40,11 @@ pub mod widgets { } pub mod textarea; + + pub mod switch { + mod widget; + pub use widget::*; + } } // ratatui prelude diff --git a/lib/cli/tui/widgets/gridselector/state.rs b/lib/cli/tui/widgets/gridselector/state.rs index 85cc64b..fea61d6 100644 --- a/lib/cli/tui/widgets/gridselector/state.rs +++ b/lib/cli/tui/widgets/gridselector/state.rs @@ -29,9 +29,9 @@ impl AsRef for GridItem { } // convert Label into String -impl Into for GridItem { - fn into(self) -> String { - self.0.clone() +impl From for String { + fn from(val: GridItem) -> Self { + val.0.clone() } } diff --git a/lib/cli/tui/widgets/switch/widget.rs b/lib/cli/tui/widgets/switch/widget.rs new file mode 100644 index 0000000..9042d63 --- /dev/null +++ b/lib/cli/tui/widgets/switch/widget.rs @@ -0,0 +1,88 @@ +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Layout, Rect}, + style::{Color, Style}, + widgets::{Block, Widget}, +}; + +/// A switch widget +/// +/// This widget is used to show visual confirmation of a boolean state +pub struct Switch { + /// The state of the switch + state: bool, + /// The color of the "on" state (`Green` by default) + color_on: Color, + /// The color of the "off" state (`DarkGray` by default) + color_off: Color, + /// The color of the switch itself (`White` by default) + color_switch: Color, +} + +impl Switch { + pub fn with_status(state: bool) -> Self { + Switch { + state, + color_on: Color::Green, + color_off: Color::DarkGray, + color_switch: Color::White, + } + } + + pub fn with_color_on(mut self, color: Color) -> Self { + self.color_on = color; + self + } + + pub fn with_color_off(mut self, color: Color) -> Self { + self.color_off = color; + self + } + + pub fn with_color_switch(mut self, color: Color) -> Self { + self.color_switch = color; + self + } + + pub fn get_layout(&self, area: Rect) -> (Rect, Rect) { + let main = Layout::default() + .direction(ratatui::layout::Direction::Vertical) + .constraints([Constraint::Length(2)]) + .split(area); + + let layout = Layout::default() + .direction(ratatui::layout::Direction::Horizontal) + .constraints([Constraint::Length(7), Constraint::Length(7)]) + .split(main[0]); + + (layout[0], layout[1]) + } + + fn get_left_color(&self) -> Color { + if self.state { + self.color_on + } else { + self.color_switch + } + } + + fn get_right_color(&self) -> Color { + if self.state { + self.color_switch + } else { + self.color_off + } + } +} + +impl Widget for Switch { + fn render(self, area: Rect, buf: &mut Buffer) { + let (left, right) = self.get_layout(area); + + let left_block = Block::default().style(Style::default().bg(self.get_left_color())); + let right_block = Block::default().style(Style::default().bg(self.get_right_color())); + + left_block.render(left, buf); + right_block.render(right, buf); + } +}