diff --git a/src/alarm.rs b/src/alarm.rs index 3acc57d..87ad731 100644 --- a/src/alarm.rs +++ b/src/alarm.rs @@ -81,7 +81,7 @@ impl AlarmRoster { } // Parse string and add as alarm. - pub fn add(&mut self, input: &String) -> Result<(), &str> { + pub fn add(&mut self, input: &String) -> Result<(), &'static str> { let mut index = 0; let mut time: u32 = 0; let mut label: String; diff --git a/src/buffer.rs b/src/buffer.rs new file mode 100644 index 0000000..8c3efd0 --- /dev/null +++ b/src/buffer.rs @@ -0,0 +1,109 @@ +use std::io::Write; +use termion::{clear, cursor, color}; +use termion::raw::RawTerminal; +use crate::layout::Layout; +use crate::utils; + + +pub struct Buffer { + content: String, + // Used for error messages. + message: Option<&'static str>, + pub altered: bool, +} + +impl Buffer { + pub fn new() -> Buffer { + Buffer { + content: String::new(), + altered: false, + message: None, + } + } + + pub fn read(&mut self) -> &String { + self.altered = false; + &self.content + } + + pub fn push(&mut self, value: char) { + self.altered = true; + self.message = None; + self.content.push(value); + } + + pub fn pop(&mut self) -> Option { + self.altered = true; + self.message = None; + self.content.pop() + } + + pub fn is_empty(&self) -> bool { + self.content.is_empty() + } + + // Clear input. + pub fn clear(&mut self) { + self.altered = true; + self.content.clear(); + } + + // Clear input and message. + pub fn reset(&mut self) { + self.message = None; + self.clear(); + } + + // Draw input buffer. + pub fn draw( + &self, + stdout: &mut RawTerminal, + layout: &mut Layout, + ) -> Result<(), std::io::Error> + { + if let Some(msg) = self.message { + write!(stdout, + "{}{}{}{}{}{}", + cursor::Hide, + cursor::Goto(layout.error.col, layout.error.line), + clear::UntilNewline, + color::Fg(color::LightRed), + &msg, + color::Fg(color::Reset))?; + return Ok(()); + } + + if !self.content.is_empty() { + write!(stdout, + "{}{}Add alarm: {}{}", + cursor::Goto(layout.buffer.col, layout.buffer.line), + clear::CurrentLine, + cursor::Show, + &self.content)?; + layout.cursor.col = + layout.buffer.col + + 11 + + utils::unicode_length(&self.content); + // TODO: This would be a much better alternative, but panics because + // of interference with async_reader. + //layout.cursor.col = stdout.cursor_pos()?.0; + } else { + // Clear buffer display. + write!(stdout, + "{}{}{}", + cursor::Goto(layout.buffer.col, layout.buffer.line), + clear::CurrentLine, + cursor::Hide)?; + } + Ok(()) + } + + // Draw error message at input buffer position. + pub fn message( + &mut self, + msg: &'static str, + ) { + self.message = Some(msg); + self.altered = true; + } +} diff --git a/src/clock.rs b/src/clock.rs index a15ae9a..223a582 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -2,7 +2,8 @@ use std::time; use std::io::Write; use termion::{color, cursor}; use termion::raw::RawTerminal; -use crate::consts::*; +use crate::consts::COLOR; +use crate::consts::digits::*; use crate::layout::{Layout, Position}; diff --git a/src/consts.rs b/src/consts.rs index b04413d..8ba75d4 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,6 +1,20 @@ -pub const NAME: &str = env!("CARGO_PKG_NAME"); -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"), + +pub const COLOR: [&dyn termion::color::Color; 6] = [ + &termion::color::Cyan, + &termion::color::Magenta, + &termion::color::Green, + &termion::color::Yellow, + &termion::color::Blue, + &termion::color::Red, +]; + +// Maximum length of labels. +pub const LABEL_SIZE_LIMIT: usize = 48; + +pub mod ui { + pub const NAME: &str = env!("CARGO_PKG_NAME"); + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + pub const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"), " [-h|-v] [-e|--exec COMMAND] [-p] [-q] [ALARM[/LABEL]] PARAMETERS: @@ -18,171 +32,163 @@ OPTIONS: SIGNALS: Reset clock. Pause or un-pause clock."); -pub const MENUBAR: &str = -"[0-9] Add alarm [d] Delete alarm [SPACE] Pause [r] Reset [c] Clear color [q] Quit"; -pub const MENUBAR_SHORT: &str = -"[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit"; -pub const MENUBAR_INS: &str = -"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit"; + pub const MENUBAR: &str = + "[0-9] Add alarm [d] Delete alarm [SPACE] Pause [r] Reset [c] Clear color [q] Quit"; + pub const MENUBAR_SHORT: &str = + "[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit"; + pub const MENUBAR_INS: &str = + "Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit"; +} -pub const COLOR: [&dyn termion::color::Color; 6] = [ - &termion::color::Cyan, - &termion::color::Magenta, - &termion::color::Green, - &termion::color::Yellow, - &termion::color::Blue, - &termion::color::Red, -]; +pub mod digits { + pub const DIGIT_HEIGHT: u16 = 5; + pub const DIGIT_WIDTH: u16 = 5; + pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [ + [ + // 0 + "█▀▀▀█", + "█ █", + "█ █", + "█ █", + "█▄▄▄█", + ], [ + // 1 + " ▀█ ", + " █ ", + " █ ", + " █ ", + " █ " + ], [ + // 2 + "▀▀▀▀█", + " █", + "█▀▀▀▀", + "█ ", + "█▄▄▄▄" + ], [ + // 3 + "▀▀▀▀█", + " █", + " ▀▀▀█", + " █", + "▄▄▄▄█" + ], [ + // 4 + "█ ", + "█ █ ", + "▀▀▀█▀", + " █ ", + " █ " + ], [ + // 5 + "█▀▀▀▀", + "█ ", + "▀▀▀▀█", + " █", + "▄▄▄▄█" + ], [ + // 6 + "█ ", + "█ ", + "█▀▀▀█", + "█ █", + "█▄▄▄█" + ], [ + // 7 + "▀▀▀▀█", + " █", + " █ ", + " █ ", + " █ ", + ], [ + // 8 + "█▀▀▀█", + "█ █", + "█▀▀▀█", + "█ █", + "█▄▄▄█" + ], [ + // 9 + "█▀▀▀█", + "█ █", + "▀▀▀▀█", + " █", + " █" + ] + ]; -// Maximum length of labels. -pub const LABEL_SIZE_LIMIT: usize = 48; -pub const DIGIT_HEIGHT: u16 = 5; -pub const DIGIT_WIDTH: u16 = 5; -pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [ - [ - // 0 - "█▀▀▀█", - "█ █", - "█ █", - "█ █", - "█▄▄▄█", - ], [ - // 1 - " ▀█ ", - " █ ", - " █ ", - " █ ", - " █ " - ], [ - // 2 - "▀▀▀▀█", - " █", - "█▀▀▀▀", - "█ ", - "█▄▄▄▄" - ], [ - // 3 - "▀▀▀▀█", - " █", - " ▀▀▀█", - " █", - "▄▄▄▄█" - ], [ - // 4 - "█ ", - "█ █ ", - "▀▀▀█▀", - " █ ", - " █ " - ], [ - // 5 - "█▀▀▀▀", - "█ ", - "▀▀▀▀█", - " █", - "▄▄▄▄█" - ], [ - // 6 - "█ ", - "█ ", - "█▀▀▀█", - "█ █", - "█▄▄▄█" - ], [ - // 7 - "▀▀▀▀█", - " █", - " █ ", - " █ ", - " █ ", - ], [ - // 8 - "█▀▀▀█", - "█ █", - "█▀▀▀█", - "█ █", - "█▄▄▄█" - ], [ - // 9 - "█▀▀▀█", - "█ █", - "▀▀▀▀█", - " █", - " █" - ] -]; - -pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [ - [ - // 0 - "█████", - "█ █", - "█ █", - "█ █", - "█████" - ], [ - // 1 - " ██ ", - " █ ", - " █ ", - " █ ", - " █ " - ], [ - // 2 - "█████", - " █", - "█████", - "█ ", - "█████" - ], [ - // 3 - "█████", - " █", - " ████", - " █", - "█████" - ], [ - // 4 - "█ ", - "█ █ ", - "█████", - " █ ", - " █ " - ], [ - // 5 - "█████", - "█ ", - "█████", - " █", - "█████" - ], [ - // 6 - "█ ", - "█ ", - "█████", - "█ █", - "█████" - ], [ - // 7 - "█████", - " █", - " █ ", - " █ ", - " █ " - ], [ - // 8 - "█████", - "█ █", - "█████", - "█ █", - "█████" - ], [ - // 9 - "█████", - "█ █", - "█████", - " █", - " █" - ] -]; + pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [ + [ + // 0 + "█████", + "█ █", + "█ █", + "█ █", + "█████" + ], [ + // 1 + " ██ ", + " █ ", + " █ ", + " █ ", + " █ " + ], [ + // 2 + "█████", + " █", + "█████", + "█ ", + "█████" + ], [ + // 3 + "█████", + " █", + " ████", + " █", + "█████" + ], [ + // 4 + "█ ", + "█ █ ", + "█████", + " █ ", + " █ " + ], [ + // 5 + "█████", + "█ ", + "█████", + " █", + "█████" + ], [ + // 6 + "█ ", + "█ ", + "█████", + "█ █", + "█████" + ], [ + // 7 + "█████", + " █", + " █ ", + " █ ", + " █ " + ], [ + // 8 + "█████", + "█ █", + "█████", + "█ █", + "█████" + ], [ + // 9 + "█████", + "█ █", + "█████", + " █", + " █" + ] + ]; +} diff --git a/src/layout.rs b/src/layout.rs index ae95ba3..13c52e7 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use crate::Config; -use crate::consts::*; +use crate::consts::digits::*; // If screen size falls below these values we skip computation of new // positions. diff --git a/src/lib.rs b/src/lib.rs index 3b9cccd..b2ff1cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ extern crate termion; pub mod alarm; +mod buffer; pub mod clock; pub mod consts; pub mod layout; @@ -12,17 +13,17 @@ use std::io::Write; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use signal_hook::{flag, low_level}; -use termion::{clear, color, cursor, style}; +use termion::{clear, cursor, style}; use termion::raw::{IntoRawMode, RawTerminal}; use termion::event::Key; use termion::input::TermRead; //use termion::cursor::DetectCursorPos; -use utils::*; +use buffer::Buffer; use clock::Clock; use layout::Layout; use alarm::{Countdown, exec_command}; pub use alarm::AlarmRoster; -pub use consts::*; +pub use consts::ui::*; const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize; const SIGWINCH: usize = signal_hook::consts::SIGWINCH as usize; @@ -47,11 +48,9 @@ pub fn run( layout.set_roster_width(alarm_roster.width()); let mut clock = Clock::new(); let mut countdown = Countdown::new(); - let mut buffer = String::new(); + let mut buffer = Buffer::new(); // State variables. - // Request redraw of input buffer. - let mut update_buffer = false; // Request redraw of menu. let mut update_menu = false; // Are we in insert mode? @@ -103,10 +102,9 @@ pub fn run( } // Update input buffer display, if requested. - if update_buffer { - draw_buffer(&mut stdout, &mut layout, &buffer)?; + if buffer.altered { + buffer.draw(&mut stdout, &mut layout)?; stdout.flush()?; - update_buffer = false; } // Update elapsed time. @@ -166,7 +164,7 @@ pub fn run( alarm_roster.draw(&mut stdout, &mut layout); // Redraw buffer. - draw_buffer(&mut stdout, &mut layout, &buffer)?; + buffer.draw(&mut stdout, &mut layout)?; // Schedule menu redraw. update_menu = true; @@ -236,9 +234,9 @@ pub fn run( // Enter. Key::Char('\n') => { if !buffer.is_empty() { - if let Err(e) = alarm_roster.add(&buffer) { + if let Err(e) = alarm_roster.add(buffer.read()) { // Error while processing input buffer. - error_msg(&mut stdout, &layout, e)?; + buffer.message(e); } else { // Input buffer processed without error. layout.set_roster_width(alarm_roster.width()); @@ -251,11 +249,10 @@ pub fn run( }, // Escape ^W, and ^U clear input buffer. Key::Esc | Key::Ctrl('w') | Key::Ctrl('u') => { - buffer.clear(); + buffer.reset(); insert_mode = false; update_menu = true; layout.force_redraw = true; - update_buffer = true; }, // Backspace. Key::Backspace => { @@ -267,12 +264,10 @@ pub fn run( layout.force_redraw = true; } } - update_buffer = true; }, // Forward every char if in insert mode. Key::Char(c) if insert_mode => { buffer.push(c); - update_buffer = true; }, // Reset clock on 'r'. Key::Char('r') => { @@ -320,10 +315,8 @@ pub fn run( insert_mode = true; update_menu = true; layout.force_redraw = true; - update_buffer = true; } else if !buffer.is_empty() && c == ':' { buffer.push(':'); - update_buffer = true; } }, // Any other key. @@ -331,7 +324,7 @@ pub fn run( } } else { // Main loop delay. - thread::sleep(time::Duration::from_millis(200)); + thread::sleep(time::Duration::from_millis(100)); } } @@ -450,52 +443,6 @@ pub fn register_signals( flag::register(SIGWINCH as i32, Arc::clone(&recalc_flag)).unwrap(); } -// Draw input buffer. -fn draw_buffer( - stdout: &mut RawTerminal, - layout: &mut Layout, - buffer: &String, -) -> Result<(), std::io::Error> -{ - if !buffer.is_empty() { - write!(stdout, - "{}{}Add alarm: {}{}", - cursor::Goto(layout.buffer.col, layout.buffer.line), - clear::CurrentLine, - cursor::Show, - buffer)?; - layout.cursor.col = layout.buffer.col + 11 + unicode_length(buffer); - // TODO: This would be a much better alternative, but panics because - // of interference with async_reader. - //layout.cursor.col = stdout.cursor_pos()?.0; - } else { - // Clear buffer display. - write!(stdout, - "{}{}{}", - cursor::Goto(layout.buffer.col, layout.buffer.line), - clear::CurrentLine, - cursor::Hide)?; - } - Ok(()) -} - -// Draw error message at input buffer position. -fn error_msg( - stdout: &mut RawTerminal, - layout: &Layout, - msg: &str -) -> Result<(), std::io::Error> -{ - write!(stdout, - "{}{}{}{}{}", - cursor::Goto(layout.error.col, layout.error.line), - color::Fg(color::LightRed), - msg, - color::Fg(color::Reset), - cursor::Hide)?; - Ok (()) -} - // Prepare to suspend execution. Called on SIGTSTP. fn suspend(mut stdout: &mut RawTerminal) -> Result<(), std::io::Error>