Skip to content
This repository has been archived by the owner on Jun 18, 2021. It is now read-only.

Commit

Permalink
Implemented async refresh for terminal resize and battery level/state…
Browse files Browse the repository at this point in the history
… change.
  • Loading branch information
rhubarbwu committed Mar 29, 2020
1 parent 70e8d6d commit c3c3a7f
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 65 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cellrs"
version = "0.1.1"
version = "0.1.2"
authors = ["leglesslamb <wu.rupert@outlook.com>"]
edition = "2018"
description = "A terminal-based battery indicator written in Rust."
Expand Down
63 changes: 30 additions & 33 deletions src/display/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<percent>().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 {
Expand All @@ -40,13 +46,12 @@ fn battery_size() -> (u16, u16) {
(x, y)
}

/// CHeck if the terminal has been resized.
pub fn check_resize<W: Write>(size: (u16, u16), out: &mut Term<W>) -> 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.
Expand All @@ -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<W: Write>(b: &Battery, out: &mut Term<W>) {
let (b_width, b_height) = match battery_size() {
pub fn display_battery<W: Write>(out: &mut RawTerminal<W>, batt: &Battery) {
let (batt_width, batt_height) = match battery_size() {
(0, 0) => return,
(bw, bh) => (bw, bh),
};
let perc = b.state_of_charge().get::<percent>().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)),
},
};

Expand All @@ -97,9 +102,9 @@ pub fn display_battery<W: Write>(b: &Battery, out: &mut Term<W>) {
}

// 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();
}

Expand All @@ -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)
}
79 changes: 49 additions & 30 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down

0 comments on commit c3c3a7f

Please sign in to comment.