diff --git a/Cargo.lock b/Cargo.lock index b332094..8740c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" dependencies = [ "async-task", "concurrent-queue", diff --git a/src/adapter.rs b/src/adapter.rs index fa82eb3..8f062e6 100644 --- a/src/adapter.rs +++ b/src/adapter.rs @@ -7,13 +7,14 @@ use ratatui::{ Frame, layout::{Alignment, Constraint, Direction, Flex, Layout}, style::{Color, Style, Stylize}, - text::Line, + text::{Line, Span}, widgets::{Block, BorderType, Borders, Cell, Clear, List, Padding, Row, Table, TableState}, }; use tokio::sync::mpsc::UnboundedSender; use crate::{ app::{AppResult, ColorMode, FocusedBlock}, + config::Config, device::Device, event::Event, }; @@ -27,10 +28,15 @@ pub struct Adapter { pub vendor: Option, pub supported_modes: Vec, pub device: Device, + pub config: Arc, } impl Adapter { - pub async fn new(session: Arc, sender: UnboundedSender) -> AppResult { + pub async fn new( + session: Arc, + sender: UnboundedSender, + config: Arc, + ) -> AppResult { let adapter = session.adapter().context("No adapter found")?; let is_powered = adapter.is_powered().await?; @@ -48,6 +54,7 @@ impl Adapter { vendor, supported_modes, device, + config, }) } @@ -82,7 +89,7 @@ impl Adapter { None => false, }; - let (device_block, access_point_block, connected_devices_block) = { + let (device_block, access_point_block, connected_devices_block, help_block) = { let chunks = Layout::default() .direction(Direction::Vertical) .constraints(if any_connected_devices { @@ -90,17 +97,19 @@ impl Adapter { Constraint::Percentage(33), Constraint::Percentage(33), Constraint::Percentage(33), + Constraint::Length(1), ] } else { &[ Constraint::Percentage(50), Constraint::Percentage(50), Constraint::Fill(1), + Constraint::Length(1), ] }) .margin(1) .split(frame.area()); - (chunks[0], chunks[1], chunks[2]) + (chunks[0], chunks[1], chunks[2], chunks[3]) }; // Device @@ -423,6 +432,46 @@ impl Adapter { frame.render_widget(connected_devices_list, connected_devices_block); } + + let help_message = match focused_block { + FocusedBlock::Device => Line::from(vec![ + Span::from(self.config.device.infos.to_string()).bold(), + Span::from(" Infos"), + Span::from(" | "), + Span::from(self.config.device.toggle_power.to_string()).bold(), + Span::from(" Toggle Power"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::AdapterInfos | FocusedBlock::AccessPointInput => Line::from(vec![ + Span::from("󱊷 ").bold(), + Span::from(" Discard"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::AccessPoint => Line::from(vec![ + Span::from(self.config.ap.start.to_string()).bold(), + Span::from(" New AP"), + Span::from(" | "), + Span::from(self.config.ap.stop.to_string()).bold(), + Span::from(" Stop AP"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + _ => Line::from(""), + }; + + let help_message = help_message.centered().blue(); + frame.render_widget(help_message, help_block); } pub fn render_station_mode( @@ -431,7 +480,7 @@ impl Adapter { color_mode: ColorMode, focused_block: FocusedBlock, ) { - let (device_block, station_block, known_networks_block, new_networks_block) = { + let (device_block, station_block, known_networks_block, new_networks_block, help_block) = { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -439,10 +488,11 @@ impl Adapter { Constraint::Length(5), Constraint::Min(5), Constraint::Min(5), + Constraint::Length(1), ]) .margin(1) .split(frame.area()); - (chunks[0], chunks[1], chunks[2], chunks[3]) + (chunks[0], chunks[1], chunks[2], chunks[3], chunks[4]) }; // Device @@ -984,6 +1034,99 @@ impl Adapter { new_networks_block, &mut new_networks_state, ); + + let help_message = match focused_block { + FocusedBlock::Device => Line::from(vec![ + Span::from(self.config.station.start_scanning.to_string()).bold(), + Span::from(" Scan"), + Span::from(" | "), + Span::from(self.config.device.infos.to_string()).bold(), + Span::from(" Infos"), + Span::from(" | "), + Span::from(self.config.device.toggle_power.to_string()).bold(), + Span::from(" Toggle Power"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::Station => Line::from(vec![ + Span::from(self.config.station.start_scanning.to_string()).bold(), + Span::from(" Scan"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::KnownNetworks => Line::from(vec![ + Span::from("k,").bold(), + Span::from(" Up"), + Span::from(" | "), + Span::from("j,").bold(), + Span::from(" Down"), + Span::from(" | "), + Span::from(if self.config.station.toggle_connect == ' ' { + "󱁐 ".to_string() + } else { + self.config.station.toggle_connect.to_string() + }) + .bold(), + Span::from(" Connect/Disconnect"), + Span::from(" | "), + Span::from(self.config.station.known_network.remove.to_string()).bold(), + Span::from(" Remove"), + Span::from(" | "), + Span::from( + self.config + .station + .known_network + .toggle_autoconnect + .to_string(), + ) + .bold(), + Span::from(" Toggle Autoconnect"), + Span::from(" | "), + Span::from("󱊷 ").bold(), + Span::from(" Discard"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::NewNetworks => Line::from(vec![ + Span::from("k,").bold(), + Span::from(" Up"), + Span::from(" | "), + Span::from("j,").bold(), + Span::from(" Down"), + Span::from(" | "), + Span::from("󱁐 ").bold(), + Span::from(" Connect"), + Span::from(" | "), + Span::from("󱊷 ").bold(), + Span::from(" Discard"), + Span::from(" | "), + Span::from("ctrl+r").bold(), + Span::from(" Switch Mode"), + Span::from(" | "), + Span::from("⇄").bold(), + Span::from(" Nav"), + ]), + FocusedBlock::AdapterInfos | FocusedBlock::AuthKey => { + Line::from(vec![Span::from("󱊷 ").bold(), Span::from(" Discard")]) + } + _ => Line::from(""), + }; + + let help_message = help_message.centered().blue(); + + frame.render_widget(help_message, help_block); } pub fn render_adapter(&self, frame: &mut Frame, color_mode: ColorMode) { diff --git a/src/access_point.rs b/src/ap.rs similarity index 100% rename from src/access_point.rs rename to src/ap.rs diff --git a/src/app.rs b/src/app.rs index efef1d7..b752b5d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,7 +18,7 @@ use async_channel::{Receiver, Sender}; use futures::FutureExt; use iwdrs::{agent::Agent, modes::Mode, session::Session}; -use crate::{adapter::Adapter, event::Event, help::Help, notification::Notification}; +use crate::{adapter::Adapter, config::Config, event::Event, notification::Notification}; pub type AppResult = std::result::Result>; @@ -29,7 +29,6 @@ pub enum FocusedBlock { AccessPoint, KnownNetworks, NewNetworks, - Help, AuthKey, AdapterInfos, AccessPointInput, @@ -46,7 +45,6 @@ pub enum ColorMode { pub struct App { pub running: bool, pub focused_block: FocusedBlock, - pub help: Help, pub color_mode: ColorMode, pub notifications: Vec, pub session: Arc, @@ -91,7 +89,11 @@ pub async fn request_confirmation( } impl App { - pub async fn new(help: Help, mode: Mode, sender: UnboundedSender) -> AppResult { + pub async fn new( + config: Arc, + mode: Mode, + sender: UnboundedSender, + ) -> AppResult { let session = { match iwdrs::session::Session::new().await { Ok(session) => Arc::new(session), @@ -102,7 +104,7 @@ impl App { } }; - let adapter = match Adapter::new(session.clone(), sender).await { + let adapter = match Adapter::new(session.clone(), sender, config).await { Ok(v) => v, Err(e) => { eprintln!("{e}"); @@ -144,7 +146,6 @@ impl App { Ok(Self { running: true, focused_block: FocusedBlock::Device, - help, color_mode, notifications: Vec::new(), session, @@ -162,7 +163,11 @@ impl App { }) } - pub async fn reset(mode: Mode, sender: UnboundedSender) -> AppResult<()> { + pub async fn reset( + mode: Mode, + sender: UnboundedSender, + config: Arc, + ) -> AppResult<()> { let session = { match iwdrs::session::Session::new().await { Ok(session) => Arc::new(session), @@ -170,7 +175,7 @@ impl App { } }; - let adapter = match Adapter::new(session.clone(), sender).await { + let adapter = match Adapter::new(session.clone(), sender, config).await { Ok(v) => v, Err(e) => { eprintln!("{e}"); diff --git a/src/device.rs b/src/device.rs index fa6ed6d..f35afdf 100644 --- a/src/device.rs +++ b/src/device.rs @@ -5,8 +5,7 @@ use iwdrs::{device::Device as iwdDevice, modes::Mode, session::Session}; use tokio::sync::mpsc::UnboundedSender; use crate::{ - access_point::AccessPoint, app::AppResult, event::Event, notification::Notification, - station::Station, + ap::AccessPoint, app::AppResult, event::Event, notification::Notification, station::Station, }; #[derive(Debug, Clone)] diff --git a/src/handler.rs b/src/handler.rs index 873e000..fe440b9 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::access_point::APFocusedSection; +use crate::ap::APFocusedSection; use crate::app::{App, AppResult, FocusedBlock}; use crate::config::Config; use crate::event::Event; @@ -119,11 +119,6 @@ pub async fn handle_key_events( } } - // Show help - KeyCode::Char('?') => { - app.focused_block = FocusedBlock::Help; - } - // Switch mode KeyCode::Char(c) if c == config.switch && key_event.modifiers == KeyModifiers::CONTROL => @@ -131,11 +126,9 @@ pub async fn handle_key_events( app.reset_mode = true; } - // Discard help popup + // Discard popup KeyCode::Esc => { - if app.focused_block == FocusedBlock::Help - || app.focused_block == FocusedBlock::AdapterInfos - { + if app.focused_block == FocusedBlock::AdapterInfos { app.focused_block = FocusedBlock::Device; } } @@ -645,9 +638,6 @@ pub async fn handle_key_events( } } - FocusedBlock::Help => { - app.help.scroll_down(); - } _ => {} } } @@ -718,9 +708,6 @@ pub async fn handle_key_events( .select(Some(i)); } } - FocusedBlock::Help => { - app.help.scroll_up(); - } _ => {} }, _ => {} @@ -741,19 +728,6 @@ pub async fn handle_key_events( } } - // Scroll down - KeyCode::Char('j') | KeyCode::Down => { - if app.focused_block == FocusedBlock::Help { - app.help.scroll_down(); - } - } - - KeyCode::Char('k') | KeyCode::Up => { - if app.focused_block == FocusedBlock::Help { - app.help.scroll_up(); - } - } - _ => {} }, _ => {} diff --git a/src/help.rs b/src/help.rs deleted file mode 100644 index e81c0b8..0000000 --- a/src/help.rs +++ /dev/null @@ -1,199 +0,0 @@ -use std::sync::Arc; - -use ratatui::{ - Frame, - layout::{Alignment, Constraint, Direction, Layout, Margin}, - style::{Color, Style, Stylize}, - widgets::{ - Block, BorderType, Borders, Cell, Clear, Padding, Row, Scrollbar, ScrollbarOrientation, - ScrollbarState, Table, TableState, - }, -}; - -use crate::{app::ColorMode, config::Config}; - -#[derive(Debug, Clone)] -pub struct Help { - block_height: usize, - state: TableState, - keys: Vec<(Cell<'static>, &'static str)>, -} - -impl Help { - pub fn new(config: &Arc) -> Self { - let mut state = TableState::new().with_offset(0); - state.select(Some(0)); - - Self { - block_height: 0, - state, - keys: vec![ - ( - Cell::from("## Global").style(Style::new().bold().fg(Color::Yellow)), - "", - ), - (Cell::from("Esc").bold(), "Dismiss different pop-ups"), - ( - Cell::from("Tab or Shift+Tab").bold(), - "Switch between different sections", - ), - (Cell::from("j or Down").bold(), "Scroll down"), - (Cell::from("k or Up").bold(), "Scroll up"), - ( - Cell::from(format!("ctrl {}", config.switch)).bold(), - "Switch adapter mode", - ), - (Cell::from("?").bold(), "Show help"), - (Cell::from("q or ctrl+c").bold(), "Quit"), - (Cell::from(""), ""), - ( - Cell::from("## Device").style(Style::new().bold().fg(Color::Yellow)), - "", - ), - ( - Cell::from(config.device.infos.to_string()).bold(), - "Show device information", - ), - ( - Cell::from(config.device.toggle_power.to_string()).bold(), - "Toggle device power", - ), - (Cell::from(""), ""), - ( - Cell::from("## Station").style(Style::new().bold().fg(Color::Yellow)), - "", - ), - ( - Cell::from(config.station.start_scanning.to_string()).bold(), - "Start scanning", - ), - ( - Cell::from({ - if config.station.toggle_connect == ' ' { - "Space".to_string() - } else { - config.station.toggle_connect.to_string() - } - }) - .bold(), - "Connect/Disconnect the network", - ), - (Cell::from(""), ""), - ( - Cell::from("## Known Networks").style(Style::new().bold().fg(Color::Yellow)), - "", - ), - ( - Cell::from(config.station.known_network.remove.to_string()).bold(), - "Remove the network from the known networks list", - ), - ( - Cell::from(config.station.known_network.toggle_autoconnect.to_string()).bold(), - "Toggle Autoconnect for selected network", - ), - (Cell::from(""), ""), - ( - Cell::from("## Access Point").style(Style::new().bold().fg(Color::Yellow)), - "", - ), - ( - Cell::from(config.ap.start.to_string()).bold(), - "Start a new access point", - ), - ( - Cell::from(config.ap.stop.to_string()).bold(), - "Stop the running access point", - ), - ], - } - } - - pub fn scroll_down(&mut self) { - let i = match self.state.selected() { - Some(i) => { - if i >= self.keys.len().saturating_sub(self.block_height - 6) { - i - } else { - i + 1 - } - } - None => 1, - }; - *self.state.offset_mut() = i; - self.state.select(Some(i)); - } - pub fn scroll_up(&mut self) { - let i = match self.state.selected() { - Some(i) => i.saturating_sub(1), - None => 1, - }; - *self.state.offset_mut() = i; - self.state.select(Some(i)); - } - - pub fn render(&mut self, frame: &mut Frame, color_mode: ColorMode) { - let layout = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Fill(1), - Constraint::Length(20), - Constraint::Fill(1), - ]) - .flex(ratatui::layout::Flex::SpaceBetween) - .split(frame.area()); - - let block = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Fill(1), - Constraint::Length(75), - Constraint::Fill(1), - ]) - .flex(ratatui::layout::Flex::SpaceBetween) - .split(layout[1])[1]; - - self.block_height = block.height as usize; - - let widths = [Constraint::Length(20), Constraint::Fill(1)]; - let rows: Vec = self - .keys - .iter() - .map(|key| { - Row::new(vec![key.0.clone(), key.1.into()]).style(match color_mode { - ColorMode::Dark => Style::default().fg(Color::White), - ColorMode::Light => Style::default().fg(Color::Black), - }) - }) - .collect(); - let rows_len = self.keys.len().saturating_sub(self.block_height - 6); - - let table = Table::new(rows, widths).block( - Block::default() - .padding(Padding::uniform(2)) - .title(" Help ") - .title_style(Style::default().bold().fg(Color::Green)) - .title_alignment(Alignment::Center) - .borders(Borders::ALL) - .style(Style::default()) - .border_type(BorderType::Thick) - .border_style(Style::default().fg(Color::Green)), - ); - - frame.render_widget(Clear, block); - frame.render_stateful_widget(table, block, &mut self.state); - - let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight) - .begin_symbol(Some("↑")) - .end_symbol(Some("↓")); - let mut scrollbar_state = - ScrollbarState::new(rows_len).position(self.state.selected().unwrap_or_default()); - frame.render_stateful_widget( - scrollbar, - block.inner(Margin { - vertical: 1, - horizontal: 0, - }), - &mut scrollbar_state, - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index df946cd..c5d2c0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,8 +12,6 @@ pub mod config; pub mod notification; -pub mod help; - pub mod device; pub mod station; @@ -26,7 +24,7 @@ pub mod auth; pub mod adapter; -pub mod access_point; +pub mod ap; pub mod cli; diff --git a/src/main.rs b/src/main.rs index ffdf583..5102d4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ use impala::app::{App, AppResult}; use impala::config::Config; use impala::event::{Event, EventHandler}; use impala::handler::handle_key_events; -use impala::help::Help; use impala::tui::Tui; use impala::{cli, rfkill}; use iwdrs::modes::Mode; @@ -19,8 +18,6 @@ async fn main() -> AppResult<()> { let config = Arc::new(Config::new()); - let help = Help::new(&config.clone()); - let backend = CrosstermBackend::new(io::stdout()); let terminal = Terminal::new(backend)?; let events = EventHandler::new(2_000); @@ -32,14 +29,14 @@ async fn main() -> AppResult<()> { let mode = Mode::try_from(mode.as_str())?; - if App::reset(mode.clone(), tui.events.sender.clone()) + if App::reset(mode.clone(), tui.events.sender.clone(), config.clone()) .await .is_err() { tui.exit()?; } - let mut app = App::new(help.clone(), mode, tui.events.sender.clone()).await?; + let mut app = App::new(config.clone(), mode, tui.events.sender.clone()).await?; while app.running { tui.draw(&mut app)?; @@ -58,13 +55,13 @@ async fn main() -> AppResult<()> { app.notifications.push(notification); } Event::Reset(mode) => { - if App::reset(mode.clone(), tui.events.sender.clone()) + if App::reset(mode.clone(), tui.events.sender.clone(), config.clone()) .await .is_err() { tui.exit()?; } - app = App::new(help.clone(), mode, tui.events.sender.clone()).await?; + app = App::new(config.clone(), mode, tui.events.sender.clone()).await?; } _ => {} } diff --git a/src/ui.rs b/src/ui.rs index abed8ca..4f7b816 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -32,11 +32,6 @@ pub fn render(app: &mut App, frame: &mut Frame) { ap.render_input(frame); } - // Help - if let FocusedBlock::Help = app.focused_block { - app.help.render(frame, app.color_mode); - } - // Notifications for (index, notification) in app.notifications.iter().enumerate() { notification.render(index, frame);