From c3c3a7fcfce4531660d7071c2b9aa32cfd00d617 Mon Sep 17 00:00:00 2001 From: leglesslamb Date: Sun, 29 Mar 2020 02:54:48 -0400 Subject: [PATCH] Implemented async refresh for terminal resize and battery level/state change. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/display/mod.rs | 63 ++++++++++++++++++------------------ src/main.rs | 79 ++++++++++++++++++++++++++++------------------ 4 files changed, 81 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd9d0fd..a37f99a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" [[package]] name = "cellrs" -version = "0.1.1" +version = "0.1.2" dependencies = [ "battery", "chrono", diff --git a/Cargo.toml b/Cargo.toml index ad8c560..753ede7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cellrs" -version = "0.1.1" +version = "0.1.2" authors = ["leglesslamb "] edition = "2018" description = "A terminal-based battery indicator written in Rust." diff --git a/src/display/mod.rs b/src/display/mod.rs index fb47343..bd8a4ce 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -4,29 +4,35 @@ extern crate termion; use battery::units::ratio::percent; use battery::Battery; use std::io::Write; -use termion::{clear, color, cursor, raw::RawTerminal as Term}; +use termion::{color, cursor, raw::RawTerminal}; // Visual characters for battery. const CELL_CHAR: &str = "|"; const CELL_WALL: &str = "="; +const DIV: u16 = 5; + +pub fn battery_level(batt: &Battery) -> u16 { + batt.state_of_charge().get::().round() as u16 +} + /// Returns battery height/width based on dimensions of the terminal. /// - The sizes used in pattern matching are to some degree arbitrary. /// - Returns (0,0) when the terminal is too thin or short: < (10, 7). /// - This will disallow the next refresh, but allows recovery and refresh /// after next acceptable resize. -fn battery_size() -> (u16, u16) { +pub fn battery_size() -> (u16, u16) { let (term_width, term_height) = termion::terminal_size().unwrap(); /* Round the width down to the next multiple of 5 and subtract the minimum of the last pattern. */ let x = match term_width { 0..=9 => return (0, 0), - 10..=24 => (term_width / 5) * 5 - 5, - 25..=49 => (term_width / 5) * 5 - 10, - 50..=99 => (term_width / 5) * 5 - 25, - _ => (term_width / 5) * 5 - 50, - }; + 10..=24 => (term_width / DIV - 1), + 25..=49 => (term_width / DIV - 2), + 50..=99 => (term_width / DIV - 5), + _ => (term_width / DIV - 10), + } * DIV; // Truncate the height to an appropriate value, to include the stats. let y = match term_height { @@ -40,13 +46,12 @@ fn battery_size() -> (u16, u16) { (x, y) } -/// CHeck if the terminal has been resized. -pub fn check_resize(size: (u16, u16), out: &mut Term) -> bool { - if size == termion::terminal_size().unwrap() { - return false; - } - write!(out, "{}", clear::All).unwrap(); - true +/// Return position of the top-left corner of the battery. +fn battery_top_left() -> (u16, u16) { + let (cent_x, cent_y) = terminal_centre(); + let (size_x, size_y) = battery_size(); + + (cent_x - size_x / 2 + 1, cent_y - size_y / 2) } /// Default red-yellow-green colour theme for the battery cells. @@ -63,24 +68,24 @@ fn cell_colour(x: u8, x_size: u8) -> u8 { /// - The dimensions of the battery scale with the terminal. /// - The status and percentage are also shown. /// - Early-return if the battery size (based on terminal size) is too small. -pub fn display_battery(b: &Battery, out: &mut Term) { - let (b_width, b_height) = match battery_size() { +pub fn display_battery(out: &mut RawTerminal, batt: &Battery) { + let (batt_width, batt_height) = match battery_size() { (0, 0) => return, (bw, bh) => (bw, bh), }; - let perc = b.state_of_charge().get::().round() as u16; - let pos = top_left(); + let perc = battery_level(batt); + let pos = battery_top_left(); // Iterate through the width of the battery. - for x in 0..b_width { + for x in 0..batt_width { // Iterate through the height to print the walls and cells. - for y in 0..b_height { - let (fill, color) = match (y, b_height - y) { + for y in 0..batt_height { + let (fill, color) = match (y, batt_height - y) { (0, _) | (_, 1) => (CELL_WALL, 15), // Skip this cell if it's beyond the battery's percentage. - _ => match 100 * x > perc * b_width { + _ => match 100 * x > perc * batt_width { true => continue, - _ => (CELL_CHAR, cell_colour(x as u8, b_width as u8)), + _ => (CELL_CHAR, cell_colour(x as u8, batt_width as u8)), }, }; @@ -97,9 +102,9 @@ pub fn display_battery(b: &Battery, out: &mut Term) { } // Set the position for the status and percentage line. - let stat_pos = cursor::Goto(pos.0, pos.1 + b_height + 1); + let stat_pos = cursor::Goto(pos.0, pos.1 + batt_height + 1); let white = color::Fg(color::White); - write!(out, "{}{}{}% - {}", stat_pos, white, perc, b.state()).unwrap(); + write!(out, "{}{}{}% - {}", stat_pos, white, perc, batt.state()).unwrap(); out.flush().unwrap(); } @@ -108,11 +113,3 @@ fn terminal_centre() -> (u16, u16) { let (x, y) = termion::terminal_size().unwrap(); (x / 2, y / 2) } - -/// Return position of the top-left corner of the battery. -fn top_left() -> (u16, u16) { - let (cent_x, cent_y) = terminal_centre(); - let (size_x, size_y) = battery_size(); - - (cent_x - size_x / 2, cent_y - size_y / 2) -} diff --git a/src/main.rs b/src/main.rs index 19cacb6..8b38616 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,67 +4,86 @@ extern crate termion; mod display; +use battery::{Manager, State}; use chrono::prelude::*; use std::io::{stdout, Read, Write}; use std::thread; use std::time::Duration; use termion::{async_stdin, clear, cursor, raw::IntoRawMode}; +const ASCII_ESC: u8 = 27; +const ASCII_Q: u8 = 113; const REFRESH: Duration = Duration::from_millis(100); fn main() -> Result<(), battery::Error> { - // The index of the selected battery. + // Battery manager and index of selected battery (default 0). + let manager = Manager::new()?; let index = 0; - // Set up the time/clock format and refresh. - let format = "%H:%M:%S".to_string(); - let clock: &str = format.as_str(); - - // Initialize the IO and size of the terminal. - let mut size = termion::terminal_size().unwrap(); + // Initialize the IO; let mut stdin = async_stdin().bytes(); let mut stdout = stdout().into_raw_mode().unwrap(); + // Initialize the battery registers. + let mut force = false; + let mut level = 101 as u16; + let mut state = State::Unknown; + + // Set up the time/clock format and refresh. + let format = "%H:%M:%S".to_string(); + let clock: &str = format.as_str(); loop { - // Reset display position. - write!(stdout, "\n{}{}\n", cursor::Hide, clear::All).unwrap(); + // Get selectde battery. + let battery = match manager.batteries()?.nth(index) { + None => break, + Some(maybe_batt) => match maybe_batt { + Err(_) => break, + Ok(batt) => batt, + }, + }; - // Display the selected battery. - let manager = battery::Manager::new()?; - for (idx, maybe_batt) in manager.batteries()?.enumerate().next() { - let battery = maybe_batt?; - display::display_battery(&battery, &mut stdout); - if idx >= index { - break; - } + // If the battery has changed level or state, display. + if force { + write!(stdout, "\n{}{}\n", cursor::Hide, clear::All).unwrap(); + display::display_battery(&mut stdout, &battery); + force = false; } // Wait until the next clock cycle, then refresh. - // Refresh early on appropriate user input or terminal resize. - let mut exit = 0; + // Refresh early if terminal size or battery level/state change. + let mut exit = false; let time = Local::now().format(clock).to_string(); - while time == Local::now().format(clock).to_string() { - let ev = stdin.next(); - if let Some(Ok(b)) = ev { + let size = termion::terminal_size().unwrap(); + while !force && Local::now().format(clock).to_string() == time { + // Match user use input to keypress functions. + if let Some(Ok(b)) = stdin.next() { match b { - b'q' => { - exit = 1; + ASCII_ESC | ASCII_Q => { + exit = true; break; } _ => (), } } + thread::sleep(REFRESH); - // Check for a terminal resize, and refresh on resize. - if display::check_resize(size, &mut stdout) { - size = termion::terminal_size().unwrap(); - break; + // Check if the terminal size or battery level/state changed. + // If not, loop and wait for th next clock tick to refresh. + // If so, update the changed value force an refresh. + force = true; + if size != termion::terminal_size().unwrap() { + write!(stdout, "{}", clear::All).unwrap(); + } else if level != display::battery_level(&battery) { + level = display::battery_level(&battery); + } else if state != battery.state() { + state = battery.state(); + } else { + force = false; } - thread::sleep(REFRESH); } // If the refresh resulted from the user quitting, break out of loop. - if exit == 1 { + if exit { break; } }