Implemented clock shifting.

This commit is contained in:
shy 2021-04-15 20:06:23 +02:00
parent 84a2a11d79
commit 2a336236e1
5 changed files with 137 additions and 57 deletions

View file

@ -31,6 +31,18 @@ impl Countdown {
self.position = None; self.position = None;
} }
pub fn set(&mut self, value: u32) {
self.value = value;
}
pub fn set_position(&mut self, position: Position) {
self.position = Some(position);
}
pub fn has_position(&self) -> bool {
self.position.is_some()
}
// Draw countdown. // Draw countdown.
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>) pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>)
-> Result<(), std::io::Error> -> Result<(), std::io::Error>
@ -170,6 +182,7 @@ impl AlarmRoster {
clock: &mut Clock, clock: &mut Clock,
layout: &Layout, layout: &Layout,
countdown: &mut Countdown, countdown: &mut Countdown,
force_redraw: bool,
) -> Option<(u32, &String)> ) -> Option<(u32, &String)>
{ {
let mut ret = None; let mut ret = None;
@ -189,8 +202,8 @@ impl AlarmRoster {
continue; continue;
} }
// Reached the alarm to exceed next. Update countdown accordingly. // Reached the alarm to exceed next. Update countdown accordingly.
countdown.value = alarm.time - clock.elapsed; countdown.set(alarm.time - clock.elapsed);
if countdown.position.is_none() || layout.force_redraw { if !countdown.has_position() || force_redraw {
// Compute position. // Compute position.
let mut col = let mut col =
layout.roster.col layout.roster.col
@ -210,7 +223,7 @@ impl AlarmRoster {
.unwrap_or(layout.roster.line); .unwrap_or(layout.roster.line);
} }
} }
countdown.position = Some(Position { col, line, }); countdown.set_position(Position { col, line });
} }
// Ignore other alarms. // Ignore other alarms.
break; break;
@ -317,6 +330,12 @@ impl AlarmRoster {
} }
} }
pub fn time_travel(&mut self, time: u32) {
for alarm in self.list.iter_mut() {
alarm.exceeded = alarm.time <= time;
}
}
// Read alarm times from stdin. // Read alarm times from stdin.
pub fn from_stdin(&mut self, stdin: std::io::Stdin) pub fn from_stdin(&mut self, stdin: std::io::Stdin)
-> Result<(), String> -> Result<(), String>

View file

@ -8,12 +8,18 @@ use crate::consts::COLOR;
use crate::Config; use crate::Config;
use crate::layout::{Layout, Position}; use crate::layout::{Layout, Position};
enum Pause {
Instant(time::Instant),
Secs(u32),
None,
}
pub struct Clock { pub struct Clock {
pub start: time::Instant, pub start: time::Instant,
pub elapsed: u32, pub elapsed: u32,
pub days: u32, pub days: u32,
pub paused: bool, pub paused: bool,
paused_at: Option<time::Instant>, paused_at: Pause,
pub color_index: Option<usize>, pub color_index: Option<usize>,
pub font: &'static font::Font, pub font: &'static font::Font,
} }
@ -25,7 +31,7 @@ impl Clock {
elapsed: 0, elapsed: 0,
days: 0, days: 0,
paused: false, paused: false,
paused_at: None, paused_at: Pause::None,
color_index: None, color_index: None,
font: config.font, font: config.font,
} }
@ -44,19 +50,24 @@ impl Clock {
} }
fn pause(&mut self) { fn pause(&mut self) {
self.paused_at = Some(time::Instant::now()); self.paused_at = Pause::Instant(time::Instant::now());
self.paused = true; self.paused = true;
} }
fn unpause(&mut self) { fn unpause(&mut self) {
// Try to derive a new start instant. match self.paused_at {
if let Some(delay) = self.paused_at { Pause::Instant(delay) => {
if let Some(new_start) = self.start.checked_add(delay.elapsed()) { if let Some(start) = self.start.checked_add(delay.elapsed()) {
self.start = new_start; self.start = start;
} }
},
Pause::Secs(secs) => {
self.start = time::Instant::now() - time::Duration::from_secs(secs as u64);
},
Pause::None => (), // O_o
} }
self.paused_at = None; self.paused_at = Pause::None;
self.paused = false; self.paused = false;
} }
@ -68,6 +79,16 @@ impl Clock {
} }
} }
pub fn shift(&mut self, shift: i32) {
let secs = if shift.is_negative() {
self.elapsed.saturating_sub(shift.abs() as u32)
} else {
self.elapsed.saturating_add(shift as u32)
};
self.paused_at = Pause::Secs(secs);
self.elapsed = secs;
}
pub fn get_width(&self) -> u16 { pub fn get_width(&self) -> u16 {
if self.elapsed >= 3600 { if self.elapsed >= 3600 {
// Hours // Hours
@ -95,6 +116,7 @@ impl Clock {
&self, &self,
mut stdout: &mut RawTerminal<W>, mut stdout: &mut RawTerminal<W>,
layout: &Layout, layout: &Layout,
force_redraw: bool,
) -> Result<(), std::io::Error> ) -> Result<(), std::io::Error>
{ {
// Setup style and color if appropriate. // Setup style and color if appropriate.
@ -106,7 +128,7 @@ impl Clock {
} }
// Run once every hour or on request. // Run once every hour or on request.
if layout.force_redraw || self.elapsed % 3600 == 0 { if force_redraw || self.elapsed % 3600 == 0 {
// Draw hours if necessary. // Draw hours if necessary.
if self.elapsed >= 3600 { if self.elapsed >= 3600 {
self.draw_digit_pair( self.draw_digit_pair(
@ -142,7 +164,7 @@ impl Clock {
} }
// Draw minutes if necessary. Once every minute or on request. // Draw minutes if necessary. Once every minute or on request.
if layout.force_redraw || self.elapsed % 60 == 0 { if force_redraw || self.elapsed % 60 == 0 {
self.draw_digit_pair( self.draw_digit_pair(
&mut stdout, &mut stdout,
(self.elapsed % 3600) / 60, (self.elapsed % 3600) / 60,
@ -151,7 +173,7 @@ impl Clock {
} }
// Draw colon if necessary. // Draw colon if necessary.
if layout.force_redraw { if force_redraw {
self.draw_colon( self.draw_colon(
&mut stdout, &mut stdout,
&layout.clock_colon0, &layout.clock_colon0,

View file

@ -39,5 +39,7 @@ SIGNALS: <SIGUSR1> Reset clock.
"[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit"; "[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit";
pub const MENUBAR_INS: &str = pub const MENUBAR_INS: &str =
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit"; "Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
pub const MENUBAR_PAUSED: &str =
"[SPACE] Continue [r] Reset [UP]/[DOWN] Set clock";
} }

View file

@ -7,7 +7,7 @@ pub struct Position {
pub struct Layout { pub struct Layout {
pub force_redraw: bool, // Redraw elements on screen. pub force_redraw: bool, // Redraw elements on screen.
pub force_recalc: bool, // Recalculate position of elements. force_recalc: bool, // Recalculate position of elements.
pub width: u16, pub width: u16,
pub height: u16, pub height: u16,
clock_width: u16, clock_width: u16,
@ -48,8 +48,9 @@ impl Layout {
} }
} }
// Update layout. Returns true when changes were made.
pub fn update(&mut self, clock: &Clock, force: bool) pub fn update(&mut self, clock: &Clock, force: bool)
-> Result<(), std::io::Error> -> Result<bool, std::io::Error>
{ {
if self.force_recalc || force { if self.force_recalc || force {
self.force_recalc = false; self.force_recalc = false;
@ -60,9 +61,14 @@ impl Layout {
self.clock_height = clock.font.height; self.clock_height = clock.font.height;
self.digit_width = clock.font.width; self.digit_width = clock.font.width;
self.compute(clock.elapsed >= 3600); self.compute(clock.elapsed >= 3600);
self.force_redraw = true; Ok(true)
} else {
Ok(false)
} }
Ok(()) }
pub fn schedule_recalc(&mut self) {
self.force_recalc = true;
} }
#[cfg(test)] #[cfg(test)]

View file

@ -43,6 +43,7 @@ pub fn run(
let mut input_keys = async_stdin.keys(); let mut input_keys = async_stdin.keys();
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut stdout = stdout.lock().into_raw_mode()?; let mut stdout = stdout.lock().into_raw_mode()?;
let mut force_redraw = true;
// Register signals. // Register signals.
let mut signals = Signals::new(&[ let mut signals = Signals::new(&[
@ -65,22 +66,22 @@ pub fn run(
// Continuing after SIGTSTP or SIGSTOP. // Continuing after SIGTSTP or SIGSTOP.
SIGCONT => { SIGCONT => {
restore_after_suspend(&mut stdout)?; restore_after_suspend(&mut stdout)?;
layout.force_redraw = true; force_redraw = true;
}, },
SIGWINCH => layout.force_recalc = true, SIGWINCH => layout.schedule_recalc(),
// Exit main loop on SIGTERM and SIGINT. // Exit main loop on SIGTERM and SIGINT.
SIGTERM | SIGINT => break 'outer, SIGTERM | SIGINT => break 'outer,
// Reset clock on SIGUSR1. // Reset clock on SIGUSR1.
SIGUSR1 => { SIGUSR1 => {
clock.reset(); clock.reset();
alarm_roster.reset_all(); alarm_roster.reset_all();
layout.force_recalc = true; layout.schedule_recalc();
layout.force_redraw = true; force_redraw = true;
}, },
// (Un-)Pause clock on SIGUSR2. // (Un-)Pause clock on SIGUSR2.
SIGUSR2 => { SIGUSR2 => {
clock.toggle(); clock.toggle();
layout.force_redraw = true; force_redraw = true;
}, },
// We didn't register anything else. // We didn't register anything else.
_ => unreachable!(), _ => unreachable!(),
@ -98,7 +99,7 @@ pub fn run(
// Conditional inner loop. Runs once every second or when explicitly // Conditional inner loop. Runs once every second or when explicitly
// requested. // requested.
if elapsed != clock.elapsed || layout.force_redraw { if elapsed != clock.elapsed || force_redraw {
// Update clock. Advance one day after 24 hours. // Update clock. Advance one day after 24 hours.
if elapsed < 24 * 60 * 60 { if elapsed < 24 * 60 * 60 {
clock.elapsed = elapsed; clock.elapsed = elapsed;
@ -106,19 +107,31 @@ pub fn run(
clock.next_day(); clock.next_day();
// "clock.elapsed" set by "clock.next_day()". // "clock.elapsed" set by "clock.next_day()".
alarm_roster.reset_all(); alarm_roster.reset_all();
layout.force_recalc = true; layout.schedule_recalc();
} }
// Update window size information and calculate the clock position. // Update window size information and calculate the clock position.
// Also enforce recalculation of layout if we start displaying // Also enforce recalculation of layout if we start displaying
// hours. // hours.
layout.update(&clock, clock.elapsed == 3600)?; match layout.update(&clock, clock.elapsed == 3600) {
Ok(true) => force_redraw = true,
Ok(false) => (),
Err(e) => return Err(e),
}
// Check for exceeded alarms. // Check for exceeded alarms.
if let Some((time, label)) = alarm_roster.check(&mut clock, &layout, &mut countdown) { if let Some((time, label)) = alarm_roster.check(
&mut clock,
&layout,
&mut countdown,
force_redraw)
{
// Do not react to exceeded alarms if the clock is paused.
if !clock.paused {
force_redraw = true;
// Write ASCII bell code. // Write ASCII bell code.
write!(stdout, "{}", 0x07 as char)?; write!(stdout, "{}", 0x07 as char)?;
layout.force_redraw = true;
match config.command { match config.command {
// Run command if configured and no command is running. // Run command if configured and no command is running.
@ -132,10 +145,11 @@ pub fn run(
// Quit if configured. // Quit if configured.
if config.quit && alarm_roster.idle() { break }; if config.quit && alarm_roster.idle() { break };
} }
}
// Clear the window and redraw menu bar, alarm roster and buffer if // Clear the window and redraw menu bar, alarm roster and buffer if
// requested. // requested.
if layout.force_redraw { if force_redraw {
// Write menu at the top. // Write menu at the top.
write!(stdout, write!(stdout,
"{}{}{}{}{}", "{}{}{}{}{}",
@ -144,6 +158,7 @@ pub fn run(
// Switch menu bars. Use a compressed version or none at // Switch menu bars. Use a compressed version or none at
// all if necessary. // all if necessary.
match buffer.visible { match buffer.visible {
_ if clock.paused && layout.can_hold(MENUBAR_PAUSED) => MENUBAR_PAUSED,
true if layout.can_hold(MENUBAR_INS) => MENUBAR_INS, true if layout.can_hold(MENUBAR_INS) => MENUBAR_INS,
false if layout.can_hold(MENUBAR) => MENUBAR, false if layout.can_hold(MENUBAR) => MENUBAR,
false if layout.can_hold(MENUBAR_SHORT) => MENUBAR_SHORT, false if layout.can_hold(MENUBAR_SHORT) => MENUBAR_SHORT,
@ -162,7 +177,7 @@ pub fn run(
buffer.draw(&mut stdout, &mut layout)?; buffer.draw(&mut stdout, &mut layout)?;
} }
clock.draw(&mut stdout, &layout)?; clock.draw(&mut stdout, &layout, force_redraw)?;
// Display countdown. // Display countdown.
if countdown.value > 0 { if countdown.value > 0 {
@ -191,7 +206,7 @@ pub fn run(
// End of conditional inner loop. // End of conditional inner loop.
// Reset redraw_all and flush stdout. // Reset redraw_all and flush stdout.
layout.force_redraw = false; force_redraw = false;
stdout.flush()?; stdout.flush()?;
} }
@ -216,21 +231,21 @@ pub fn run(
} }
buffer.clear(); buffer.clear();
buffer.visible = false; buffer.visible = false;
layout.force_redraw = true; force_redraw = true;
} }
}, },
// Escape and ^U clear input buffer. // Escape and ^U clear input buffer.
Key::Esc | Key::Ctrl('u') => { Key::Esc | Key::Ctrl('u') => {
buffer.reset(); buffer.reset();
buffer.visible = false; buffer.visible = false;
layout.force_redraw = true; force_redraw = true;
}, },
// ^W removes last word. // ^W removes last word.
Key::Ctrl('w') => { Key::Ctrl('w') => {
buffer.strip_word(); buffer.strip_word();
if buffer.is_empty() { if buffer.is_empty() {
buffer.visible = false; buffer.visible = false;
layout.force_redraw = true; force_redraw = true;
} }
}, },
// Backspace. // Backspace.
@ -239,9 +254,25 @@ pub fn run(
buffer.strip_char(); buffer.strip_char();
if buffer.is_empty() { if buffer.is_empty() {
buffer.visible = false; buffer.visible = false;
layout.force_redraw = true; force_redraw = true;
} }
}, },
// Set clock.
Key::Up if clock.paused => {
clock.shift(10);
// We would very likely not detect us passing the hour
// barrier and would panic when trying to draw hours
// without position if we do not schedule a recalculation
// here.
layout.schedule_recalc();
force_redraw = true;
},
Key::Down if clock.paused => {
clock.shift(-10);
alarm_roster.time_travel(clock.elapsed);
layout.schedule_recalc();
force_redraw = true;
},
// Forward every char if in insert mode. // Forward every char if in insert mode.
Key::Char(c) if buffer.visible => { Key::Char(c) if buffer.visible => {
buffer.push(c); buffer.push(c);
@ -250,18 +281,18 @@ pub fn run(
Key::Char('r') => { Key::Char('r') => {
clock.reset(); clock.reset();
alarm_roster.reset_all(); alarm_roster.reset_all();
layout.force_recalc = true; layout.schedule_recalc();
layout.force_redraw = true; force_redraw = true;
}, },
// (Un-)Pause on space. // (Un-)Pause on space.
Key::Char(' ') => { Key::Char(' ') => {
clock.toggle(); clock.toggle();
layout.force_redraw = true; force_redraw = true;
}, },
// Clear clock color on 'c'. // Clear clock color on 'c'.
Key::Char('c') => { Key::Char('c') => {
clock.color_index = None; clock.color_index = None;
layout.force_redraw = true; force_redraw = true;
}, },
// Delete last alarm on 'd'. // Delete last alarm on 'd'.
Key::Char('d') => { Key::Char('d') => {
@ -270,20 +301,20 @@ pub fn run(
// manually. It is safe to do it anyway. // manually. It is safe to do it anyway.
layout.set_roster_width(alarm_roster.width()); layout.set_roster_width(alarm_roster.width());
countdown.reset(); countdown.reset();
layout.force_redraw = true; force_redraw = true;
} }
}, },
// Exit on q and ^C. // Exit on q and ^C.
Key::Char('q') | Key::Ctrl('c') => break, Key::Char('q') | Key::Ctrl('c') => break,
// Force redraw on ^R. // Force redraw on ^R.
Key::Ctrl('r') => layout.force_redraw = true, Key::Ctrl('r') => force_redraw = true,
// Suspend an ^Z. // Suspend an ^Z.
Key::Ctrl('z') => { Key::Ctrl('z') => {
suspend(&mut stdout)?; suspend(&mut stdout)?;
// Clear SIGCONT, as we have already taken care to reset // Clear SIGCONT, as we have already taken care to reset
// the terminal. // the terminal.
//signal.compare_and_swap(SIGCONT, 0, Ordering::Relaxed); //signal.compare_and_swap(SIGCONT, 0, Ordering::Relaxed);
layout.force_redraw = true; force_redraw = true;
// Jump to the start of the main loop. // Jump to the start of the main loop.
continue; continue;
}, },
@ -291,7 +322,7 @@ pub fn run(
if c.is_ascii_digit() { if c.is_ascii_digit() {
buffer.push(c); buffer.push(c);
buffer.visible = true; buffer.visible = true;
layout.force_redraw = true; force_redraw = true;
} else if !buffer.is_empty() && c == ':' { } else if !buffer.is_empty() && c == ':' {
buffer.push(':'); buffer.push(':');
} }
@ -300,7 +331,7 @@ pub fn run(
_ => (), _ => (),
} }
} else { } else {
// Main loop delay. // Main loop delay. Skipped after key press.
thread::sleep(time::Duration::from_millis(100)); thread::sleep(time::Duration::from_millis(100));
} }
} }