Smush all history into one commit
This commit is contained in:
commit
c1d54f8b5e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
88
Cargo.lock
generated
Normal file
88
Cargo.lock
generated
Normal file
@ -0,0 +1,88 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpn_rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"termion",
|
||||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"termion",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rpn_rs"
|
||||
version = "0.1.0"
|
||||
authors = ["Austen Adler <agadler@austenadler.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tui = "0.14"
|
||||
termion = "1.5"
|
335
src/calc.rs
Normal file
335
src/calc.rs
Normal file
@ -0,0 +1,335 @@
|
||||
pub mod constants;
|
||||
pub mod errors;
|
||||
pub mod operations;
|
||||
|
||||
use constants::CalculatorConstant;
|
||||
use constants::CalculatorConstants;
|
||||
use constants::CalculatorConstantsIter;
|
||||
use constants::CalculatorMacro;
|
||||
use constants::CalculatorMacros;
|
||||
use constants::CalculatorMacrosIter;
|
||||
use constants::CalculatorRegisters;
|
||||
use constants::CalculatorRegistersIter;
|
||||
use errors::CalculatorError;
|
||||
use operations::CalculatorOperation;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
enum OpArgs {
|
||||
Unary(f64),
|
||||
Binary([f64; 2]),
|
||||
None,
|
||||
}
|
||||
|
||||
struct CalculatorStateChange {
|
||||
pop: OpArgs,
|
||||
push: OpArgs,
|
||||
}
|
||||
|
||||
pub struct Calculator<'a> {
|
||||
stack: VecDeque<f64>,
|
||||
macros: CalculatorMacros<'a>,
|
||||
constants: CalculatorConstants<'a>,
|
||||
registers: CalculatorRegisters,
|
||||
undo_buf: Vec<CalculatorStateChange>,
|
||||
redo_buf: Vec<CalculatorStateChange>,
|
||||
}
|
||||
|
||||
impl<'a> Default for Calculator<'a> {
|
||||
fn default() -> Calculator<'a> {
|
||||
Calculator {
|
||||
stack: vec![1.2, 1.3].into_iter().collect(),
|
||||
undo_buf: vec![],
|
||||
redo_buf: vec![],
|
||||
registers: CalculatorRegisters::new(),
|
||||
macros: [
|
||||
(
|
||||
'm',
|
||||
CalculatorMacro {
|
||||
help: "<cr>64?>64%",
|
||||
value: " 64?>64%",
|
||||
},
|
||||
),
|
||||
(
|
||||
'u',
|
||||
CalculatorMacro {
|
||||
help: "Quadratic Formula",
|
||||
value: "RcRbRarbnrb2 ^4 rarc**-v+2 ra*/rbnrb2^4 rarc**-v-2 ra*/",
|
||||
},
|
||||
),
|
||||
(
|
||||
's',
|
||||
CalculatorMacro {
|
||||
help: "Sample data",
|
||||
value: "\\\\2 5 3n",
|
||||
},
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
constants: [
|
||||
(
|
||||
't',
|
||||
CalculatorConstant {
|
||||
help: "Tau (2pi)",
|
||||
value: std::f64::consts::TAU,
|
||||
},
|
||||
),
|
||||
(
|
||||
'e',
|
||||
CalculatorConstant {
|
||||
help: "Euler's Number e",
|
||||
value: std::f64::consts::E,
|
||||
},
|
||||
),
|
||||
(
|
||||
'p',
|
||||
CalculatorConstant {
|
||||
help: "Pi",
|
||||
value: std::f64::consts::PI,
|
||||
},
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Calculator<'a> {
|
||||
pub fn get_constants_iter(&'a self) -> CalculatorConstantsIter<'a> {
|
||||
self.constants.iter()
|
||||
}
|
||||
pub fn get_macros_iter(&'a self) -> CalculatorMacrosIter<'a> {
|
||||
self.macros.iter()
|
||||
}
|
||||
pub fn get_registers_iter(&self) -> CalculatorRegistersIter {
|
||||
self.registers.iter()
|
||||
}
|
||||
|
||||
pub fn push_constant(&mut self, key: char) -> Result<(), CalculatorError> {
|
||||
match self.constants.get(&key) {
|
||||
Some(CalculatorConstant { value, .. }) => {
|
||||
let value = *value;
|
||||
self.push(value)
|
||||
}
|
||||
None => Err(CalculatorError::NoSuchConstant),
|
||||
}
|
||||
}
|
||||
pub fn push_register(&mut self, key: char) -> Result<(), CalculatorError> {
|
||||
match self.registers.get(&key) {
|
||||
Some(f) => {
|
||||
let f = *f;
|
||||
self.push(f)
|
||||
}
|
||||
None => Err(CalculatorError::NoSuchRegister),
|
||||
}
|
||||
}
|
||||
// TODO: Use hashmap
|
||||
pub fn save_register(&mut self, key: char) -> Result<(), CalculatorError> {
|
||||
let f = self.pop()?;
|
||||
self.registers.insert(key, f);
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_macro(&mut self, key: char) -> Result<&CalculatorMacro<'a>, CalculatorError> {
|
||||
match self.macros.get(&key) {
|
||||
Some(m) => Ok(m),
|
||||
None => Err(CalculatorError::NoSuchMacro),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, f: f64) -> Result<(), CalculatorError> {
|
||||
self.direct_state_change(CalculatorStateChange {
|
||||
pop: OpArgs::None,
|
||||
push: OpArgs::Unary(f),
|
||||
})
|
||||
}
|
||||
pub fn pop(&mut self) -> Result<f64, CalculatorError> {
|
||||
let f = self.checked_get(0)?;
|
||||
self.direct_state_change(CalculatorStateChange {
|
||||
pop: OpArgs::Unary(f),
|
||||
push: OpArgs::None,
|
||||
})?;
|
||||
Ok(f)
|
||||
}
|
||||
pub fn get_stack(&self) -> &VecDeque<f64> {
|
||||
&self.stack
|
||||
}
|
||||
//TODO: VecDeque could have other types
|
||||
pub fn op(&mut self, op: CalculatorOperation) -> Result<(), CalculatorError> {
|
||||
let state_change = match op {
|
||||
CalculatorOperation::Add => self.binary_op(|[a, b]| OpArgs::Unary(b + a)),
|
||||
CalculatorOperation::Subtract => self.binary_op(|[a, b]| OpArgs::Unary(b - a)),
|
||||
CalculatorOperation::Multiply => self.binary_op(|[a, b]| OpArgs::Unary(b * a)),
|
||||
CalculatorOperation::Divide => self.binary_op(|[a, b]| OpArgs::Unary(b / a)),
|
||||
CalculatorOperation::IntegerDivide => {
|
||||
self.binary_op(|[a, b]| OpArgs::Unary(b.div_euclid(a)))
|
||||
}
|
||||
CalculatorOperation::Negate => self.unary_op(|a| OpArgs::Unary(-a)),
|
||||
CalculatorOperation::AbsoluteValue => self.unary_op(|a| OpArgs::Unary(a.abs())),
|
||||
CalculatorOperation::Inverse => self.unary_op(|a| OpArgs::Unary(1.0 / a)),
|
||||
CalculatorOperation::Modulo => self.binary_op(|[a, b]| OpArgs::Unary(b % a)),
|
||||
//CalculatorOperation::Remainder => {
|
||||
// self.binary_op(|[a, b]| OpArgs::Unary(b.rem_euclid(a)))
|
||||
//}
|
||||
CalculatorOperation::Dup => self.unary_op(|a| OpArgs::Binary([a, a])),
|
||||
CalculatorOperation::Drop => self.unary_op(|_| OpArgs::None),
|
||||
CalculatorOperation::Swap => self.binary_op(|[a, b]| OpArgs::Binary([b, a])),
|
||||
CalculatorOperation::Sin => self.unary_op(|a| OpArgs::Unary(a.sin())),
|
||||
CalculatorOperation::Cos => self.unary_op(|a| OpArgs::Unary(a.cos())),
|
||||
CalculatorOperation::Tan => self.unary_op(|a| OpArgs::Unary(a.tan())),
|
||||
CalculatorOperation::ASin => self.unary_op(|a| OpArgs::Unary(a.asin())),
|
||||
CalculatorOperation::ACos => self.unary_op(|a| OpArgs::Unary(a.acos())),
|
||||
CalculatorOperation::ATan => self.unary_op(|a| OpArgs::Unary(a.atan())),
|
||||
CalculatorOperation::Sqrt => self.unary_op(|a| OpArgs::Unary(a.sqrt())),
|
||||
// CalculatorOperation::Factorial => vec![args[0].()],
|
||||
CalculatorOperation::Log => self.unary_op(|a| OpArgs::Unary(a.log10())),
|
||||
CalculatorOperation::Ln => self.unary_op(|a| OpArgs::Unary(a.ln())),
|
||||
CalculatorOperation::Pow => self.binary_op(|[a, b]| OpArgs::Unary(b.powf(a))),
|
||||
CalculatorOperation::E => self.binary_op(|[a, b]| OpArgs::Unary(b * 10.0f64.powf(a))),
|
||||
CalculatorOperation::Undo => {
|
||||
let s = self
|
||||
.undo_buf
|
||||
.pop()
|
||||
.ok_or_else(|| CalculatorError::EmptyHistory(String::from("undo")))?;
|
||||
return self.apply_state_change(s, false);
|
||||
}
|
||||
CalculatorOperation::Redo => {
|
||||
let s = self
|
||||
.redo_buf
|
||||
.pop()
|
||||
.ok_or_else(|| CalculatorError::EmptyHistory(String::from("redo")))?;
|
||||
return self.apply_state_change(s, true);
|
||||
}
|
||||
};
|
||||
|
||||
self.direct_state_change(state_change?)
|
||||
}
|
||||
|
||||
fn unary_op(
|
||||
&mut self,
|
||||
op: impl FnOnce(f64) -> OpArgs,
|
||||
) -> Result<CalculatorStateChange, CalculatorError> {
|
||||
let arg = self
|
||||
.stack
|
||||
.get(0)
|
||||
.ok_or(CalculatorError::NotEnoughStackEntries)?;
|
||||
Ok(CalculatorStateChange {
|
||||
pop: OpArgs::Unary(*arg),
|
||||
push: op(*arg),
|
||||
})
|
||||
}
|
||||
fn binary_op(
|
||||
&mut self,
|
||||
op: impl FnOnce([f64; 2]) -> OpArgs,
|
||||
) -> Result<CalculatorStateChange, CalculatorError> {
|
||||
let args: [f64; 2] = [
|
||||
*self
|
||||
.stack
|
||||
.get(0)
|
||||
.ok_or(CalculatorError::NotEnoughStackEntries)?,
|
||||
*self
|
||||
.stack
|
||||
.get(1)
|
||||
.ok_or(CalculatorError::NotEnoughStackEntries)?,
|
||||
];
|
||||
Ok(CalculatorStateChange {
|
||||
pop: OpArgs::Binary(args),
|
||||
push: op(args),
|
||||
})
|
||||
}
|
||||
|
||||
fn direct_state_change(&mut self, c: CalculatorStateChange) -> Result<(), CalculatorError> {
|
||||
let result = self.apply_state_change(c, true);
|
||||
if result.is_ok() {
|
||||
// Clear the redo buffer since this was a direct state change
|
||||
self.redo_buf = vec![];
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn apply_state_change(
|
||||
&mut self,
|
||||
c: CalculatorStateChange,
|
||||
forward: bool,
|
||||
) -> Result<(), CalculatorError> {
|
||||
let (to_pop, to_push) = if forward {
|
||||
(&c.pop, &c.push)
|
||||
} else {
|
||||
(&c.push, &c.pop)
|
||||
};
|
||||
|
||||
match to_push {
|
||||
OpArgs::Unary(a) => {
|
||||
if a.is_nan() || a.is_infinite() {
|
||||
return Err(CalculatorError::ArithmeticError);
|
||||
}
|
||||
}
|
||||
OpArgs::Binary([a, b]) => {
|
||||
if a.is_nan() || b.is_nan() || a.is_infinite() || b.is_infinite() {
|
||||
return Err(CalculatorError::ArithmeticError);
|
||||
}
|
||||
}
|
||||
OpArgs::None => {}
|
||||
};
|
||||
|
||||
match to_pop {
|
||||
OpArgs::Unary(a) => {
|
||||
self.stack_eq(0, *a)?;
|
||||
self.stack.pop_front();
|
||||
}
|
||||
OpArgs::Binary([a, b]) => {
|
||||
if forward {
|
||||
self.stack_eq(0, *a)?;
|
||||
self.stack_eq(1, *b)?;
|
||||
} else {
|
||||
self.stack_eq(0, *a)?;
|
||||
self.stack_eq(1, *b)?;
|
||||
}
|
||||
self.stack.pop_front();
|
||||
self.stack.pop_front();
|
||||
}
|
||||
OpArgs::None => {}
|
||||
};
|
||||
|
||||
match to_push {
|
||||
OpArgs::Unary(a) => {
|
||||
self.stack.push_front(*a);
|
||||
}
|
||||
OpArgs::Binary([a, b]) => {
|
||||
self.stack.push_front(*b);
|
||||
self.stack.push_front(*a);
|
||||
}
|
||||
OpArgs::None => {}
|
||||
};
|
||||
|
||||
if forward {
|
||||
self.undo_buf.push(c);
|
||||
} else {
|
||||
self.redo_buf.push(c);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stack_eq(&self, idx: usize, value: f64) -> Result<(), CalculatorError> {
|
||||
if (self.checked_get(idx)? - value).abs() > f64::EPSILON {
|
||||
Err(CalculatorError::CorruptStateChange(format!(
|
||||
"Stack index {} should be {}, but is {}",
|
||||
idx,
|
||||
value,
|
||||
self.checked_get(idx)?,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_get(&self, idx: usize) -> Result<f64, CalculatorError> {
|
||||
match self.stack.get(idx) {
|
||||
None => Err(CalculatorError::NotEnoughStackEntries),
|
||||
Some(r) => Ok(*r),
|
||||
}
|
||||
}
|
||||
}
|
23
src/calc/constants.rs
Normal file
23
src/calc/constants.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use std::collections::hash_map::Iter;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CalculatorConstant<'a> {
|
||||
pub help: &'a str,
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct CalculatorMacro<'a> {
|
||||
pub help: &'a str,
|
||||
pub value: &'a str,
|
||||
}
|
||||
|
||||
pub type CalculatorConstants<'a> = HashMap<char, CalculatorConstant<'a>>;
|
||||
pub type CalculatorConstantsIter<'a> = Iter<'a, char, CalculatorConstant<'a>>;
|
||||
|
||||
pub type CalculatorMacros<'a> = HashMap<char, CalculatorMacro<'a>>;
|
||||
pub type CalculatorMacrosIter<'a> = Iter<'a, char, CalculatorMacro<'a>>;
|
||||
|
||||
pub type CalculatorRegisters = HashMap<char, f64>;
|
||||
pub type CalculatorRegistersIter<'a> = Iter<'a, char, f64>;
|
26
src/calc/errors.rs
Normal file
26
src/calc/errors.rs
Normal file
@ -0,0 +1,26 @@
|
||||
pub enum CalculatorError {
|
||||
ArithmeticError,
|
||||
NotEnoughStackEntries,
|
||||
CorruptStateChange(String),
|
||||
EmptyHistory(String),
|
||||
NoSuchConstant,
|
||||
NoSuchRegister,
|
||||
NoSuchMacro,
|
||||
}
|
||||
|
||||
impl CalculatorError {
|
||||
//TODO: Use &str instead of Strings
|
||||
pub fn message(&self) -> String {
|
||||
match self {
|
||||
CalculatorError::ArithmeticError => String::from("Arithmetic Error"),
|
||||
CalculatorError::NotEnoughStackEntries => String::from("Not enough items in the stack"),
|
||||
CalculatorError::CorruptStateChange(msg) => {
|
||||
String::from("Corrupt state change: ") + msg
|
||||
}
|
||||
CalculatorError::EmptyHistory(msg) => String::from("No history to ") + msg,
|
||||
CalculatorError::NoSuchConstant => String::from("No such constant"),
|
||||
CalculatorError::NoSuchRegister => String::from("No such register"),
|
||||
CalculatorError::NoSuchMacro => String::from("No such macro"),
|
||||
}
|
||||
}
|
||||
}
|
102
src/calc/operations.rs
Normal file
102
src/calc/operations.rs
Normal file
@ -0,0 +1,102 @@
|
||||
pub enum CalculatorOperation {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Negate,
|
||||
AbsoluteValue,
|
||||
Inverse,
|
||||
Modulo,
|
||||
IntegerDivide,
|
||||
//Remainder,
|
||||
Drop,
|
||||
Dup,
|
||||
Swap,
|
||||
Sin,
|
||||
Cos,
|
||||
Tan,
|
||||
ASin,
|
||||
ACos,
|
||||
ATan,
|
||||
Sqrt,
|
||||
Undo,
|
||||
Redo,
|
||||
Pow,
|
||||
// Factorial,
|
||||
Log,
|
||||
Ln,
|
||||
E,
|
||||
}
|
||||
|
||||
impl CalculatorOperation {
|
||||
pub fn from_char(key: char) -> Result<CalculatorOperation, ()> {
|
||||
match key {
|
||||
'+' => Ok(CalculatorOperation::Add),
|
||||
'-' => Ok(CalculatorOperation::Subtract),
|
||||
'*' => Ok(CalculatorOperation::Multiply),
|
||||
'/' => Ok(CalculatorOperation::Divide),
|
||||
'n' => Ok(CalculatorOperation::Negate),
|
||||
'|' => Ok(CalculatorOperation::AbsoluteValue),
|
||||
'i' => Ok(CalculatorOperation::Inverse),
|
||||
'%' => Ok(CalculatorOperation::Modulo),
|
||||
//'r' => Ok(CalculatorOperation::Remainder),
|
||||
'\\' => Ok(CalculatorOperation::Drop),
|
||||
'?' => Ok(CalculatorOperation::IntegerDivide),
|
||||
'\n' => Ok(CalculatorOperation::Dup),
|
||||
'>' => Ok(CalculatorOperation::Swap),
|
||||
's' => Ok(CalculatorOperation::Sin),
|
||||
'c' => Ok(CalculatorOperation::Cos),
|
||||
't' => Ok(CalculatorOperation::Tan),
|
||||
'S' => Ok(CalculatorOperation::ASin),
|
||||
'C' => Ok(CalculatorOperation::ACos),
|
||||
'T' => Ok(CalculatorOperation::ATan),
|
||||
'v' => Ok(CalculatorOperation::Sqrt),
|
||||
//TODO: Should not be calculator states probably
|
||||
'u' => Ok(CalculatorOperation::Undo),
|
||||
'U' => Ok(CalculatorOperation::Redo),
|
||||
'^' => Ok(CalculatorOperation::Pow),
|
||||
//'!' => Ok(CalculatorOperation::Factorial),
|
||||
'l' => Ok(CalculatorOperation::Log),
|
||||
'L' => Ok(CalculatorOperation::Ln),
|
||||
'e' => Ok(CalculatorOperation::E),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_stack(&self) -> usize {
|
||||
match self {
|
||||
CalculatorOperation::Add
|
||||
| CalculatorOperation::Subtract
|
||||
| CalculatorOperation::Multiply
|
||||
| CalculatorOperation::Divide
|
||||
| CalculatorOperation::Modulo
|
||||
| CalculatorOperation::IntegerDivide
|
||||
| CalculatorOperation::Swap
|
||||
| CalculatorOperation::Pow
|
||||
| CalculatorOperation::E
|
||||
//| CalculatorOperation::Remainder
|
||||
=> 2,
|
||||
CalculatorOperation::Negate
|
||||
| CalculatorOperation::AbsoluteValue
|
||||
| CalculatorOperation::Inverse
|
||||
| CalculatorOperation::Drop
|
||||
|
||||
| CalculatorOperation::Sin
|
||||
| CalculatorOperation::Cos
|
||||
| CalculatorOperation::Tan
|
||||
| CalculatorOperation::ASin
|
||||
| CalculatorOperation::ACos
|
||||
| CalculatorOperation::ATan
|
||||
| CalculatorOperation::Sqrt
|
||||
| CalculatorOperation::Dup
|
||||
|
||||
//|CalculatorOperation::Factorial
|
||||
| CalculatorOperation::Log
|
||||
| CalculatorOperation::Ln
|
||||
=> 1,
|
||||
CalculatorOperation::Undo
|
||||
| CalculatorOperation::Redo
|
||||
=> 0,
|
||||
}
|
||||
}
|
||||
}
|
444
src/main.rs
Normal file
444
src/main.rs
Normal file
@ -0,0 +1,444 @@
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod calc;
|
||||
mod util;
|
||||
|
||||
use calc::constants::CalculatorMacro;
|
||||
use calc::operations::CalculatorOperation;
|
||||
use calc::Calculator;
|
||||
use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::process;
|
||||
use util::event::{Event, Events};
|
||||
//use util::event::T;
|
||||
use std::{error::Error, io};
|
||||
use termion::{event::Key, raw::IntoRawMode, screen::AlternateScreen};
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Modifier, Style},
|
||||
terminal::Frame,
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
struct Dimensions {
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
enum RegisterState {
|
||||
Save,
|
||||
Load,
|
||||
}
|
||||
|
||||
enum AppState {
|
||||
Calculator,
|
||||
Help,
|
||||
Constants,
|
||||
Macros,
|
||||
Registers(RegisterState),
|
||||
}
|
||||
|
||||
struct App<'a> {
|
||||
input: String,
|
||||
calculator: Calculator<'a>,
|
||||
error_msg: Option<String>,
|
||||
state: AppState,
|
||||
current_macro: Option<char>,
|
||||
}
|
||||
|
||||
impl<'a> Default for App<'a> {
|
||||
fn default() -> App<'a> {
|
||||
App {
|
||||
input: String::new(),
|
||||
calculator: Calculator::default(),
|
||||
error_msg: None,
|
||||
state: AppState::Calculator,
|
||||
current_macro: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let stdout = io::stdout().into_raw_mode()?;
|
||||
// let stdout = MouseTerminal::from(stdout);
|
||||
let stdout = AlternateScreen::from(stdout);
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let events = Events::new();
|
||||
let mut app = App::default();
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
|
||||
let msg = match &app.error_msg {
|
||||
Some(e) => vec![
|
||||
Span::raw("Error: "),
|
||||
Span::styled(e, Style::default().add_modifier(Modifier::RAPID_BLINK)),
|
||||
],
|
||||
None => vec![
|
||||
Span::raw("Press "),
|
||||
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to exit, "),
|
||||
Span::styled("h", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" for help"),
|
||||
],
|
||||
};
|
||||
|
||||
let text = Text::from(Spans::from(msg));
|
||||
let help_message = Paragraph::new(text);
|
||||
f.render_widget(help_message, chunks[0]);
|
||||
|
||||
let mut stack: Vec<ListItem> = app
|
||||
.calculator
|
||||
.get_stack()
|
||||
.iter()
|
||||
.take(chunks[1].height as usize - 2)
|
||||
.enumerate()
|
||||
.rev()
|
||||
.map(|(i, m)| {
|
||||
let content = vec![Spans::from(Span::raw(format!("{}: {}", i, m)))];
|
||||
ListItem::new(content)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// stack.insert(
|
||||
// 0,
|
||||
// ListItem::new(Span::raw(format!("dbg: {}", chunks[2].height))),
|
||||
// );
|
||||
|
||||
for _ in 0..(chunks[1]
|
||||
.height
|
||||
.saturating_sub(stack.len() as u16)
|
||||
.saturating_sub(2))
|
||||
{
|
||||
stack.insert(0, ListItem::new(Span::raw("~")));
|
||||
}
|
||||
|
||||
let stack =
|
||||
List::new(stack).block(Block::default().borders(Borders::ALL).title("Stack"));
|
||||
f.render_widget(stack, chunks[1]);
|
||||
|
||||
let input = Paragraph::new(app.input.as_ref())
|
||||
.style(Style::default())
|
||||
.block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
f.render_widget(input, chunks[2]);
|
||||
|
||||
f.set_cursor(chunks[2].x + app.input.len() as u16 + 1, chunks[2].y + 1);
|
||||
|
||||
match &app.state {
|
||||
AppState::Help => {
|
||||
draw_clippy_rect(
|
||||
ClippyRectangle {
|
||||
title: "Help",
|
||||
msg: "+ => Add s => Sin\n\
|
||||
- => Subtract c => Cos\n\
|
||||
* => Multiply t => Tan\n\
|
||||
/ => Divide S => ASin\n\
|
||||
n => Negate C => ACos\n\
|
||||
| => Abs T => ATan\n\
|
||||
i => Inverse v => Sqrt\n\
|
||||
% => Modulo u => Undo\n\
|
||||
\\ => Drop U => Redo\n\
|
||||
? => IntegerDivide ^ => Pow\n\
|
||||
<ret> => Dup l => Log\n\
|
||||
> => Swap L => Ln\n\
|
||||
e => E ^c => Constants\n\
|
||||
^m => Macros rR => Registers",
|
||||
},
|
||||
f,
|
||||
);
|
||||
}
|
||||
AppState::Constants => {
|
||||
draw_clippy_rect(
|
||||
ClippyRectangle {
|
||||
title: "Constants",
|
||||
msg: app
|
||||
.calculator
|
||||
.get_constants_iter()
|
||||
.map(|(key, constant)| {
|
||||
format!("{}: {} ({})", key, constant.help, constant.value)
|
||||
})
|
||||
.fold(String::new(), |acc, s| acc + &s + "\n")
|
||||
.trim_end(),
|
||||
},
|
||||
f,
|
||||
);
|
||||
}
|
||||
AppState::Registers(state) => {
|
||||
let title = match state {
|
||||
RegisterState::Save => "Registers (press char to save)",
|
||||
RegisterState::Load => "Registers",
|
||||
};
|
||||
draw_clippy_rect(
|
||||
ClippyRectangle {
|
||||
title: title,
|
||||
msg: app
|
||||
.calculator
|
||||
.get_registers_iter()
|
||||
.map(|(key, value)| format!("{}: {}", key, value))
|
||||
.fold(String::new(), |acc, s| acc + &s + "\n")
|
||||
.trim_end(),
|
||||
},
|
||||
f,
|
||||
);
|
||||
}
|
||||
AppState::Macros => {
|
||||
draw_clippy_rect(
|
||||
ClippyRectangle {
|
||||
title: "Macros",
|
||||
msg: app
|
||||
.calculator
|
||||
.get_macros_iter()
|
||||
.map(|(key, mac)| format!("{}: {}", key, mac.help))
|
||||
.fold(String::new(), |acc, s| acc + &s + "\n")
|
||||
.trim_end(),
|
||||
},
|
||||
f,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Event::Input(key) = events.next()? {
|
||||
handle_key(&mut app, &events, key);
|
||||
}
|
||||
|
||||
for e in events.try_iter() {
|
||||
match e {
|
||||
Event::Input(key) => handle_key(&mut app, &events, key),
|
||||
Event::MacroEnd => app.current_macro = None,
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
app.current_macro = None;
|
||||
}
|
||||
// TODO: Bubble up return Ok so we can handle saving
|
||||
//Ok(())
|
||||
}
|
||||
|
||||
fn calc_operation(app: &mut App, c: char) {
|
||||
if let Ok(op) = CalculatorOperation::from_char(c) {
|
||||
if let Ok(f) = app.input.parse::<f64>() {
|
||||
if app.calculator.push(f).is_ok() {
|
||||
app.input.clear();
|
||||
}
|
||||
}
|
||||
|
||||
app.error_msg = match app.calculator.op(op) {
|
||||
Err(e) => Some(e.message()),
|
||||
Ok(()) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClippyRectangle<'a> {
|
||||
title: &'a str,
|
||||
msg: &'a str,
|
||||
}
|
||||
|
||||
impl ClippyRectangle<'_> {
|
||||
// TODO: Make this static somehow
|
||||
fn size(&self) -> Dimensions {
|
||||
let (width, height) = self.msg.lines().fold((0, 0), |(width, height), l| {
|
||||
(cmp::max(width, l.len()), height + 1)
|
||||
});
|
||||
Dimensions {
|
||||
width: u16::try_from(width).unwrap_or(u16::MAX),
|
||||
height: u16::try_from(height).unwrap_or(u16::MAX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_clippy_rect<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<TermionBackend<T>>) {
|
||||
let block = Block::default().title(c.title).borders(Borders::ALL);
|
||||
let dimensions = c.size();
|
||||
let popup_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(dimensions.height + 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
let area = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(cmp::max(dimensions.width, c.title.len() as u16) + 2),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(popup_layout[1])[1];
|
||||
f.render_widget(Clear, area);
|
||||
|
||||
let help_message = Paragraph::new(c.msg)
|
||||
.style(Style::default())
|
||||
.block(Block::default().borders(Borders::ALL).title(c.title));
|
||||
f.render_widget(help_message, area);
|
||||
}
|
||||
|
||||
fn handle_key(app: &mut App, events: &Events, key: Key) {
|
||||
match &app.state {
|
||||
AppState::Calculator => {
|
||||
app.error_msg = None;
|
||||
match key {
|
||||
Key::Char('q') => {
|
||||
process::exit(0);
|
||||
}
|
||||
Key::Ctrl('c') => {
|
||||
app.state = AppState::Constants;
|
||||
}
|
||||
Key::Char('r') => {
|
||||
app.state = AppState::Registers(RegisterState::Load);
|
||||
}
|
||||
Key::Char('R') => {
|
||||
app.state = AppState::Registers(RegisterState::Save);
|
||||
}
|
||||
Key::Char('m') => {
|
||||
app.state = AppState::Macros;
|
||||
}
|
||||
Key::Char('h') => {
|
||||
app.state = AppState::Help;
|
||||
}
|
||||
Key::Char(c @ '0'..='9') => {
|
||||
app.input.push(c);
|
||||
}
|
||||
Key::Char(c @ 'e') | Key::Char(c @ '.') => {
|
||||
if !app.input.contains(c) {
|
||||
app.input.push(c);
|
||||
}
|
||||
}
|
||||
Key::Char('\n') | Key::Char(' ') => {
|
||||
if app.input.is_empty() {
|
||||
calc_operation(app, '\n');
|
||||
} else if let Ok(f) = app.input.parse::<f64>() {
|
||||
if app.calculator.push(f).is_ok() {
|
||||
app.input.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Key::Right => {
|
||||
calc_operation(app, '>');
|
||||
}
|
||||
Key::Down => {
|
||||
if let Ok(x) = app.calculator.pop() {
|
||||
app.input = x.to_string();
|
||||
}
|
||||
}
|
||||
Key::Backspace => {
|
||||
app.input.pop();
|
||||
}
|
||||
Key::Delete => {
|
||||
app.input.clear();
|
||||
}
|
||||
Key::Char(c) => {
|
||||
calc_operation(app, c);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
AppState::Help => match key {
|
||||
Key::Esc | Key::Char('q') => {
|
||||
app.state = AppState::Calculator;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
AppState::Constants => match key {
|
||||
Key::Esc | Key::Char('q') => {
|
||||
app.state = AppState::Calculator;
|
||||
}
|
||||
Key::Char(c) => {
|
||||
app.error_msg = match app.calculator.push_constant(c) {
|
||||
Err(e) => Some(e.message()),
|
||||
Ok(()) => {
|
||||
app.input.clear();
|
||||
app.state = AppState::Calculator;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
AppState::Registers(task) => match key {
|
||||
Key::Esc | Key::Char('q') => {
|
||||
app.state = AppState::Calculator;
|
||||
}
|
||||
Key::Char(c) => match task {
|
||||
RegisterState::Save => {
|
||||
app.error_msg = match app.calculator.save_register(c) {
|
||||
Err(e) => Some(e.message()),
|
||||
Ok(()) => {
|
||||
app.input.clear();
|
||||
app.state = AppState::Calculator;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
RegisterState::Load => {
|
||||
app.error_msg = match app.calculator.push_register(c) {
|
||||
Err(e) => Some(e.message()),
|
||||
Ok(()) => {
|
||||
app.input.clear();
|
||||
app.state = AppState::Calculator;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
AppState::Macros => match key {
|
||||
Key::Esc | Key::Char('q') => {
|
||||
app.state = AppState::Calculator;
|
||||
}
|
||||
Key::Char(c) => {
|
||||
if !app.input.is_empty() {
|
||||
//TODO: A better way to do this
|
||||
if let Ok(f) = app.input.parse::<f64>() {
|
||||
if app.calculator.push(f).is_ok() {
|
||||
app.input.clear();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle macros internally to the calculator
|
||||
app.error_msg = match app.calculator.get_macro(c) {
|
||||
Ok(CalculatorMacro { value, .. }) => {
|
||||
// let value = *value;
|
||||
events.fill_event_buf(value);
|
||||
app.state = AppState::Calculator;
|
||||
None
|
||||
}
|
||||
Err(e) => Some(e.message()),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
181
src/user_input.rs
Normal file
181
src/user_input.rs
Normal file
@ -0,0 +1,181 @@
|
||||
/// A simple example demonstrating how to handle user input. This is
|
||||
/// a bit out of the scope of the library as it does not provide any
|
||||
/// input handling out of the box. However, it may helps some to get
|
||||
/// started.
|
||||
///
|
||||
/// This is a very simple example:
|
||||
/// * A input box always focused. Every character you type is registered
|
||||
/// here
|
||||
/// * Pressing Backspace erases a character
|
||||
/// * Pressing Enter pushes the current input in the history of previous
|
||||
/// messages
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod util;
|
||||
|
||||
use crate::util::event::{Event, Events};
|
||||
use std::{error::Error, io};
|
||||
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Span, Spans, Text},
|
||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
enum InputMode {
|
||||
Normal,
|
||||
Editing,
|
||||
}
|
||||
|
||||
/// App holds the state of the application
|
||||
struct App {
|
||||
/// Current value of the input box
|
||||
input: String,
|
||||
/// Current input mode
|
||||
input_mode: InputMode,
|
||||
/// History of recorded messages
|
||||
messages: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> App {
|
||||
App {
|
||||
input: String::new(),
|
||||
input_mode: InputMode::Normal,
|
||||
messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Terminal initialization
|
||||
let stdout = io::stdout().into_raw_mode()?;
|
||||
let stdout = MouseTerminal::from(stdout);
|
||||
let stdout = AlternateScreen::from(stdout);
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// Setup event handlers
|
||||
let mut events = Events::new();
|
||||
|
||||
// Create default app state
|
||||
let mut app = App::default();
|
||||
|
||||
loop {
|
||||
// Draw UI
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(1),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(f.size());
|
||||
|
||||
let (msg, style) = match app.input_mode {
|
||||
InputMode::Normal => (
|
||||
vec![
|
||||
Span::raw("Press "),
|
||||
Span::styled("q", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to exit, "),
|
||||
Span::styled("e", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to start editing."),
|
||||
],
|
||||
Style::default().add_modifier(Modifier::RAPID_BLINK),
|
||||
),
|
||||
InputMode::Editing => (
|
||||
vec![
|
||||
Span::raw("Press "),
|
||||
Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to stop editing, "),
|
||||
Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to record the message"),
|
||||
],
|
||||
Style::default(),
|
||||
),
|
||||
};
|
||||
let mut text = Text::from(Spans::from(msg));
|
||||
text.patch_style(style);
|
||||
let help_message = Paragraph::new(text);
|
||||
f.render_widget(help_message, chunks[0]);
|
||||
|
||||
let input = Paragraph::new(app.input.as_ref())
|
||||
.style(match app.input_mode {
|
||||
InputMode::Normal => Style::default(),
|
||||
InputMode::Editing => Style::default().fg(Color::Yellow),
|
||||
})
|
||||
.block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
f.render_widget(input, chunks[1]);
|
||||
match app.input_mode {
|
||||
InputMode::Normal =>
|
||||
// Hide the cursor. `Frame` does this by default, so we don't need to do anything here
|
||||
{}
|
||||
|
||||
InputMode::Editing => {
|
||||
// Make the cursor visible and ask tui-rs to put it at the specified coordinates after rendering
|
||||
f.set_cursor(
|
||||
// Put cursor past the end of the input text
|
||||
chunks[1].x + app.input.width() as u16 + 1,
|
||||
// Move one line down, from the border to the input line
|
||||
chunks[1].y + 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let messages: Vec<ListItem> = app
|
||||
.messages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, m)| {
|
||||
let content = vec![Spans::from(Span::raw(format!("{}: {}", i, m)))];
|
||||
ListItem::new(content)
|
||||
})
|
||||
.collect();
|
||||
let messages =
|
||||
List::new(messages).block(Block::default().borders(Borders::ALL).title("Messages"));
|
||||
f.render_widget(messages, chunks[2]);
|
||||
})?;
|
||||
|
||||
// Handle input
|
||||
if let Event::Input(input) = events.next()? {
|
||||
match app.input_mode {
|
||||
InputMode::Normal => match input {
|
||||
Key::Char('e') => {
|
||||
app.input_mode = InputMode::Editing;
|
||||
events.disable_exit_key();
|
||||
}
|
||||
Key::Char('q') => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
InputMode::Editing => match input {
|
||||
Key::Char('\n') => {
|
||||
app.messages.push(app.input.drain(..).collect());
|
||||
}
|
||||
Key::Char(c) => {
|
||||
app.input.push(c);
|
||||
}
|
||||
Key::Backspace => {
|
||||
app.input.pop();
|
||||
}
|
||||
Key::Esc => {
|
||||
app.input_mode = InputMode::Normal;
|
||||
events.enable_exit_key();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
120
src/util/event.rs
Normal file
120
src/util/event.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use std::io;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::mpsc::TryIter;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
|
||||
pub enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
MacroEnd,
|
||||
}
|
||||
|
||||
/// A small event handler that wrap termion input and tick events. Each event
|
||||
/// type is handled in its own thread and returned to a common `Receiver`
|
||||
#[allow(dead_code)]
|
||||
pub struct Events {
|
||||
rx: mpsc::Receiver<Event<Key>>,
|
||||
tx: mpsc::Sender<Event<Key>>,
|
||||
input_handle: thread::JoinHandle<()>,
|
||||
ignore_exit_key: Arc<AtomicBool>,
|
||||
tick_handle: thread::JoinHandle<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Config {
|
||||
pub exit_key: Key,
|
||||
pub tick_rate: Duration,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
exit_key: Key::Char('q'),
|
||||
tick_rate: Duration::from_millis(1000),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Events {
|
||||
pub fn new() -> Events {
|
||||
Events::with_config(Config::default())
|
||||
}
|
||||
|
||||
pub fn with_config(config: Config) -> Events {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mac_tx = tx.clone();
|
||||
let ignore_exit_key = Arc::new(AtomicBool::new(true));
|
||||
let input_handle = {
|
||||
let tx = tx.clone();
|
||||
let ignore_exit_key = ignore_exit_key.clone();
|
||||
thread::spawn(move || {
|
||||
let stdin = io::stdin();
|
||||
for evt in stdin.keys() {
|
||||
if let Ok(key) = evt {
|
||||
if let Err(err) = tx.send(Event::Input(key)) {
|
||||
eprintln!("{}", err);
|
||||
return;
|
||||
}
|
||||
if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
let tick_handle = {
|
||||
thread::spawn(move || loop {
|
||||
if tx.send(Event::Tick).is_err() {
|
||||
break;
|
||||
}
|
||||
thread::sleep(config.tick_rate);
|
||||
})
|
||||
};
|
||||
Events {
|
||||
rx,
|
||||
tx: mac_tx,
|
||||
ignore_exit_key,
|
||||
input_handle,
|
||||
tick_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_event_buf(&self, mac: &str) {
|
||||
for c in mac.chars() {
|
||||
// TODO: Catch errors
|
||||
if let Err(_) = self.tx.send(Event::Input(Key::Char(c))) {
|
||||
//return;
|
||||
}
|
||||
}
|
||||
//self.tx.send(Event::MacroEnd);
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
|
||||
self.rx.recv()
|
||||
}
|
||||
|
||||
pub fn try_next(&self) -> Result<Event<Key>, mpsc::TryRecvError> {
|
||||
self.rx.try_recv()
|
||||
}
|
||||
pub fn try_iter(&self) -> TryIter<Event<Key>> {
|
||||
self.rx.try_iter()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn disable_exit_key(&mut self) {
|
||||
self.ignore_exit_key.store(true, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn enable_exit_key(&mut self) {
|
||||
self.ignore_exit_key.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
1
src/util/mod.rs
Normal file
1
src/util/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod event;
|
Loading…
x
Reference in New Issue
Block a user