This commit is contained in:
2026-08-01 22:41:59 +03:00
parent 7d050f3eb2
commit 4f043fc1c0
12 changed files with 1284 additions and 49 deletions
+861
View File
@@ -0,0 +1,861 @@
//! Engineering (scientific) calculator — formula entry with trig/log/powers.
use std::f64::consts::{E, PI};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AngleMode {
Deg,
Rad,
}
impl AngleMode {
pub fn label(self) -> &'static str {
match self {
AngleMode::Deg => "DEG",
AngleMode::Rad => "RAD",
}
}
pub fn cycle(self) -> Self {
match self {
AngleMode::Deg => AngleMode::Rad,
AngleMode::Rad => AngleMode::Deg,
}
}
fn to_rad(self, x: f64) -> f64 {
match self {
AngleMode::Deg => x.to_radians(),
AngleMode::Rad => x,
}
}
fn from_rad(self, x: f64) -> f64 {
match self {
AngleMode::Deg => x.to_degrees(),
AngleMode::Rad => x,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SciError {
DivByZero,
Domain,
Overflow,
Invalid,
}
impl SciError {
pub fn message(self) -> &'static str {
match self {
SciError::DivByZero => "Cannot divide by zero",
SciError::Domain => "Invalid input",
SciError::Overflow => "Overflow",
SciError::Invalid => "Invalid input",
}
}
}
#[derive(Debug, Clone)]
pub struct Engineering {
entry: String,
typing: bool,
formula: String,
expression: String,
done: bool,
error: Option<SciError>,
angle: AngleMode,
/// 2nd function layer (asin instead of sin, …).
second: bool,
memory: f64,
memory_set: bool,
}
impl Default for Engineering {
fn default() -> Self {
Self::new()
}
}
impl Engineering {
pub fn new() -> Self {
Self {
entry: "0".into(),
typing: false,
formula: String::new(),
expression: String::new(),
done: false,
error: None,
angle: AngleMode::Deg,
second: false,
memory: 0.0,
memory_set: false,
}
}
pub fn display(&self) -> &str {
if let Some(err) = self.error {
return err.message();
}
&self.entry
}
pub fn expression(&self) -> &str {
&self.expression
}
pub fn angle_mode(&self) -> AngleMode {
self.angle
}
pub fn second(&self) -> bool {
self.second
}
pub fn has_memory(&self) -> bool {
self.memory_set
}
pub fn cycle_angle(&mut self) {
self.angle = self.angle.cycle();
}
pub fn toggle_second(&mut self) {
self.second = !self.second;
}
pub fn clear_all(&mut self) {
let angle = self.angle;
let mem = (self.memory, self.memory_set);
*self = Self::new();
self.angle = angle;
self.memory = mem.0;
self.memory_set = mem.1;
}
pub fn clear_entry(&mut self) {
self.error = None;
self.entry = "0".into();
self.typing = false;
self.sync_expr();
}
pub fn backspace(&mut self) {
if self.error.is_some() {
self.clear_entry();
return;
}
if self.done {
return;
}
if self.typing {
self.entry.pop();
if self.entry.is_empty() || self.entry == "-" {
self.entry = "0".into();
self.typing = false;
}
self.sync_expr();
return;
}
let mut s = self.formula.trim_end().to_string();
if s.ends_with('(') {
s.pop();
while s
.chars()
.last()
.is_some_and(|c| c.is_ascii_alphanumeric() || c == '_')
{
s.pop();
}
} else if s.chars().last().is_some_and(|c| "+−×÷^)".contains(c)) {
s.pop();
} else {
// pop trailing number / constant
while s
.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == '.' || c == 'π' || c == 'e')
{
s.pop();
}
}
while s.ends_with(' ') {
s.pop();
}
self.formula = s;
self.sync_expr();
}
pub fn input_digit(&mut self, d: char) {
if self.error.is_some() {
self.clear_all();
}
self.begin_entry_if_done();
if !self.typing {
self.entry = d.to_string();
self.typing = true;
} else if self.entry == "0" {
self.entry = d.to_string();
} else if self.entry == "-0" {
self.entry = format!("-{d}");
} else if digit_count(&self.entry) < 16 {
self.entry.push(d);
}
self.sync_expr();
}
pub fn input_dot(&mut self) {
if self.error.is_some() {
self.clear_all();
}
self.begin_entry_if_done();
if !self.typing {
self.entry = "0.".into();
self.typing = true;
} else if !self.entry.contains('.') {
self.entry.push('.');
}
self.sync_expr();
}
pub fn negate(&mut self) {
if self.error.is_some() {
return;
}
if self.done {
if let Ok(v) = self.entry.parse::<f64>() {
self.set_result(-v);
self.done = true;
}
return;
}
if self.typing || self.entry != "0" {
if let Some(stripped) = self.entry.strip_prefix('-') {
self.entry = stripped.to_string();
} else if self.entry != "0" {
self.entry = format!("-{}", self.entry);
}
self.typing = true;
self.sync_expr();
}
}
pub fn set_op(&mut self, op: &str) {
if self.error.is_some() {
return;
}
if self.done {
self.formula.clear();
self.done = false;
self.typing = true;
}
self.flush_entry();
// Replace trailing binary op.
let t = self.formula.trim_end();
if t.chars().last().is_some_and(|c| "+−×÷^".contains(c)) {
while self.formula.ends_with(' ') {
self.formula.pop();
}
self.formula.pop();
while self.formula.ends_with(' ') {
self.formula.pop();
}
}
if !self.formula.is_empty() {
self.formula.push(' ');
}
self.formula.push_str(op);
self.formula.push(' ');
self.entry = "0".into();
self.typing = false;
self.second = false;
self.sync_expr();
}
pub fn paren_open(&mut self) {
if self.error.is_some() {
return;
}
if self.done {
self.formula.clear();
self.done = false;
self.entry = "0".into();
self.typing = false;
}
if self.typing {
self.flush_entry();
let t = self.formula.trim_end();
if t.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == ')' || c == 'π' || c == 'e')
{
self.formula.push_str(" × ");
}
} else {
let t = self.formula.trim_end();
if t.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == ')')
{
self.formula.push_str(" × ");
}
}
self.formula.push('(');
self.entry = "0".into();
self.typing = false;
self.sync_expr();
}
pub fn paren_close(&mut self) {
if self.error.is_some() || self.done {
return;
}
let opens = self.formula.chars().filter(|&c| c == '(').count();
let closes = self.formula.chars().filter(|&c| c == ')').count();
if opens <= closes {
return;
}
if self.typing || (!self.formula.ends_with('(') && self.entry != "0") {
self.flush_entry();
}
self.formula.push(')');
self.entry = "0".into();
self.typing = false;
self.sync_expr();
}
/// Insert a function prefix like `sin(` or constant `π`.
pub fn insert_func(&mut self, name: &str) {
if self.error.is_some() {
self.clear_all();
}
if self.done {
self.formula.clear();
self.done = false;
self.entry = "0".into();
self.typing = false;
}
if self.typing {
self.flush_entry();
let t = self.formula.trim_end();
if t.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == ')' || c == 'π' || c == 'e')
{
self.formula.push_str(" × ");
}
} else {
let t = self.formula.trim_end();
if t.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == ')' || c == 'π' || c == 'e')
{
self.formula.push_str(" × ");
}
}
self.formula.push_str(name);
self.formula.push('(');
self.entry = "0".into();
self.typing = false;
self.second = false;
self.sync_expr();
}
pub fn insert_const(&mut self, name: &str) {
if self.error.is_some() {
self.clear_all();
}
if self.done {
self.formula.clear();
self.done = false;
}
if self.typing {
self.flush_entry();
self.formula.push_str(" × ");
} else {
let t = self.formula.trim_end();
if t.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == ')' || c == 'π' || c == 'e')
{
self.formula.push_str(" × ");
}
}
self.formula.push_str(name);
self.entry = "0".into();
self.typing = false;
self.second = false;
self.sync_expr();
}
pub fn equals(&mut self) {
if self.error.is_some() {
return;
}
let mut src = self.formula.clone();
let ends_paren = src.trim_end().ends_with(')');
if !ends_paren {
if !src.is_empty() && !src.ends_with(' ') && !src.ends_with('(') {
src.push(' ');
}
// If formula ends with func( and entry is being typed, append entry then )
if src.ends_with('(') || self.typing || src.is_empty() {
src.push_str(&self.entry);
} else if !src
.trim_end()
.chars()
.last()
.is_some_and(|c| c.is_ascii_digit() || c == 'π' || c == 'e' || c == ')')
{
src.push_str(&self.entry);
}
} else if src.is_empty() {
src = self.entry.clone();
}
let opens = src.chars().filter(|&c| c == '(').count();
let closes = src.chars().filter(|&c| c == ')').count();
for _ in 0..opens.saturating_sub(closes) {
src.push(')');
}
match eval_sci(&src, self.angle) {
Ok(v) => {
self.expression = format!("{src} =");
self.set_result(v);
self.formula.clear();
self.typing = false;
self.done = true;
self.second = false;
}
Err(e) => self.fail(e),
}
}
pub fn memory_clear(&mut self) {
self.memory = 0.0;
self.memory_set = false;
}
pub fn memory_recall(&mut self) {
if self.error.is_some() {
self.clear_all();
}
if self.done {
self.formula.clear();
self.done = false;
}
self.entry = format_num(self.memory);
self.typing = true;
self.sync_expr();
}
pub fn memory_add(&mut self) {
if let Ok(v) = self.current() {
self.memory += v;
self.memory_set = true;
}
}
pub fn memory_sub(&mut self) {
if let Ok(v) = self.current() {
self.memory -= v;
self.memory_set = true;
}
}
pub fn memory_store(&mut self) {
if let Ok(v) = self.current() {
self.memory = v;
self.memory_set = true;
}
}
fn current(&self) -> Result<f64, SciError> {
self.entry.parse().map_err(|_| SciError::Invalid)
}
fn begin_entry_if_done(&mut self) {
if self.done {
self.formula.clear();
self.expression.clear();
self.done = false;
}
}
fn flush_entry(&mut self) {
if !self.typing && self.entry == "0" && self.formula.trim_end().ends_with(')') {
return;
}
if !self.typing && self.entry == "0" && self.formula.trim_end().ends_with('π') {
return;
}
if !self.typing && self.entry == "0" && self.formula.trim_end().ends_with('e') {
// constant e already there
let t = self.formula.trim_end();
if t.ends_with('e')
&& t.len() > 1
&& !t.chars().nth_back(1).unwrap_or(' ').is_ascii_alphanumeric()
{
return;
}
if t == "e" || t.ends_with(" e") {
return;
}
}
if !self.formula.is_empty()
&& !self.formula.ends_with(' ')
&& !self.formula.ends_with('(')
{
self.formula.push(' ');
}
self.formula.push_str(&self.entry);
self.typing = false;
}
fn sync_expr(&mut self) {
if self.done {
return;
}
let mut s = self.formula.clone();
if self.typing {
if !s.is_empty() && !s.ends_with(' ') && !s.ends_with('(') {
s.push(' ');
}
s.push_str(&self.entry);
}
self.expression = s;
}
fn set_result(&mut self, v: f64) {
if !v.is_finite() {
self.fail(SciError::Overflow);
return;
}
self.entry = format_num(v);
self.error = None;
}
fn fail(&mut self, e: SciError) {
self.error = Some(e);
self.formula.clear();
self.expression.clear();
self.typing = false;
self.done = false;
}
}
fn digit_count(s: &str) -> usize {
s.chars().filter(|c| c.is_ascii_digit()).count()
}
fn format_num(v: f64) -> String {
if !v.is_finite() {
return "0".into();
}
if (v - v.round()).abs() < 1e-10 && v.abs() < 1e15 {
return format!("{}", v.round() as i64);
}
let s = format!("{:.12}", v);
let s = s.trim_end_matches('0').trim_end_matches('.').to_string();
if s.is_empty() || s == "-" {
"0".into()
} else {
s
}
}
// --- expression evaluator ---
#[derive(Clone, Debug)]
enum Tok {
Num(f64),
Op(char), // + − × ÷ ^
LParen,
RParen,
Ident(String),
}
fn eval_sci(src: &str, angle: AngleMode) -> Result<f64, SciError> {
let tokens = tokenize(src)?;
let mut idx = 0;
let v = parse_expr(&tokens, &mut idx, angle)?;
if idx != tokens.len() {
return Err(SciError::Invalid);
}
if !v.is_finite() {
return Err(SciError::Overflow);
}
Ok(v)
}
fn tokenize(src: &str) -> Result<Vec<Tok>, SciError> {
let chars: Vec<char> = src.chars().collect();
let mut i = 0;
let mut out = Vec::new();
while i < chars.len() {
let c = chars[i];
if c.is_whitespace() {
i += 1;
continue;
}
match c {
'+' => {
out.push(Tok::Op('+'));
i += 1;
}
'−' | '-' => {
out.push(Tok::Op('−'));
i += 1;
}
'×' | '*' => {
out.push(Tok::Op('×'));
i += 1;
}
'÷' | '/' => {
out.push(Tok::Op('÷'));
i += 1;
}
'^' => {
out.push(Tok::Op('^'));
i += 1;
}
'(' => {
out.push(Tok::LParen);
i += 1;
}
')' => {
out.push(Tok::RParen);
i += 1;
}
'π' => {
out.push(Tok::Num(PI));
i += 1;
}
'0'..='9' | '.' => {
let start = i;
i += 1;
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
i += 1;
}
let lit: String = chars[start..i].iter().collect();
let v: f64 = lit.parse().map_err(|_| SciError::Invalid)?;
out.push(Tok::Num(v));
}
'a'..='z' | 'A'..='Z' | '_' => {
let start = i;
i += 1;
while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
i += 1;
}
let name: String = chars[start..i].iter().collect();
if name == "e" || name == "E" {
out.push(Tok::Num(E));
} else if name == "pi" || name == "PI" {
out.push(Tok::Num(PI));
} else {
out.push(Tok::Ident(name.to_ascii_lowercase()));
}
}
_ => return Err(SciError::Invalid),
}
}
Ok(out)
}
fn parse_expr(tokens: &[Tok], idx: &mut usize, angle: AngleMode) -> Result<f64, SciError> {
let mut left = parse_term(tokens, idx, angle)?;
while let Some(Tok::Op(op @ ('+' | '−'))) = tokens.get(*idx).cloned() {
*idx += 1;
let right = parse_term(tokens, idx, angle)?;
left = if op == '+' { left + right } else { left - right };
}
Ok(left)
}
fn parse_term(tokens: &[Tok], idx: &mut usize, angle: AngleMode) -> Result<f64, SciError> {
let mut left = parse_power(tokens, idx, angle)?;
while let Some(Tok::Op(op @ ('×' | '÷'))) = tokens.get(*idx).cloned() {
*idx += 1;
let right = parse_power(tokens, idx, angle)?;
left = if op == '×' {
left * right
} else {
if right == 0.0 {
return Err(SciError::DivByZero);
}
left / right
};
}
Ok(left)
}
fn parse_power(tokens: &[Tok], idx: &mut usize, angle: AngleMode) -> Result<f64, SciError> {
let base = parse_unary(tokens, idx, angle)?;
if let Some(Tok::Op('^')) = tokens.get(*idx).cloned() {
*idx += 1;
// right-associative
let exp = parse_power(tokens, idx, angle)?;
let r = base.powf(exp);
if !r.is_finite() {
return Err(SciError::Overflow);
}
Ok(r)
} else {
Ok(base)
}
}
fn parse_unary(tokens: &[Tok], idx: &mut usize, angle: AngleMode) -> Result<f64, SciError> {
if let Some(Tok::Op('−')) = tokens.get(*idx).cloned() {
*idx += 1;
return Ok(-parse_unary(tokens, idx, angle)?);
}
if let Some(Tok::Op('+')) = tokens.get(*idx).cloned() {
*idx += 1;
return parse_unary(tokens, idx, angle);
}
if let Some(Tok::Ident(name)) = tokens.get(*idx).cloned() {
*idx += 1;
// function call: name (
match tokens.get(*idx) {
Some(Tok::LParen) => {
*idx += 1;
let arg = parse_expr(tokens, idx, angle)?;
match tokens.get(*idx) {
Some(Tok::RParen) => *idx += 1,
_ => return Err(SciError::Invalid),
}
apply_func(&name, arg, angle)
}
_ => Err(SciError::Invalid),
}
} else {
parse_primary(tokens, idx, angle)
}
}
fn parse_primary(tokens: &[Tok], idx: &mut usize, angle: AngleMode) -> Result<f64, SciError> {
match tokens.get(*idx).cloned() {
Some(Tok::Num(v)) => {
*idx += 1;
Ok(v)
}
Some(Tok::LParen) => {
*idx += 1;
let v = parse_expr(tokens, idx, angle)?;
match tokens.get(*idx) {
Some(Tok::RParen) => {
*idx += 1;
Ok(v)
}
_ => Err(SciError::Invalid),
}
}
_ => Err(SciError::Invalid),
}
}
fn apply_func(name: &str, x: f64, angle: AngleMode) -> Result<f64, SciError> {
let r = match name {
"sin" => angle.to_rad(x).sin(),
"cos" => angle.to_rad(x).cos(),
"tan" => angle.to_rad(x).tan(),
"asin" => {
if !(-1.0..=1.0).contains(&x) {
return Err(SciError::Domain);
}
angle.from_rad(x.asin())
}
"acos" => {
if !(-1.0..=1.0).contains(&x) {
return Err(SciError::Domain);
}
angle.from_rad(x.acos())
}
"atan" => angle.from_rad(x.atan()),
"sinh" => x.sinh(),
"cosh" => x.cosh(),
"tanh" => x.tanh(),
"ln" => {
if x <= 0.0 {
return Err(SciError::Domain);
}
x.ln()
}
"log" | "log10" => {
if x <= 0.0 {
return Err(SciError::Domain);
}
x.log10()
}
"exp" => x.exp(),
"sqrt" => {
if x < 0.0 {
return Err(SciError::Domain);
}
x.sqrt()
}
"cbrt" => x.cbrt(),
"sq" | "sqr" => x * x,
"cube" => x * x * x,
"inv" => {
if x == 0.0 {
return Err(SciError::DivByZero);
}
1.0 / x
}
"abs" => x.abs(),
"fact" | "factorial" => factorial(x)?,
"tenpow" | "pow10" => 10f64.powf(x),
_ => return Err(SciError::Invalid),
};
if !r.is_finite() {
Err(SciError::Overflow)
} else {
Ok(r)
}
}
fn factorial(x: f64) -> Result<f64, SciError> {
if x < 0.0 || x != x.floor() || x > 170.0 {
return Err(SciError::Domain);
}
let n = x as u32;
let mut r = 1.0;
for i in 2..=n {
r *= f64::from(i);
}
if !r.is_finite() {
Err(SciError::Overflow)
} else {
Ok(r)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sin_90_deg() {
let mut e = Engineering::new();
e.insert_func("sin");
e.input_digit('9');
e.input_digit('0');
e.equals();
assert_eq!(e.display(), "1");
}
#[test]
fn power() {
let mut e = Engineering::new();
e.input_digit('2');
e.set_op("^");
e.input_digit('1');
e.input_digit('0');
e.equals();
assert_eq!(e.display(), "1024");
}
}
+160 -9
View File
@@ -4,6 +4,7 @@
mod convert;
mod crc;
mod engine;
mod engineering;
mod programmer;
use std::cell::RefCell;
@@ -12,6 +13,7 @@ use std::rc::Rc;
use convert::{ConvField, ConvPanel, Converter, Endian, RatioWidth};
use crc::CrcTool;
use engine::{Calculator, Op, StdPanel};
use engineering::Engineering;
use programmer::{Base, ProgOp, Programmer};
use slint::{ComponentHandle, ModelRc, VecModel};
@@ -20,6 +22,7 @@ slint::include_modules!();
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Standard,
Engineering,
Programmer,
Convert,
Crc,
@@ -28,6 +31,7 @@ enum Mode {
struct State {
mode: Mode,
std: Calculator,
eng: Engineering,
prog: Programmer,
conv: Converter,
crc: CrcTool,
@@ -40,6 +44,7 @@ impl Default for State {
Self {
mode: Mode::Standard,
std: Calculator::new(),
eng: Engineering::new(),
prog: Programmer::new(),
conv: Converter::new(),
crc: CrcTool::new(),
@@ -49,12 +54,34 @@ impl Default for State {
}
}
fn main() -> Result<(), slint::PlatformError> {
fn main() {
install_panic_hook();
#[cfg(windows)]
{
// OpenGL/femtovg often fails on remote desktop / old drivers; software is portable.
if std::env::var_os("SLINT_BACKEND").is_none() {
// SAFETY: single-threaded init before any other threads.
unsafe { std::env::set_var("SLINT_BACKEND", "winit-software") };
}
}
if let Err(e) = run_app() {
let msg = format!("rcalc failed to start:\n{e}\n\nTip: try setting SLINT_BACKEND=winit-software");
log_startup_error(&msg);
#[cfg(windows)]
windows_error_dialog(&msg);
eprintln!("{msg}");
std::process::exit(1);
}
}
fn run_app() -> Result<(), slint::PlatformError> {
// Platform must exist first — calling set_xdg_app_id before AppWindow::new
// returns NoPlatform and the Wayland app_id stays unset (KDE shows the "W" icon).
let ui = AppWindow::new()?;
// Must match the .desktop basename (rcalc.desktop → "rcalc"), before show/run.
slint::set_xdg_app_id("rcalc")?;
let _ = slint::set_xdg_app_id("rcalc");
ui.set_app_title(format!("Rcalc-{}", env!("APP_VERSION")).into());
let state = Rc::new(RefCell::new(State::default()));
@@ -84,6 +111,56 @@ fn main() -> Result<(), slint::PlatformError> {
ui.run()
}
fn install_panic_hook() {
let default = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let msg = format!("rcalc panic:\n{info}");
log_startup_error(&msg);
#[cfg(windows)]
windows_error_dialog(&msg);
default(info);
}));
}
fn log_startup_error(msg: &str) {
let path = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.join("rcalc-crash.log")))
.unwrap_or_else(|| std::path::PathBuf::from("rcalc-crash.log"));
let _ = std::fs::write(&path, msg);
}
#[cfg(windows)]
fn windows_error_dialog(msg: &str) {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
fn wide(s: &str) -> Vec<u16> {
OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect()
}
#[link(name = "user32")]
unsafe extern "system" {
fn MessageBoxW(
hwnd: *mut core::ffi::c_void,
text: *const u16,
caption: *const u16,
flags: u32,
) -> i32;
}
let text = wide(msg);
let caption = wide("Rcalc");
unsafe {
MessageBoxW(
std::ptr::null_mut(),
text.as_ptr(),
caption.as_ptr(),
0x10, // MB_ICONERROR
);
}
}
fn bit_row_model(row: &[(bool, u32)]) -> ModelRc<BitInfo> {
let items: Vec<BitInfo> = row
.iter()
@@ -98,9 +175,10 @@ fn bit_row_model(row: &[(bool, u32)]) -> ModelRc<BitInfo> {
fn window_profile(state: &State) -> u32 {
let mode = match state.mode {
Mode::Standard => 0,
Mode::Programmer => 1,
Mode::Convert => 2,
Mode::Crc => 3,
Mode::Engineering => 1,
Mode::Programmer => 2,
Mode::Convert => 3,
Mode::Crc => 4,
};
let bits = if state.show_bits {
state.prog.word_size().bits()
@@ -123,7 +201,8 @@ fn fit_window(ui: &AppWindow, state: &mut State) {
state.win_profile = profile;
let (w, h) = match state.mode {
Mode::Standard => (340.0, 560.0),
Mode::Standard => (360.0, 580.0),
Mode::Engineering => (420.0, 720.0),
Mode::Programmer => {
let bit_h = if state.show_bits {
match state.prog.word_size().bits() {
@@ -159,10 +238,22 @@ fn apply_crc_results(ui: &AppWindow, state: &State) {
fn refresh(ui: &AppWindow, state: &mut State) {
ui.set_mode(match state.mode {
Mode::Standard => 0,
Mode::Programmer => 1,
Mode::Convert => 2,
Mode::Crc => 3,
Mode::Engineering => 1,
Mode::Programmer => 2,
Mode::Convert => 3,
Mode::Crc => 4,
});
ui.set_mode_title(
match state.mode {
Mode::Standard => "Standard",
Mode::Engineering => "Engineering",
Mode::Programmer => "Programmer",
Mode::Convert => "Convert",
Mode::Crc => "CRC",
}
.into(),
);
ui.set_menu_open(false);
fit_window(ui, state);
@@ -176,6 +267,13 @@ fn refresh(ui: &AppWindow, state: &mut State) {
StdPanel::Formula => 1,
});
}
Mode::Engineering => {
ui.set_display_text(state.eng.display().into());
ui.set_expression_text(state.eng.expression().into());
ui.set_has_memory(state.eng.has_memory());
ui.set_eng_angle_label(state.eng.angle_mode().label().into());
ui.set_eng_second(state.eng.second());
}
Mode::Programmer => {
ui.set_display_text(state.prog.display().into());
ui.set_expression_text(state.prog.expression().into());
@@ -246,6 +344,10 @@ fn handle_key(state: &mut State, id: &str) {
state.mode = Mode::Standard;
return;
}
"mode:eng" => {
state.mode = Mode::Engineering;
return;
}
"mode:prog" => {
state.mode = Mode::Programmer;
return;
@@ -263,12 +365,61 @@ fn handle_key(state: &mut State, id: &str) {
match state.mode {
Mode::Standard => handle_standard(&mut state.std, id),
Mode::Engineering => handle_engineering(&mut state.eng, id),
Mode::Programmer => handle_programmer(state, id),
Mode::Convert => handle_convert(&mut state.conv, id),
Mode::Crc => {}
}
}
fn handle_engineering(calc: &mut Engineering, id: &str) {
match id {
"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" => {
calc.input_digit(id.chars().next().unwrap());
}
"." | "," => calc.input_dot(),
"+" => calc.set_op("+"),
"-" | "−" => calc.set_op("−"),
"*" | "×" => calc.set_op("×"),
"/" | "÷" => calc.set_op("÷"),
"^" => calc.set_op("^"),
"=" => calc.equals(),
"(" => calc.paren_open(),
")" => calc.paren_close(),
"CE" => calc.clear_entry(),
"C" | "clear" => calc.clear_all(),
"BS" => calc.backspace(),
"neg" => calc.negate(),
"eng:angle" => calc.cycle_angle(),
"eng:2nd" => calc.toggle_second(),
"eng:pi" => calc.insert_const("π"),
"eng:e" => calc.insert_const("e"),
"eng:sin" => calc.insert_func("sin"),
"eng:cos" => calc.insert_func("cos"),
"eng:tan" => calc.insert_func("tan"),
"eng:asin" => calc.insert_func("asin"),
"eng:acos" => calc.insert_func("acos"),
"eng:atan" => calc.insert_func("atan"),
"eng:sqrt" => calc.insert_func("sqrt"),
"eng:cbrt" => calc.insert_func("cbrt"),
"eng:sq" => calc.insert_func("sq"),
"eng:cube" => calc.insert_func("cube"),
"eng:inv" => calc.insert_func("inv"),
"eng:abs" => calc.insert_func("abs"),
"eng:fact" => calc.insert_func("fact"),
"eng:exp" => calc.insert_func("exp"),
"eng:tenpow" => calc.insert_func("tenpow"),
"eng:log" => calc.insert_func("log"),
"eng:ln" => calc.insert_func("ln"),
"MC" => calc.memory_clear(),
"MR" => calc.memory_recall(),
"M+" => calc.memory_add(),
"M-" => calc.memory_sub(),
"MS" => calc.memory_store(),
_ => {}
}
}
fn handle_standard(calc: &mut Calculator, id: &str) {
match id {
"panel:std" => calc.set_panel(StdPanel::Standard),