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

View file

@ -8,12 +8,18 @@ use crate::consts::COLOR;
use crate::Config;
use crate::layout::{Layout, Position};
enum Pause {
Instant(time::Instant),
Secs(u32),
None,
}
pub struct Clock {
pub start: time::Instant,
pub elapsed: u32,
pub days: u32,
pub paused: bool,
paused_at: Option<time::Instant>,
paused_at: Pause,
pub color_index: Option<usize>,
pub font: &'static font::Font,
}
@ -25,7 +31,7 @@ impl Clock {
elapsed: 0,
days: 0,
paused: false,
paused_at: None,
paused_at: Pause::None,
color_index: None,
font: config.font,
}
@ -44,19 +50,24 @@ impl Clock {
}
fn pause(&mut self) {
self.paused_at = Some(time::Instant::now());
self.paused_at = Pause::Instant(time::Instant::now());
self.paused = true;
}
fn unpause(&mut self) {
// Try to derive a new start instant.
if let Some(delay) = self.paused_at {
if let Some(new_start) = self.start.checked_add(delay.elapsed()) {
self.start = new_start;
}
match self.paused_at {
Pause::Instant(delay) => {
if let Some(start) = self.start.checked_add(delay.elapsed()) {
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;
}
@ -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 {
if self.elapsed >= 3600 {
// Hours
@ -95,6 +116,7 @@ impl Clock {
&self,
mut stdout: &mut RawTerminal<W>,
layout: &Layout,
force_redraw: bool,
) -> Result<(), std::io::Error>
{
// Setup style and color if appropriate.
@ -106,7 +128,7 @@ impl Clock {
}
// 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.
if self.elapsed >= 3600 {
self.draw_digit_pair(
@ -142,7 +164,7 @@ impl Clock {
}
// 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(
&mut stdout,
(self.elapsed % 3600) / 60,
@ -151,7 +173,7 @@ impl Clock {
}
// Draw colon if necessary.
if layout.force_redraw {
if force_redraw {
self.draw_colon(
&mut stdout,
&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";
pub const MENUBAR_INS: &str =
"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 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 height: 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)
-> Result<(), std::io::Error>
-> Result<bool, std::io::Error>
{
if self.force_recalc || force {
self.force_recalc = false;
@ -60,9 +61,14 @@ impl Layout {
self.clock_height = clock.font.height;
self.digit_width = clock.font.width;
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)]

View file

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