diff --git a/src/alarm.rs b/src/alarm.rs index 9de034f..8777652 100644 --- a/src/alarm.rs +++ b/src/alarm.rs @@ -8,6 +8,10 @@ use crate::layout::{Layout, Position}; use crate::utils::*; use crate::consts::{COLOR, LABEL_SIZE_LIMIT}; +// Delimiter between time and label. Remember to update usage information in +// consts.rs when changing this. +const DELIMITER: char = '/'; + pub struct Countdown { pub value: u32, @@ -24,6 +28,7 @@ impl Countdown { pub fn reset(&mut self) { self.value = 0; + self.position = None; } // Draw countdown. @@ -88,7 +93,7 @@ impl AlarmRoster { let mut label: String; let time_str: &str; - if let Some(i) = input.find('/') { + if let Some(i) = input.find(DELIMITER) { label = input[(i + 1)..].to_string(); // Truncate label. unicode_truncate(&mut label, LABEL_SIZE_LIMIT); @@ -101,15 +106,18 @@ impl AlarmRoster { // Parse input into seconds. if time_str.contains(':') { for sub in time_str.rsplit(':') { - if !sub.is_empty() { - match sub.parse::() { - // Valid. - Ok(d) if d < 60 && index < 3 => time += d * 60u32.pow(index), - // Passes as u32, but does not fit into time range. - Ok(_) => return Err("Could not parse value as time."), - // Could not parse to u32. - Err(_) => return Err("Could not parse value as integer."), - } + match sub.parse::() { + // Valid. + Ok(d) if d < 60 && index < 3 => time += d * 60u32.pow(index), + // Passes as u32, but does not fit into time range. + Ok(_) => return Err("Could not parse value as time."), + // Ignore failure caused by an empty string. + // TODO: Match error kind when stable. See documentation + // for std::num::ParseIntError and + // https://github.com/rust-lang/rust/issues/22639 + Err(_) if sub.is_empty() => (), + // Could not parse to u32. + Err(_) => return Err("Could not parse value as integer."), } index += 1; } @@ -124,7 +132,12 @@ impl AlarmRoster { // Skip if time is out of boundaries. if time == 0 { return Err("Evaluates to zero.") }; if time >= 24 * 60 * 60 { return Err("Values >24h not supported.") }; + // Filter out double entries. + if self.list.iter().any(|a| a.time == time) { + return Err("Already exists."); + } + // Label will never change from now on. label.shrink_to_fit(); let alarm = Alarm { label, @@ -133,21 +146,11 @@ impl AlarmRoster { exceeded: false, }; - // Add to list, insert based on alarm time. Disallow double entries. - let mut i = self.list.len(); - if i == 0 { - self.list.push(alarm); - } else { - while i > 0 { - // Filter out double entries. - if self.list[i - 1].time == time { - return Err("Already exists."); - } else if self.list[i - 1].time < time { - break; - } - i -= 1; - } + // Add to list, insert based on alarm time. + if let Some(i) = self.list.iter().position(|a| a.time > time) { self.list.insert(i, alarm); + } else { + self.list.push(alarm); } Ok(()) } @@ -170,52 +173,47 @@ impl AlarmRoster { ) -> Option<(u32, &String)> { let mut ret = None; - let mut index = 0; let size = self.list.len() as u16; - for alarm in &mut self.list { + for (index, alarm) in self.list.iter_mut().enumerate() // Ignore alarms marked exceeded. - if !alarm.exceeded { - if alarm.time <= clock.elapsed { - // Found alarm to raise. - ret = Some((alarm.time, &alarm.label)); - alarm.exceeded = true; - clock.color_index = Some(alarm.color_index); - countdown.value = 0; - countdown.position = None; - // Skip ahead to the next one. - index += 1; - continue; - } - // Reached the alarm to exceed next. Update countdown - // accordingly. - countdown.value = alarm.time - clock.elapsed; - if countdown.position.is_none() || layout.force_redraw { - // Compute position. - let mut col = - layout.roster.col - + 3 - + unicode_length(&alarm.label); - let mut line = layout.roster.line + index; + .filter(|(_, a)| !a.exceeded) { - // Compensate for "hidden" items in the alarm roster. - // TODO: Make this more elegant and robust. - if let Some(offset) = size.checked_sub(layout.roster_height + 1) { - if index <= offset{ - // Draw next to placeholder ("[...]"). - line = layout.roster.line; - col = layout.roster.col + 6; - } else { - line = line.checked_sub(offset) - .unwrap_or(layout.roster.line); - } - } - countdown.position = Some(Position { col, line, }); - } - // Ignore other alarms. - break; + if alarm.time <= clock.elapsed { + // Found alarm to raise. + ret = Some((alarm.time, &alarm.label)); + alarm.exceeded = true; + clock.color_index = Some(alarm.color_index); + countdown.reset(); + // Skip ahead to the next one. + continue; } - index += 1; + // Reached the alarm to exceed next. Update countdown accordingly. + countdown.value = alarm.time - clock.elapsed; + if countdown.position.is_none() || layout.force_redraw { + // Compute position. + let mut col = + layout.roster.col + + 3 + + unicode_length(&alarm.label); + let mut line = layout.roster.line + index as u16; + + // Compensate for "hidden" items in the alarm roster. + // TODO: Make this more elegant and robust. + if let Some(offset) = size.checked_sub(layout.roster_height + 1) { + if index as u16 <= offset{ + // Draw next to placeholder ("[...]"). + line = layout.roster.line; + col = layout.roster.col + 6; + } else { + line = line.checked_sub(offset) + .unwrap_or(layout.roster.line); + } + } + countdown.position = Some(Position { col, line, }); + } + // Ignore other alarms. + break; } ret // Return value. } @@ -228,16 +226,13 @@ impl AlarmRoster { config: &Config, ) -> Result<(), std::io::Error> { - let mut index = 0; - - // Find first item to print in case we lack space to print them all. - // Final '-1' to take account for the input buffer. - let mut first = 0; + // Find first item to print in case we lack the space to print them + // all. Final '-1' to take account for the input buffer. + let mut offset = 0; if self.list.len() > layout.roster_height as usize { - // Actually -1 (zero indexing) +1 (first line containing "..."). - first = self.list.len() - layout.roster_height as usize; - index += 1; + // Actually -1 (zero indexing) +1 (first line containing "[...]"). + offset = self.list.len() - layout.roster_height as usize; write!(stdout, "{}{}[...]{}", @@ -247,12 +242,19 @@ impl AlarmRoster { )?; } - for alarm in &self.list[first..] { + for (i, alarm) in self.list.iter().skip(offset).enumerate() { + let line = if offset > 0 { + // Add offset of one for "[...]". + layout.roster.line + i as u16 + 1 + } else { + layout.roster.line + i as u16 + }; + match alarm.exceeded { true if config.fancy => { write!(stdout, "{}{}{}{} {} {}🭬{}{}", - cursor::Goto(layout.roster.col, layout.roster.line + index), + cursor::Goto(layout.roster.col, line), color::Fg(COLOR[alarm.color_index]), style::Bold, style::Invert, @@ -265,7 +267,7 @@ impl AlarmRoster { false if config.fancy => { write!(stdout, "{}{}█🭬{}{}", - cursor::Goto(layout.roster.col, layout.roster.line + index), + cursor::Goto(layout.roster.col, line), color::Fg(COLOR[alarm.color_index]), color::Fg(color::Reset), &alarm.label, @@ -274,7 +276,7 @@ impl AlarmRoster { true => { write!(stdout, "{}{}{}{} {} {}{}", - cursor::Goto(layout.roster.col, layout.roster.line + index), + cursor::Goto(layout.roster.col, line), color::Fg(COLOR[alarm.color_index]), style::Bold, style::Invert, @@ -286,14 +288,13 @@ impl AlarmRoster { false => { write!(stdout, "{}{} {} {}", - cursor::Goto(layout.roster.col, layout.roster.line + index), + cursor::Goto(layout.roster.col, line), color::Bg(COLOR[alarm.color_index]), color::Bg(color::Reset), &alarm.label, )?; }, } - index += 1; } Ok(()) } diff --git a/src/clock.rs b/src/clock.rs index 590d70e..6374bc9 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -92,7 +92,7 @@ impl Clock { // Draw clock according to layout. pub fn draw( - &mut self, + &self, mut stdout: &mut RawTerminal, layout: &Layout, ) -> Result<(), std::io::Error> @@ -105,18 +105,21 @@ impl Clock { write!(stdout, "{}", color::Fg(COLOR[c]))?; } - // Draw hours if necessary. + // Run once every hour or on request. if layout.force_redraw || self.elapsed % 3600 == 0 { + // Draw hours if necessary. if self.elapsed >= 3600 { self.draw_digit_pair( &mut stdout, self.elapsed / 3600, - &layout.clock_hr)?; + &layout.clock_hr, + )?; // Draw colon. self.draw_colon( &mut stdout, - &layout.clock_colon1)?; + &layout.clock_colon1, + )?; } // Draw days. @@ -124,7 +127,8 @@ impl Clock { let day_count = format!( "+ {} {}", self.days, - if self.days == 1 { "DAY" } else { "DAYS" }); + if self.days == 1 { "DAY" } else { "DAYS" }, + ); write!(stdout, "{}{:>11}", @@ -132,61 +136,65 @@ impl Clock { layout.clock_days.col, layout.clock_days.line, ), - day_count)?; + day_count, + )?; } } - // Draw minutes if necessary. + // Draw minutes if necessary. Once every minute or on request. if layout.force_redraw || self.elapsed % 60 == 0 { self.draw_digit_pair( &mut stdout, (self.elapsed % 3600) / 60, - &layout.clock_min)?; + &layout.clock_min, + )?; } // Draw colon if necessary. if layout.force_redraw { self.draw_colon( &mut stdout, - &layout.clock_colon0)?; + &layout.clock_colon0, + )?; } // Draw seconds. self.draw_digit_pair( &mut stdout, self.elapsed % 60, - &layout.clock_sec)?; + &layout.clock_sec, + )?; // Reset color and style. if self.paused || self.color_index != None { write!(stdout, "{}{}", style::NoFaint, - color::Fg(color::Reset))?; + color::Fg(color::Reset), + )?; } Ok(()) } fn draw_digit_pair( - &mut self, + &self, stdout: &mut RawTerminal, value: u32, pos: &Position, ) -> Result<(), std::io::Error> { - let left = value / 10; - let right = value % 10; + let left = self.font.digits[value as usize / 10].iter(); + let right = self.font.digits[value as usize % 10].iter(); - for l in 0..self.font.height { + for (i, (left, right)) in left.zip(right).enumerate() { write!(stdout, "{}{} {}", - cursor::Goto(pos.col, pos.line + l), - // First digit. - self.font.digits[left as usize][l as usize], - // Second digit. - self.font.digits[right as usize][l as usize] + cursor::Goto(pos.col, pos.line + i as u16), + left, + right, )?; } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 9e6dc64..2eb2e1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ fn main() { // Parse command line arguments into config and alarm roster. let config = Config::new(args, &mut alarm_roster) .unwrap_or_else(|e| { - println!("{}", e); + eprintln!("{}", e); process::exit(1); });