Move input buffer into it's own module.

This commit is contained in:
shy 2021-04-11 12:44:25 +02:00
parent d95f2faaef
commit ade2c9f4da
6 changed files with 299 additions and 236 deletions

View file

@ -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;

109
src/buffer.rs Normal file
View file

@ -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<char> {
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<W: Write>(
&self,
stdout: &mut RawTerminal<W>,
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;
}
}

View file

@ -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};

View file

@ -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: <SIGUSR1> Reset clock.
<SIGUSR2> 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
"█████",
"█ █",
"█████",
"",
""
]
];
}

View file

@ -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.

View file

@ -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<W: Write>(
stdout: &mut RawTerminal<W>,
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<W: Write>(
stdout: &mut RawTerminal<W>,
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<W: Write>(mut stdout: &mut RawTerminal<W>)
-> Result<(), std::io::Error>