0.1.11
This commit is contained in:
@@ -1,2 +1,6 @@
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "x86_64-w64-mingw32-gcc"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-static-libgcc",
|
||||
"-C", "link-arg=-static-libstdc++",
|
||||
]
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
/dist/rcalc.AppDir/
|
||||
/dist/.glibc-build.txt
|
||||
.rcalc_last_bump
|
||||
/target-windows
|
||||
|
||||
Generated
+1
-1
@@ -3904,7 +3904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rcalc"
|
||||
version = "0.1.4"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"slint",
|
||||
"slint-build",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rcalc"
|
||||
version = "0.1.5"
|
||||
version = "0.1.12"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -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
@@ -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),
|
||||
|
||||
+256
-38
@@ -165,6 +165,30 @@ component EqBtn inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
component MenuRow inherits Rectangle {
|
||||
callback clicked();
|
||||
in property <string> label;
|
||||
in property <bool> active;
|
||||
height: 36px;
|
||||
background: root.active ? #2f3a42 : (touch.has-hover ? #323232 : transparent);
|
||||
|
||||
Text {
|
||||
text: root.label;
|
||||
color: root.active ? #60cdff : #e0e0e0;
|
||||
font-size: 14px;
|
||||
font-weight: root.active ? 700 : 500;
|
||||
vertical-alignment: center;
|
||||
horizontal-alignment: left;
|
||||
x: 14px;
|
||||
width: parent.width - 28px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
touch := TouchArea {
|
||||
clicked => { root.clicked(); }
|
||||
}
|
||||
}
|
||||
|
||||
component ModeTab inherits Rectangle {
|
||||
callback clicked();
|
||||
in property <string> label;
|
||||
@@ -303,8 +327,10 @@ export component AppWindow inherits Window {
|
||||
min-height: 520px;
|
||||
background: #202020;
|
||||
|
||||
// 0 = standard, 1 = programmer, 2 = convert, 3 = crc
|
||||
// 0=standard 1=engineering 2=programmer 3=convert 4=crc
|
||||
in-out property <int> mode: 0;
|
||||
in-out property <bool> menu-open: false;
|
||||
in property <string> mode-title: "Standard";
|
||||
|
||||
in property <string> display-text: "0";
|
||||
in property <string> expression-text: "";
|
||||
@@ -312,6 +338,10 @@ export component AppWindow inherits Window {
|
||||
// 0 = classic standard, 1 = formula
|
||||
in property <int> std-panel: 0;
|
||||
|
||||
// Engineering
|
||||
in property <string> eng-angle-label: "DEG";
|
||||
in property <bool> eng-second: false;
|
||||
|
||||
in property <string> hex-text: "0";
|
||||
in property <string> dec-text: "0";
|
||||
in property <string> oct-text: "0";
|
||||
@@ -367,44 +397,130 @@ export component AppWindow inherits Window {
|
||||
root.key-pressed(id);
|
||||
}
|
||||
|
||||
property <bool> hex-digits: root.mode == 1 && root.active-base == 0;
|
||||
property <bool> allow-2: root.mode == 0 || root.active-base <= 2;
|
||||
property <bool> allow-8: root.mode == 0 || root.active-base <= 1;
|
||||
property <bool> allow-a: root.mode == 1 && root.active-base == 0;
|
||||
property <bool> hex-digits: root.mode == 2 && root.active-base == 0;
|
||||
property <bool> allow-2: root.mode == 0 || root.mode == 1 || root.active-base <= 2;
|
||||
property <bool> allow-8: root.mode == 0 || root.mode == 1 || root.active-base <= 1;
|
||||
property <bool> allow-a: root.mode == 2 && root.active-base == 0;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 12px;
|
||||
spacing: 6px;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: 6px;
|
||||
height: 30px;
|
||||
ModeTab {
|
||||
label: "Standard";
|
||||
active: root.mode == 0;
|
||||
clicked => { root.press("mode:std"); }
|
||||
spacing: 10px;
|
||||
height: 34px;
|
||||
|
||||
Rectangle {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 4px;
|
||||
background: menu-touch.has-hover || root.menu-open ? #3b3b3b : transparent;
|
||||
|
||||
// Drawn icon — Unicode ☰ is missing in many Windows fonts.
|
||||
VerticalLayout {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
spacing: 4px;
|
||||
|
||||
Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; }
|
||||
Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; }
|
||||
Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; }
|
||||
}
|
||||
|
||||
menu-touch := TouchArea {
|
||||
clicked => {
|
||||
root.menu-open = !root.menu-open;
|
||||
if (root.menu-open) {
|
||||
mode-menu.show();
|
||||
} else {
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mode-menu := PopupWindow {
|
||||
x: 0;
|
||||
y: 38px;
|
||||
width: 210px;
|
||||
height: 220px;
|
||||
close-on-click: true;
|
||||
|
||||
Rectangle {
|
||||
background: #2a2a2a;
|
||||
border-radius: 6px;
|
||||
border-width: 1px;
|
||||
border-color: #404040;
|
||||
|
||||
VerticalLayout {
|
||||
padding: 6px;
|
||||
spacing: 2px;
|
||||
|
||||
MenuRow {
|
||||
label: "Standard";
|
||||
active: root.mode == 0;
|
||||
clicked => {
|
||||
root.press("mode:std");
|
||||
root.menu-open = false;
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
MenuRow {
|
||||
label: "Engineering";
|
||||
active: root.mode == 1;
|
||||
clicked => {
|
||||
root.press("mode:eng");
|
||||
root.menu-open = false;
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
MenuRow {
|
||||
label: "Programmer";
|
||||
active: root.mode == 2;
|
||||
clicked => {
|
||||
root.press("mode:prog");
|
||||
root.menu-open = false;
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
MenuRow {
|
||||
label: "Convert";
|
||||
active: root.mode == 3;
|
||||
clicked => {
|
||||
root.press("mode:conv");
|
||||
root.menu-open = false;
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
MenuRow {
|
||||
label: "CRC";
|
||||
active: root.mode == 4;
|
||||
clicked => {
|
||||
root.press("mode:crc");
|
||||
root.menu-open = false;
|
||||
mode-menu.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ModeTab {
|
||||
label: "Programmer";
|
||||
active: root.mode == 1;
|
||||
clicked => { root.press("mode:prog"); }
|
||||
}
|
||||
ModeTab {
|
||||
label: "Convert";
|
||||
active: root.mode == 2;
|
||||
clicked => { root.press("mode:conv"); }
|
||||
}
|
||||
ModeTab {
|
||||
label: "CRC";
|
||||
active: root.mode == 3;
|
||||
clicked => { root.press("mode:crc"); }
|
||||
|
||||
Text {
|
||||
text: root.mode-title;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 0 || root.mode == 1: HorizontalLayout {
|
||||
if root.mode == 0 || root.mode == 1 || root.mode == 2: HorizontalLayout {
|
||||
height: 18px;
|
||||
Text {
|
||||
text: root.has-memory && root.mode == 0 ? "M" : "";
|
||||
text: root.has-memory && (root.mode == 0 || root.mode == 1) ? "M" : "";
|
||||
color: #b4b4b4;
|
||||
font-size: 12px;
|
||||
horizontal-alignment: left;
|
||||
@@ -412,7 +528,7 @@ export component AppWindow inherits Window {
|
||||
width: 16px;
|
||||
}
|
||||
Text {
|
||||
text: root.mode == 1 ? root.word-size-label : "";
|
||||
text: root.mode == 2 ? root.word-size-label : "";
|
||||
color: #60cdff;
|
||||
font-size: 11px;
|
||||
vertical-alignment: center;
|
||||
@@ -426,18 +542,18 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 0 || root.mode == 1: Text {
|
||||
if root.mode == 0 || root.mode == 1 || root.mode == 2: Text {
|
||||
text: root.display-text;
|
||||
color: white;
|
||||
font-size: root.mode == 1 ? 28px : 34px;
|
||||
font-size: root.mode == 2 ? 28px : 34px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: right;
|
||||
vertical-alignment: center;
|
||||
height: root.mode == 1 ? 40px : 48px;
|
||||
height: root.mode == 2 ? 40px : 48px;
|
||||
overflow: elide;
|
||||
}
|
||||
|
||||
if root.mode == 3: VerticalLayout {
|
||||
if root.mode == 4: VerticalLayout {
|
||||
spacing: 10px;
|
||||
|
||||
Text {
|
||||
@@ -594,7 +710,7 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 2: VerticalLayout {
|
||||
if root.mode == 3: VerticalLayout {
|
||||
spacing: 8px;
|
||||
|
||||
HorizontalLayout {
|
||||
@@ -956,7 +1072,7 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 1: VerticalLayout {
|
||||
if root.mode == 2: VerticalLayout {
|
||||
spacing: 2px;
|
||||
BaseRow {
|
||||
label: "HEX";
|
||||
@@ -984,7 +1100,7 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 1 && root.show-bits: VerticalLayout {
|
||||
if root.mode == 2 && root.show-bits: VerticalLayout {
|
||||
spacing: 2px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
@@ -1007,6 +1123,108 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 1: HorizontalLayout {
|
||||
spacing: 6px;
|
||||
height: 32px;
|
||||
FuncBtn {
|
||||
label: root.eng-angle-label;
|
||||
clicked => { root.press("eng:angle"); }
|
||||
}
|
||||
MemBtn { label: "MC"; clicked => { root.press("MC"); } }
|
||||
MemBtn { label: "MR"; clicked => { root.press("MR"); } }
|
||||
MemBtn { label: "M+"; clicked => { root.press("M+"); } }
|
||||
MemBtn { label: "M-"; clicked => { root.press("M-"); } }
|
||||
MemBtn { label: "MS"; clicked => { root.press("MS"); } }
|
||||
}
|
||||
|
||||
if root.mode == 1: GridLayout {
|
||||
spacing: 4px;
|
||||
Row {
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "2nd●" : "2nd";
|
||||
clicked => { root.press("eng:2nd"); }
|
||||
}
|
||||
FuncBtn { min-h: 32px; label: "π"; clicked => { root.press("eng:pi"); } }
|
||||
FuncBtn { min-h: 32px; label: "e"; clicked => { root.press("eng:e"); } }
|
||||
FuncBtn { min-h: 32px; label: "C"; clicked => { root.press("C"); } }
|
||||
FuncBtn { min-h: 32px; label: "⌫"; clicked => { root.press("BS"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "sin⁻¹" : "sin";
|
||||
clicked => { root.press(root.eng-second ? "eng:asin" : "eng:sin"); }
|
||||
}
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "cos⁻¹" : "cos";
|
||||
clicked => { root.press(root.eng-second ? "eng:acos" : "eng:cos"); }
|
||||
}
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "tan⁻¹" : "tan";
|
||||
clicked => { root.press(root.eng-second ? "eng:atan" : "eng:tan"); }
|
||||
}
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "³√x" : "√x";
|
||||
clicked => { root.press(root.eng-second ? "eng:cbrt" : "eng:sqrt"); }
|
||||
}
|
||||
FuncBtn { min-h: 32px; label: "÷"; clicked => { root.press("/"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "x³" : "x²";
|
||||
clicked => { root.press(root.eng-second ? "eng:cube" : "eng:sq"); }
|
||||
}
|
||||
FuncBtn { min-h: 32px; label: "1/x"; clicked => { root.press("eng:inv"); } }
|
||||
FuncBtn { min-h: 32px; label: "|x|"; clicked => { root.press("eng:abs"); } }
|
||||
FuncBtn { min-h: 32px; label: "n!"; clicked => { root.press("eng:fact"); } }
|
||||
FuncBtn { min-h: 32px; label: "×"; clicked => { root.press("*"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn { min-h: 32px; label: "x^y"; clicked => { root.press("^"); } }
|
||||
FuncBtn { min-h: 32px; label: "("; clicked => { root.press("("); } }
|
||||
FuncBtn { min-h: 32px; label: ")"; clicked => { root.press(")"); } }
|
||||
FuncBtn { min-h: 32px; label: "CE"; clicked => { root.press("CE"); } }
|
||||
FuncBtn { min-h: 32px; label: "−"; clicked => { root.press("-"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn {
|
||||
min-h: 32px;
|
||||
label: root.eng-second ? "eˣ" : "10ˣ";
|
||||
clicked => { root.press(root.eng-second ? "eng:exp" : "eng:tenpow"); }
|
||||
}
|
||||
DigitBtn { min-h: 32px; label: "7"; clicked => { root.press("7"); } }
|
||||
DigitBtn { min-h: 32px; label: "8"; clicked => { root.press("8"); } }
|
||||
DigitBtn { min-h: 32px; label: "9"; clicked => { root.press("9"); } }
|
||||
FuncBtn { min-h: 32px; label: "+"; clicked => { root.press("+"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn { min-h: 32px; label: "log"; clicked => { root.press("eng:log"); } }
|
||||
DigitBtn { min-h: 32px; label: "4"; clicked => { root.press("4"); } }
|
||||
DigitBtn { min-h: 32px; label: "5"; clicked => { root.press("5"); } }
|
||||
DigitBtn { min-h: 32px; label: "6"; clicked => { root.press("6"); } }
|
||||
DigitBtn { min-h: 32px; label: "±"; clicked => { root.press("neg"); } }
|
||||
}
|
||||
Row {
|
||||
FuncBtn { min-h: 32px; label: "ln"; clicked => { root.press("eng:ln"); } }
|
||||
DigitBtn { min-h: 32px; label: "1"; clicked => { root.press("1"); } }
|
||||
DigitBtn { min-h: 32px; label: "2"; clicked => { root.press("2"); } }
|
||||
DigitBtn { min-h: 32px; label: "3"; clicked => { root.press("3"); } }
|
||||
EqBtn { min-h: 32px; clicked => { root.press("="); } }
|
||||
}
|
||||
Row {
|
||||
DigitBtn { min-h: 32px; label: "0"; clicked => { root.press("0"); } }
|
||||
DigitBtn { min-h: 32px; label: "."; clicked => { root.press("."); } }
|
||||
FuncBtn { min-h: 32px; label: "exp"; clicked => { root.press("eng:exp"); } }
|
||||
Rectangle { min-height: 32px; background: transparent; }
|
||||
Rectangle { min-height: 32px; background: transparent; }
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 0: HorizontalLayout {
|
||||
spacing: 6px;
|
||||
height: 30px;
|
||||
@@ -1074,7 +1292,7 @@ export component AppWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.mode == 1: GridLayout {
|
||||
if root.mode == 2: GridLayout {
|
||||
spacing: 4px;
|
||||
Row {
|
||||
FuncBtn { min-h: 34px; label: root.word-size-label; clicked => { root.press("word"); } }
|
||||
@@ -1140,7 +1358,7 @@ export component AppWindow inherits Window {
|
||||
forward-focus: key-handler;
|
||||
key-handler := FocusScope {
|
||||
key-pressed(event) => {
|
||||
if (root.mode == 3) {
|
||||
if (root.mode == 4) {
|
||||
return reject;
|
||||
}
|
||||
if (event.text == Key.Return) {
|
||||
@@ -1152,7 +1370,7 @@ export component AppWindow inherits Window {
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.Escape) {
|
||||
root.press(root.mode == 0 ? "C" : "clear");
|
||||
root.press(root.mode == 0 || root.mode == 1 ? "C" : "clear");
|
||||
return accept;
|
||||
}
|
||||
if (event.text == Key.Delete) {
|
||||
|
||||
Reference in New Issue
Block a user