Implemented clock shifting.
This commit is contained in:
parent
84a2a11d79
commit
2a336236e1
5 changed files with 137 additions and 57 deletions
25
src/alarm.rs
25
src/alarm.rs
|
@ -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>
|
||||
|
|
46
src/clock.rs
46
src/clock.rs
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
107
src/lib.rs
107
src/lib.rs
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue