Compare commits

..

No commits in common. "master" and "v0.6.0" have entirely different histories.

35 changed files with 237 additions and 7408 deletions

3382
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,24 +11,13 @@ repository = "https://"
keywords = ["tui", "cli", "rpn"] keywords = ["tui", "cli", "rpn"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
[workspace]
members = [
".",
"./rpn_rs_tui/",
"./rpn_rs_gui/",
]
[dependencies] [dependencies]
crossterm = "0.18"
tui = { version = "0.14", default-features = false, features = ['crossterm'] }
serde = {version = "1.0", features = ["derive"]} serde = {version = "1.0", features = ["derive"]}
# confy = "0.4.0" # confy = "0.4.0"
toml = "0.4.2" toml = "0.4.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
rust_decimal = { version = "1.29.1", features=["maths"] }
rust_decimal_macros = "1.29.1"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing-appender = "0.2.2"
tracing = "0.1.37"
egui_extras = "0.22.0"
[dependencies.confy] [dependencies.confy]
# TODO: Update this to v0.5.0 when it finally comes out -- for now, use latest git master # TODO: Update this to v0.5.0 when it finally comes out -- for now, use latest git master

View File

@ -133,7 +133,3 @@ Will I implement these features? I don't know. Lots of these could be done by se
* Bases: Not yet * Bases: Not yet
* Different math operators like `!` or `sum`: If someone asks me to, I guess * Different math operators like `!` or `sum`: If someone asks me to, I guess
* Conditionals: If someone asks me to, I guess * Conditionals: If someone asks me to, I guess
=== Credits
* LCD Solid Font licensed as public domain from: https://www.fontspace.com/lcd-solid-font-f11346

View File

@ -1,6 +0,0 @@
# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=web_sys_unstable_apis"]

View File

@ -1,45 +0,0 @@
name: Github Pages
# By default, runs if you push to master. keeps your deployed app in sync with master branch.
on:
push:
branches:
- master
# to only run when you do a new github release, comment out above part and uncomment the below trigger.
# on:
# release:
# types:
# - published
permissions:
contents: write # for committing to gh-pages branch.
jobs:
build-github-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 # repo checkout
- uses: actions-rs/toolchain@v1 # get rust toolchain for wasm
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
- name: Rust Cache # cache the rust build artefacts
uses: Swatinem/rust-cache@v1
- name: Download and install Trunk binary
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
- name: Build # build
# "${GITHUB_REPOSITORY#*/}" evaluates into the name of the repository
# using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico .
# this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested
# relatively as eframe_template/favicon.ico. if we skip public-url option, the href paths will instead request username.github.io/favicon.ico which
# will obviously return error 404 not found.
run: ./trunk build --release --public-url "${GITHUB_REPOSITORY#*/}"
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
# this option will not maintain any history of your previous pages deployment
# set to false if you want all page build to be committed to your gh-pages branch history
single-commit: true

View File

@ -1,105 +0,0 @@
on: [push, pull_request]
name: CI
env:
# This is required to enable the web_sys clipboard API which egui_web uses
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
RUSTFLAGS: --cfg=web_sys_unstable_apis
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features
check_wasm:
name: Check wasm32
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: wasm32-unknown-unknown
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --lib --target wasm32-unknown-unknown
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
- uses: actions-rs/cargo@v1
with:
command: test
args: --lib
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
trunk:
name: trunk
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
target: wasm32-unknown-unknown
override: true
- name: Download and install Trunk binary
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
- name: Build
run: ./trunk build

View File

@ -1,2 +0,0 @@
/target
/dist

2626
rpn_rs_gui/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
[package]
name = "rpn_rs_gui"
version = "0.1.0"
authors = ["Austen Adler <agadler@austenadler.com>"]
edition = "2021"
rust-version = "1.65"
[dependencies]
rpn_rs = {path=".."}
egui = "0.22.0"
eframe = { version = "0.22.0", default-features = false, features = ["accesskit", "default_fonts", "glow", "persistence"] }
# You only need serde if you want app persistence:
serde = { version = "1", features = ["derive"] }
tracing = "0.1.37"
egui_extras = "0.22.0"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"
tracing-wasm = "0.2"
wasm-bindgen = { version = "0.2.87" }
wasm-bindgen-futures = "0.4"
# TODO: This block was commented
# [profile.release]
# opt-level = 2 # fast and small wasm
# # Optimize all dependencies even in debug builds:
# [profile.dev.package."*"]
# opt-level = 2
# TODO: This block was commented
# [patch.crates-io]
# If you want to use the bleeding edge version of egui and eframe:
# egui = { git = "https://github.com/emilk/egui", branch = "master" }
# eframe = { git = "https://github.com/emilk/egui", branch = "master" }
# If you fork https://github.com/emilk/egui you can test with:
# egui = { path = "../egui/crates/egui" }
# eframe = { path = "../egui/crates/eframe" }

Binary file not shown.

View File

@ -1,75 +0,0 @@
# eframe template
[![dependency status](https://deps.rs/repo/github/emilk/eframe_template/status.svg)](https://deps.rs/repo/github/emilk/eframe_template)
[![Build Status](https://github.com/emilk/eframe_template/workflows/CI/badge.svg)](https://github.com/emilk/eframe_template/actions?workflow=CI)
This is a template repo for [eframe](https://github.com/emilk/egui/tree/master/crates/eframe), a framework for writing apps using [egui](https://github.com/emilk/egui/).
The goal is for this to be the simplest way to get started writing a GUI app in Rust.
You can compile your app natively or for the web, and share it using Github Pages.
## Getting started
Start by clicking "Use this template" at https://github.com/emilk/eframe_template/ or follow [these instructions](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template).
Change the name of the crate: Chose a good name for your project, and change the name to it in:
* `Cargo.toml`
* Change the `package.name` from `eframe_template` to `your_crate`.
* Change the `package.authors`
* `main.rs`
* Change `eframe_template::TemplateApp` to `your_crate::TemplateApp`
* `index.html`
* Change the `<title>eframe template</title>` to `<title>your_crate</title>`. optional.
* `assets/sw.js`
* Change the `'./eframe_template.js'` to `./your_crate.js` (in `filesToCache` array)
* Change the `'./eframe_template_bg.wasm'` to `./your_crate_bg.wasm` (in `filesToCache` array)
### Learning about egui
`src/app.rs` contains a simple example app. This is just to give some inspiration - most of it can be removed if you like.
The official egui docs are at <https://docs.rs/egui>. If you prefer watching a video introduction, check out <https://www.youtube.com/watch?v=NtUkr_z7l84>. For inspiration, check out the [the egui web demo](https://emilk.github.io/egui/index.html) and follow the links in it to its source code.
### Testing locally
Make sure you are using the latest version of stable rust by running `rustup update`.
`cargo run --release`
On Linux you need to first run:
`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
On Fedora Rawhide you need to run:
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel fontconfig-devel`
### Web Locally
You can compile your app to [WASM](https://en.wikipedia.org/wiki/WebAssembly) and publish it as a web page.
We use [Trunk](https://trunkrs.dev/) to build for web target.
1. Install Trunk with `cargo install --locked trunk`.
2. Run `trunk serve` to build and serve on `http://127.0.0.1:8080`. Trunk will rebuild automatically if you edit the project.
3. Open `http://127.0.0.1:8080/index.html#dev` in a browser. See the warning below.
> `assets/sw.js` script will try to cache our app, and loads the cached version when it cannot connect to server allowing your app to work offline (like PWA).
> appending `#dev` to `index.html` will skip this caching, allowing us to load the latest builds during development.
### Web Deploy
1. Just run `trunk build --release`.
2. It will generate a `dist` directory as a "static html" website
3. Upload the `dist` directory to any of the numerous free hosting websites including [GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site).
4. we already provide a workflow that auto-deploys our app to GitHub pages if you enable it.
> To enable Github Pages, you need to go to Repository -> Settings -> Pages -> Source -> set to `gh-pages` branch and `/` (root).
>
> If `gh-pages` is not available in `Source`, just create and push a branch called `gh-pages` and it should be available.
You can test the template app at <https://emilk.github.io/eframe_template/>.
## Updating egui
As of 2022, egui is in active development with frequent releases with breaking changes. [eframe_template](https://github.com/emilk/eframe_template/) will be updated in lock-step to always use the latest version of egui.
When updating `egui` and `eframe` it is recommended you do so one version at the time, and read about the changes in [the egui changelog](https://github.com/emilk/egui/blob/master/CHANGELOG.md) and [eframe changelog](https://github.com/emilk/egui/blob/master/crates/eframe/CHANGELOG.md).

View File

@ -1,2 +0,0 @@
[build]
filehash = false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,28 +0,0 @@
{
"name": "egui Template PWA",
"short_name": "egui-template-pwa",
"icons": [
{
"src": "./icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "./maskable_icon_x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "./icon-1024.png",
"sizes": "1024x1024",
"type": "image/png"
}
],
"lang": "en-US",
"id": "/index.html",
"start_url": "./index.html",
"display": "standalone",
"background_color": "white",
"theme_color": "white"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

View File

@ -1,30 +0,0 @@
var cacheName = 'egui-template-pwa';
var filesToCache = [
'./',
'./index.html',
'./eframe_template.js',
'./eframe_template_bg.wasm',
];
// self.addEventListener('keydown', function (e) {
// console.log("Got keydown", e);
// e.preventDefault();
// })
/* Start the service worker and cache all of the app's content */
self.addEventListener('install', function (e) {
e.waitUntil(
caches.open(cacheName).then(function (cache) {
return cache.addAll(filesToCache);
})
);
});
/* Serve cached content when offline */
self.addEventListener('fetch', function (e) {
e.respondWith(
caches.match(e.request).then(function (response) {
return response || fetch(e.request);
})
);
});

View File

@ -1,11 +0,0 @@
#!/usr/bin/env bash
# This scripts runs various CI-like checks in a convenient way.
set -eux
cargo check --workspace --all-targets
cargo check --workspace --all-features --lib --target wasm32-unknown-unknown
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::all
cargo test --workspace --all-targets --all-features
cargo test --workspace --doc
trunk build

View File

@ -1,140 +0,0 @@
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<!-- change this to your project name -->
<title>RPN RS2</title>
<!-- config for our rust wasm binary. go to https://trunkrs.dev/assets/#rust for more customization -->
<link data-trunk rel="rust" data-wasm-opt="2" />
<!-- this is the base url relative to which other urls will be constructed. trunk will insert this from the public-url option -->
<base data-trunk-public-url />
<link data-trunk rel="icon" href="assets/favicon.ico">
<link data-trunk rel="copy-file" href="assets/sw.js" />
<link data-trunk rel="copy-file" href="assets/manifest.json" />
<link data-trunk rel="copy-file" href="assets/icon-1024.png" />
<link data-trunk rel="copy-file" href="assets/icon-256.png" />
<link data-trunk rel="copy-file" href="assets/icon_ios_touch_192.png" />
<link data-trunk rel="copy-file" href="assets/maskable_icon_x512.png" />
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="icon_ios_touch_192.png">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
body {
/* Light mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #909090;
}
@media (prefers-color-scheme: dark) {
body {
/* Dark mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #404040;
}
}
/* Allow canvas to fill entire web page: */
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
/* Position canvas in center-top: */
canvas {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 0%);
}
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
/* ---------------------------------------------- */
/* Loading animation from https://loading.io/css/ */
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<!-- The WASM code will resize the canvas dynamically -->
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
<canvas id="the_canvas_id"></canvas>
<!--Register Service Worker. this will cache the wasm / js scripts for offline use (for PWA functionality). -->
<!-- Force refresh (Ctrl + F5) to load the latest files instead of cached files -->
<script>
// We disable caching during development so that we always view the latest version.
if ('serviceWorker' in navigator && window.location.hash !== "#dev") {
window.addEventListener('load', function () {
navigator.serviceWorker.register('sw.js');
});
}
</script>
</body>
</html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->

View File

@ -1,539 +0,0 @@
use std::collections::HashSet;
use egui::{
Align, Button, Color32, Direction, FontData, FontDefinitions, FontFamily, FontId, Frame, Grid,
Id, Key, Label, Layout, Margin, PointerButton, Pos2, Rect, RichText, Rounding, ScrollArea,
Sense, Stroke, Style, TouchId, TouchPhase, Vec2,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use rpn_rs::calc::{
entries::CalculatorEntry,
errors::CalculatorError,
types::{CalculatorDisplayMode, CalculatorState},
Calculator,
};
use tracing::{error, info};
mod buttons;
use buttons::CalculatorButton;
use self::buttons::{BUTTON_LAYOUT, BUTTON_LAYOUT_SETTINGS};
const DEFAULT_FONT_SIZE: f32 = 45.0;
const STACK_FONT_SIZE: f32 = 25.0;
const DEFAULT_FONT: FontId = FontId::monospace(DEFAULT_FONT_SIZE);
// const BUTTON_SIZE_WIDTH: Size = Size::remainder();
// const BUTTON_SIZE_HEIGHT: Size = Size::remainder();
// const BUTTON_SIZE: Vec2 = Vec2 { x: 67.0, y: 60.0 };
const BUTTON_SPACING: Vec2 = Vec2 { x: 0.0, y: 0.0 };
// const BUTTON_PADDING: Vec2 = Vec2 { x: 24.0, y: 5.0 };
const BUTTON_PADDING: Vec2 = Vec2 { x: 0.0, y: 0.0 };
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct TemplateApp {
calculator: CalculatorInner,
#[serde(skip)]
latest_error: Option<CalculatorError>,
#[serde(skip)]
touches_down: HashSet<ClickTapId>,
#[serde(skip)]
new_touches: Vec<Pos2>,
}
#[derive(serde::Deserialize, serde::Serialize, Default)]
#[serde(default)]
pub struct CalculatorInner {
calculator: Calculator,
#[serde(skip)]
error_state: ErrorState,
}
impl CalculatorInner {
fn calculator_input(&mut self, c: char) {
let action = if c == '$' {
self.calculator.backspace()
} else {
self.calculator.take_input(c)
};
if let Err(e) = action {
self.error_state.errored(e);
} else {
self.error_state.success();
}
}
}
/// Phase of a click or tap
#[derive(Debug, Clone, Copy)]
enum ClickPhase {
Start,
End,
Ignored,
}
/// ID of a click or tap
///
/// Required because taps have IDs, but clicks don't
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
enum ClickTapId {
Tap(u64),
Click(u8),
}
impl From<&TouchId> for ClickTapId {
fn from(value: &TouchId) -> Self {
Self::Tap(value.0)
}
}
impl From<&PointerButton> for ClickTapId {
fn from(value: &PointerButton) -> Self {
Self::Click(*value as u8)
}
}
impl From<&TouchPhase> for ClickPhase {
fn from(value: &TouchPhase) -> Self {
match value {
TouchPhase::Start => Self::Start,
TouchPhase::End => Self::End,
TouchPhase::Move | TouchPhase::Cancel => Self::Ignored,
}
}
}
impl Default for TemplateApp {
fn default() -> Self {
Self {
calculator: CalculatorInner::default(),
latest_error: None,
touches_down: HashSet::new(),
new_touches: Vec::new(),
}
}
}
impl TemplateApp {
/// Called once before the first frame.
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// This is also where you can customize the look and feel of egui using
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
let ret;
if let Some(storage) = cc.storage {
ret = eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
} else {
ret = Default::default()
}
TemplateApp::initialize_fonts(&cc.egui_ctx);
ret
}
fn draw_stack(&mut self, ui: &mut egui::Ui) {
TableBuilder::new(ui)
.stick_to_bottom(true)
// .column(Column::auto() )
.column(Column::remainder())
// .header(20.0, |mut header| {
// header.col(|ui| {
// ui.heading("Entry");
// });
// header.col(|ui| {
// ui.heading("Value");
// });
// })
.body(|mut body| {
for (_idx, entry) in self.calculator.calculator.stack.iter().enumerate().rev() {
body.row(30.0, |mut row| {
// row.col(|ui| {
// ui.add(Label::new(
// RichText::new(format!("{idx}"))
// .background_color(Color32::RED)
// .font(DEFAULT_FONT)
// .size(STACK_FONT_SIZE)
// .color(Color32::WHITE)
// ));
// });
row.col(|ui| {
ui.add(Label::new(
RichText::new(
entry.format_entry(&self.calculator.calculator.display_mode),
)
.background_color(Color32::DARK_GRAY)
.font(DEFAULT_FONT)
.size(STACK_FONT_SIZE)
.color(Color32::WHITE),
));
});
});
}
});
}
fn draw_error(&mut self, ui: &mut egui::Ui) {
if let Some(ref e) = self.latest_error {
ui.label(
RichText::new(e.to_string())
.font(DEFAULT_FONT)
.size(STACK_FONT_SIZE)
.background_color(Color32::RED)
.color(Color32::WHITE),
);
}
}
fn draw_input(&mut self, ui: &mut egui::Ui) {
ui.painter()
.rect_filled(ui.available_rect_before_wrap(), 0.0, Color32::LIGHT_GREEN);
ui.label(
RichText::new(self.calculator.calculator.get_l())
.color(Color32::BLACK)
.font(FontId::monospace(30.0)),
);
}
fn draw_buttons(&mut self, ui: &mut egui::Ui) {
let button_layout = match self.calculator.calculator.state {
CalculatorState::Normal => BUTTON_LAYOUT,
CalculatorState::WaitingForConstant => return,
CalculatorState::WaitingForMacro => return,
CalculatorState::WaitingForRegister(_) => return,
CalculatorState::WaitingForSetting => BUTTON_LAYOUT_SETTINGS,
};
StripBuilder::new(ui)
.sizes(Size::exact(60.0), button_layout.len())
// .sizes(Size::remainder(), button_layout.len())
.vertical(|mut strip| {
for row in button_layout.iter() {
strip.strip(|builder| {
builder
.sizes(Size::remainder(), row.len())
.horizontal(|mut strip| {
for button_definition in row.iter() {
strip.cell(|ui| {
let sense = Sense::click();
let label = RichText::new(button_definition.value)
.font(DEFAULT_FONT)
.color(Color32::WHITE)
.background_color(Color32::BLACK);
ui.painter().rect_filled(
ui.available_rect_before_wrap(),
0.0,
Color32::GOLD,
);
ui.style_mut().spacing.window_margin = Margin {
left: 0.0,
right: 0.0,
top: 0.0,
bottom: 0.0,
};
let max_size = {
let ret = ui.available_rect_before_wrap();
Vec2 {
x: ret.width(),
y: ret.height(),
}
};
ui.vertical_centered(|ui| {
ui.add_enabled(
button_definition.enabled,
Button::new(label)
.stroke(Stroke::NONE)
.rounding(Rounding::none())
.fill(Color32::from_gray(0x12))
.min_size(max_size)
.sense(sense),
)
});
// let button = {
// let ret = ui.add_enabled(
// button_definition.enabled,
// Button::new(label)
// .stroke(Stroke::NONE)
// .rounding(Rounding::none())
// .fill(Color32::from_gray(0x12))
// .min_size(max_size)
// .sense(sense),
// );
// ret
// };
// Check if any new touches intersect with this button
let max_rect = ui.max_rect();
for touch in self.new_touches.iter() {
if max_rect.contains(*touch) {
self.calculator
.calculator_input(button_definition.value);
}
}
});
}
});
});
}
});
}
fn layout() -> Layout {
Layout::from_main_dir_and_cross_align(Direction::TopDown, Align::Center)
.with_cross_justify(true)
}
fn handle_input(&mut self, i: &egui::InputState) {
if i.events.is_empty() {
return;
}
for e in i.events.iter() {
match e {
egui::Event::Text(t) => {
self.calculator.calculator_input(t.chars().next().unwrap());
}
// egui::Event::Key {
// key: Key::ArrowLeft,
// pressed: true,
// ..
// } => {
// self.calculator.calculator_input('<');
// }
egui::Event::Key {
key: Key::ArrowRight,
pressed: true,
..
} => {
self.calculator.calculator_input('>');
}
egui::Event::Key {
key: Key::Enter,
pressed: true,
..
} => {
self.calculator.calculator_input(' ');
}
egui::Event::Touch {
device_id: _,
id,
phase,
pos,
force: _,
} => {
self.handle_touch_event(phase.into(), pos.clone(), id);
}
egui::Event::PointerButton {
pos,
button,
pressed,
modifiers: _,
} => self.handle_touch_event(
if *pressed {
ClickPhase::Start
} else {
ClickPhase::End
},
*pos,
button,
),
egui::Event::Copy
| egui::Event::Cut
| egui::Event::Paste(_)
| egui::Event::Key {
key: _,
pressed: _,
repeat: _,
modifiers: _,
}
| egui::Event::PointerMoved(_)
| egui::Event::PointerGone
| egui::Event::Scroll(_)
| egui::Event::Zoom(_)
| egui::Event::CompositionStart
| egui::Event::CompositionUpdate(_)
| egui::Event::CompositionEnd(_)
| egui::Event::AccessKitActionRequest(_)
| egui::Event::MouseWheel {
unit: _,
delta: _,
modifiers: _,
}
| egui::Event::WindowFocused(_) => continue,
}
}
}
fn handle_touch_event(&mut self, phase: ClickPhase, pos: Pos2, id: impl Into<ClickTapId>) {
let id = id.into();
match phase {
ClickPhase::Start => {
// TODO: This can be way better
// If this is a brand new touch
if !self.touches_down.contains(&id)
// And it isn't a duplicate
// This can occur on phones where touches can be pointer events
&& !self.new_touches.contains(&pos)
{
self.new_touches.push(pos);
}
self.touches_down.insert(id);
}
ClickPhase::End => {
self.touches_down.remove(&id);
}
ClickPhase::Ignored => {}
}
}
fn initialize_fonts(egui_ctx: &egui::Context) {
info!("Initializing fonts");
let mut fonts = FontDefinitions::default();
fonts.font_data.insert(
"LCD_Solid".to_owned(),
FontData::from_static(include_bytes!("../LcdSolid-VPzB.ttf")),
);
fonts
.families
.get_mut(&FontFamily::Monospace)
.unwrap()
.insert(0, "LCD_Solid".to_owned());
fonts
.families
.get_mut(&FontFamily::Monospace)
.unwrap()
.push("LCD_Solid".to_owned());
egui_ctx.set_fonts(fonts);
}
}
impl eframe::App for TemplateApp {
/// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let Self { .. } = self;
let mut style: Style = (*ctx.style()).clone();
style.spacing.button_padding = BUTTON_PADDING;
ctx.set_style(style);
self.calculator.error_state = ErrorState::default();
egui::CentralPanel::default().show(ctx, |ui| {
// ui.with_layout(self.layout, add_contents)
ui.input(|i: &egui::InputState| self.handle_input(i));
StripBuilder::new(ui)
// header
.size(Size::exact(65.0))
// Stack
// .size(Size::remainder().at_most(35.0))
.size(Size::remainder().at_least(40.0))
// Input
.size(Size::exact(40.0))
// Error
.size(Size::exact(20.0))
// Buttons
.size(Size::exact(450.0))
.vertical(|mut strip| {
strip.cell(|ui| {
ui.heading("rpn_rs_gui");
ui.hyperlink("https://gitea.austen-wares.com/stonewareslord/rpn_rs");
});
strip.cell(|ui| {
// Stack
ui.painter().rect_filled(
ui.available_rect_before_wrap(),
0.0,
Color32::LIGHT_GRAY,
);
self.draw_stack(ui);
});
strip.cell(|ui| {
self.draw_input(ui);
// Reset the error state and update `self.latest_error` if required
match std::mem::take(&mut self.calculator.error_state) {
ErrorState::NoModify => {}
ErrorState::Errored(e) => self.latest_error = Some(e),
ErrorState::Clear => self.latest_error = None,
}
});
strip.cell(|ui| {
// Error bar
self.draw_error(ui);
});
strip.cell(|ui| {
// Buttons
self.draw_buttons(ui);
});
})
});
self.new_touches.drain(..);
}
}
enum ErrorState {
// Do not touch the state of the error
NoModify,
// There was an error; this was the latest value
Errored(CalculatorError),
// We should clear the error at the end
Clear,
}
impl ErrorState {
fn errored(&mut self, e: CalculatorError) {
error!("Calculator input error: {e:?}");
match self {
Self::NoModify | ErrorState::Clear => *self = Self::Errored(e),
Self::Errored(_) => {
// We already errored, so do not change anything
}
}
}
fn success(&mut self) {
match self {
Self::NoModify => {
// There was a success and there was no previous failure, so clear any error value
*self = Self::Clear
}
Self::Errored(_) => {
// There was a previous error. We can't clear the error
}
Self::Clear => {
// The calculator error was already removed
}
}
}
}
impl Default for ErrorState {
fn default() -> ErrorState {
ErrorState::NoModify
}
}

View File

@ -1,108 +0,0 @@
use rpn_rs::calc::Calculator;
pub struct CalculatorButton {
pub value: char,
pub help: Option<&'static str>,
pub enabled: bool,
}
impl CalculatorButton {
const fn new(value: char, help: Option<&'static str>, enabled: bool) -> Self {
Self {
value,
help,
enabled,
}
}
}
pub(crate) const BUTTON_LAYOUT_SETTINGS: &[&[CalculatorButton]] = &[
&[CalculatorButton::new('q', Some("Exit"), true)],
&[
CalculatorButton::new('d', Some("Degrees"), true),
CalculatorButton::new('r', Some("Radians"), true),
CalculatorButton::new('g', Some("Grads"), true),
],
&[
CalculatorButton::new('_', Some("Default"), true),
CalculatorButton::new(',', Some("Comma separated"), true),
CalculatorButton::new(' ', Some("Space separated"), true),
],
&[
CalculatorButton::new('s', Some("Scientific"), true),
CalculatorButton::new('S', Some("Scientific (stack precision)"), true),
],
&[
CalculatorButton::new('e', Some("Engineering"), true),
CalculatorButton::new('E', Some("Engineering (stack precision)"), true),
],
&[
CalculatorButton::new('f', Some("Fixed"), true),
CalculatorButton::new('F', Some("Fixed (stack precision)"), true),
],
// CalculatorButton::new('w', Some("Do not write settings and stack on quit (default)"), true),
// CalculatorButton::new('W', Some("Write stack and settings on quit"), true),
// CalculatorButton::new('L', Some("Left align"), true),
// CalculatorButton::new('R', Some("Right align"), true),
];
pub(crate) const BUTTON_LAYOUT: &[&[CalculatorButton]] = &[
&[
// CalculatorButton::new('s', "Sin", true),
CalculatorButton::new('\\', Some("Drop"), true),
// TODO: Settings buttons
CalculatorButton::new('@', Some("Settings"), true),
// CalculatorButton::new(' ', Some("Enter"), true),
CalculatorButton::new('>', Some("Swap"), true),
CalculatorButton::new('$', Some("Backspace"), true),
],
&[
// CalculatorButton::new('|', "AbsoluteValue", true),
CalculatorButton::new('^', Some("Pow"), true),
CalculatorButton::new('U', Some("Redo"), true),
CalculatorButton::new('u', Some("Undo"), true),
CalculatorButton::new('L', Some("Ln"), true),
// CalculatorButton::new('l', Some("Log"), true),
],
&[
// CalculatorButton::new('c', "Cos", true),
CalculatorButton::new('v', Some("Sqrt"), true),
// CalculatorButton::new('%', Some("Modulo"), true),
CalculatorButton::new('n', Some("Negate"), true),
CalculatorButton::new('i', Some("Inverse"), true),
CalculatorButton::new('/', Some("Divide"), true),
],
&[
// CalculatorButton::new('t', "Tan", true),
CalculatorButton::new('7', None, true),
CalculatorButton::new('8', None, true),
CalculatorButton::new('9', None, true),
CalculatorButton::new('*', Some("Multiply"), true),
],
&[
// CalculatorButton::new('S', "ASin", true),
CalculatorButton::new('4', None, true),
CalculatorButton::new('5', None, true),
CalculatorButton::new('6', None, true),
CalculatorButton::new('-', Some("Subtract"), true),
],
&[
// CalculatorButton::new('C', "ACos", true),
CalculatorButton::new('1', None, true),
CalculatorButton::new('2', None, true),
CalculatorButton::new('3', None, true),
CalculatorButton::new('+', Some("Add"), true),
],
&[
// CalculatorButton::new('T', "ATan", true),
CalculatorButton::new('0', None, true),
CalculatorButton::new('.', Some("Decimal"), true),
CalculatorButton::new(' ', Some("Return"), true),
CalculatorButton::new(' ', Some("Return"), true),
],
// CalculatorButton::new ( '?', "IntegerDivide", true),
// CalculatorButton::new ( 'V', "BuildVector", true),
// CalculatorButton::new ( 'M', "BuildMatrix", true),
// CalculatorButton::new ( '_', "Deconstruct", true),
// CalculatorButton::new ( ', true)', "Transpose"),
];

View File

@ -1,4 +0,0 @@
#![warn(clippy::all, rust_2018_idioms)]
mod app;
pub use app::TemplateApp;

View File

@ -1,39 +0,0 @@
#![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init();
let native_options = eframe::NativeOptions::default();
eframe::run_native(
"RPN RS2",
native_options,
Box::new(|cc| Box::new(rpn_rs_gui::TemplateApp::new(cc))),
)
}
// when compiling to web using trunk.
#[cfg(target_arch = "wasm32")]
fn main() {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
let web_options = eframe::WebOptions::default();
wasm_bindgen_futures::spawn_local(async {
eframe::WebRunner::new()
.start(
"the_canvas_id", // hardcode it
web_options,
Box::new(|cc| Box::new(rpn_rs_gui::TemplateApp::new(cc))),
)
.await
.expect("failed to start eframe");
});
}

View File

@ -1,14 +0,0 @@
[package]
name = "rpn_rs_tui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rpn_rs = {path=".."}
crossterm = { version = "0.18" }
tui = { version = "0.14", default-features = false, features = ["crossterm"] }
tracing-appender = "0.2.2"
tracing-subscriber = "0.3.17"
tracing = "0.1.37"

View File

@ -1,12 +1,9 @@
use rust_decimal::prelude::ToPrimitive;
pub mod entries; pub mod entries;
use crate::constants;
use lazy_static::lazy_static; use lazy_static::lazy_static;
pub mod errors; pub mod errors;
pub mod operations; pub mod operations;
pub mod types; pub mod types;
use crate::calc::entries::CalculatorEntry; use crate::calc::entries::CalculatorEntry;
use rust_decimal::Decimal;
use confy::{load, store}; use confy::{load, store};
use entries::{Entry, Matrix, Number, Vector}; use entries::{Entry, Matrix, Number, Vector};
@ -200,7 +197,7 @@ impl Default for Calculator {
CalculatorConstant { CalculatorConstant {
help: String::from("Tau (2pi)"), help: String::from("Tau (2pi)"),
value: Entry::Number(Number { value: Entry::Number(Number {
value: constants::TAU, value: std::f64::consts::TAU,
}), }),
}, },
), ),
@ -209,7 +206,7 @@ impl Default for Calculator {
CalculatorConstant { CalculatorConstant {
help: String::from("Euler's Number e"), help: String::from("Euler's Number e"),
value: Entry::Number(Number { value: Entry::Number(Number {
value: constants::E, value: std::f64::consts::E,
}), }),
}, },
), ),
@ -218,7 +215,7 @@ impl Default for Calculator {
CalculatorConstant { CalculatorConstant {
help: String::from("Pi"), help: String::from("Pi"),
value: Entry::Number(Number { value: Entry::Number(Number {
value: constants::PI, value: std::f64::consts::PI,
}), }),
}, },
), ),
@ -482,10 +479,7 @@ impl Calculator {
return Ok(false); return Ok(false);
} }
let f = self let f = self.l.parse::<f64>().or(Err(CalculatorError::ParseError))?;
.l
.parse::<Decimal>()
.or(Err(CalculatorError::ParseError))?;
self.push(Entry::Number(Number { value: f }))?; self.push(Entry::Number(Number { value: f }))?;
self.l.clear(); self.l.clear();
Ok(true) Ok(true)
@ -526,11 +520,14 @@ impl Calculator {
Entry::Matrix(_) | Entry::Vector(_) => return Err(CalculatorError::TypeMismatch), Entry::Matrix(_) | Entry::Vector(_) => return Err(CalculatorError::TypeMismatch),
Entry::Number(Number { value }) => value, Entry::Number(Number { value }) => value,
}; };
// Ensure this can be cast to a usize
match f.to_usize() { if !f.is_finite() || f.is_sign_negative() {
Some(u) => Ok((u, entry)), return Err(CalculatorError::ArithmeticError);
None => Err(CalculatorError::ArithmeticError),
} }
#[allow(clippy::cast_sign_loss)]
let u = f as usize;
Ok((u, entry))
} }
/// Pops a precision instead of an Entry. Precisions are of type usize /// Pops a precision instead of an Entry. Precisions are of type usize
pub fn pop_precision(&mut self) -> CalculatorResult<usize> { pub fn pop_precision(&mut self) -> CalculatorResult<usize> {

View File

@ -255,8 +255,6 @@ impl fmt::Display for Entry {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
fn valid_square_matrix() -> Entry { fn valid_square_matrix() -> Entry {
Entry::Matrix(Matrix { Entry::Matrix(Matrix {
@ -268,25 +266,25 @@ mod tests {
Vector { Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { value: dec!(2.0) }, Number { value: 2.0_f64 },
Number { value: dec!(-3.0) }, Number { value: -3.0_f64 },
], ],
}, },
Vector { Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(4.0) }, Number { value: 4.0_f64 },
Number { value: dec!(-5.0) }, Number { value: -5.0_f64 },
Number { value: dec!(0.0) }, Number { value: 0.0_f64 },
], ],
}, },
Vector { Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(-7.0) }, Number { value: -7.0_f64 },
Number { value: dec!(8.0) }, Number { value: 8.0_f64 },
Number { value: dec!(9.0) }, Number { value: 9.0_f64 },
], ],
}, },
], ],
@ -303,25 +301,25 @@ mod tests {
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(-1.0) }, Number { value: -1.0_f64 },
Number { value: dec!(-2.0) }, Number { value: -2.0_f64 },
Number { value: dec!(3.0) }, Number { value: 3.0_f64 },
], ],
}), }),
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(-4.0) }, Number { value: -4.0_f64 },
Number { value: dec!(5.0) }, Number { value: 5.0_f64 },
Number { value: dec!(-0.0) }, Number { value: -0.0_f64 },
], ],
}), }),
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(7.0) }, Number { value: 7.0_f64 },
Number { value: dec!(-8.0) }, Number { value: -8.0_f64 },
Number { value: dec!(-9.0) }, Number { value: -9.0_f64 },
], ],
}), }),
]), ]),
@ -333,25 +331,25 @@ mod tests {
// Entry::Vector(Vector { // Entry::Vector(Vector {
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number { value: dec!(1.0) }, // Number { value: 1.0_f64 },
// Number { value: dec!(2.0) }, // Number { value: 2.0_f64 },
// Number { value: dec!(3.0) }, // Number { value: 3.0_f64 },
// ], // ],
// }), // }),
// Entry::Vector(Vector { // Entry::Vector(Vector {
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number { value: dec!(4.0) }, // Number { value: 4.0_f64 },
// Number { value: dec!(5.0) }, // Number { value: 5.0_f64 },
// Number { value: dec!(0.0) }, // Number { value: 0.0_f64 },
// ], // ],
// }), // }),
// Entry::Vector(Vector { // Entry::Vector(Vector {
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number { value: dec!(7.0) }, // Number { value: 7.0_f64 },
// Number { value: dec!(8.0) }, // Number { value: 8.0_f64 },
// Number { value: dec!(9.0) }, // Number { value: 9.0_f64 },
// ], // ],
// }), // }),
// ]), // ]),
@ -360,23 +358,23 @@ mod tests {
// Entry::Vector(Vector { // Entry::Vector(Vector {
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number{value: dec!(1.0},) // Number{value: 1.0_f64},
// Number{value: dec!(2.0},) // Number{value: 2.0_f64},
// Number{value: dec!(-3.0},) // Number{value: -3.0_f64},
// ]}), // ]}),
// Entry::Vector (Vector{ // Entry::Vector (Vector{
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number{value: dec!(4.0},) // Number{value: 4.0_f64},
// Number{value: dec!(-5.0},) // Number{value: -5.0_f64},
// Number{value: dec!(0.0},) // Number{value: 0.0_f64},
// ]}), // ]}),
// Entry::Vector (Vector{ // Entry::Vector (Vector{
// direction: VectorDirection::Column, // direction: VectorDirection::Column,
// values: vec![ // values: vec![
// Number{value: dec!(-7.0},) // Number{value: -7.0_f64},
// Number{value: dec!(8.0},) // Number{value: 8.0_f64},
// Number{value: dec!(9.0},) // Number{value: 9.0_f64},
// ])), // ])),
( (
"transpose", "transpose",
@ -385,25 +383,25 @@ mod tests {
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { value: dec!(4.0) }, Number { value: 4.0_f64 },
Number { value: dec!(-7.0) }, Number { value: -7.0_f64 },
], ],
}), }),
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(2.0) }, Number { value: 2.0_f64 },
Number { value: dec!(-5.0) }, Number { value: -5.0_f64 },
Number { value: dec!(8.0) }, Number { value: 8.0_f64 },
], ],
}), }),
Entry::Vector(Vector { Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(-3.0) }, Number { value: -3.0_f64 },
Number { value: dec!(0.0) }, Number { value: 0.0_f64 },
Number { value: dec!(9.0) }, Number { value: 9.0_f64 },
], ],
}), }),
]), ]),
@ -418,9 +416,9 @@ mod tests {
vectors: vec![Vector { vectors: vec![Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { value: dec!(100.0) }, Number { value: 100.0_f64 },
Number { value: dec!(64.0) }, Number { value: 64.0_f64 },
], ],
}], }],
}) })
@ -428,9 +426,9 @@ mod tests {
Matrix::from(&[Entry::Vector(Vector { Matrix::from(&[Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { value: dec!(10.0) }, Number { value: 10.0_f64 },
Number { value: dec!(8.0) }, Number { value: 8.0_f64 },
], ],
})]), })]),
), ),
@ -445,10 +443,10 @@ mod tests {
vectors: vec![Vector { vectors: vec![Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { value: dec!(100.0) }, Number { value: 100.0_f64 },
Number { Number {
value: dec!(100_000.0), value: 100_000.0_f64,
}, },
], ],
}], }],
@ -457,9 +455,9 @@ mod tests {
Matrix::from(&[Entry::Vector(Vector { Matrix::from(&[Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(0.0) }, Number { value: 0.0_f64 },
Number { value: dec!(2.0) }, Number { value: 2.0_f64 },
Number { value: dec!(5.0) }, Number { value: 5.0_f64 },
], ],
})]), })]),
), ),
@ -473,9 +471,9 @@ mod tests {
vectors: vec![Vector { vectors: vec![Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![ values: vec![
Number { value: dec!(1.0) }, Number { value: 1.0_f64 },
Number { Number {
value: constants::E, value: std::f64::consts::E,
}, },
], ],
}], }],
@ -483,7 +481,7 @@ mod tests {
.ln(), .ln(),
Matrix::from(&[Entry::Vector(Vector { Matrix::from(&[Entry::Vector(Vector {
direction: VectorDirection::Column, direction: VectorDirection::Column,
values: vec![Number { value: dec!(0.0) }, Number { value: dec!(1.0) }], values: vec![Number { value: 0.0_f64 }, Number { value: 1.0_f64 }],
})]), })]),
), ),
] { ] {

View File

@ -1,8 +1,11 @@
use super::{Entry, Number, Vector, VectorDirection}; use super::{Entry, Number, Vector, VectorDirection};
use crate::calc::{ use crate::{
entries::CalculatorEntry, calc::{
errors::{CalculatorError, CalculatorResult}, errors::{CalculatorError, CalculatorResult},
types::{CalculatorAngleMode, CalculatorDisplayMode}, types::CalculatorAngleMode,
CalculatorDisplayMode,
},
CalculatorEntry,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;

View File

@ -1,27 +1,37 @@
use super::{Entry, Matrix, Vector}; use super::{Entry, Matrix, Vector};
use crate::{ use crate::{
calc::{ calc::{
entries::CalculatorEntry,
errors::{CalculatorError, CalculatorResult}, errors::{CalculatorError, CalculatorResult},
types::{CalculatorAngleMode, CalculatorDisplayMode}, types::CalculatorAngleMode,
CalculatorDisplayMode,
}, },
constants, CalculatorEntry,
}; };
use rust_decimal::{Decimal, MathematicalOps};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Number { pub struct Number {
pub value: Decimal, pub value: f64,
} }
// impl PartialEq for Number { impl PartialEq for Number {
// fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
// (self.value - other.value).abs() < f64::EPSILON if self.value.is_nan() && other.value.is_nan()
// } || self.value.is_infinite() && other.value.is_infinite()
// } {
// } true
} else if self.value.is_nan()
|| self.value.is_infinite()
|| other.value.is_infinite()
|| other.value.is_nan()
{
false
} else {
(self.value - other.value).abs() < f64::EPSILON
}
}
}
impl CalculatorEntry for Number { impl CalculatorEntry for Number {
fn to_editable_string(&self) -> CalculatorResult<String> { fn to_editable_string(&self) -> CalculatorResult<String> {
@ -39,7 +49,7 @@ impl CalculatorEntry for Number {
} }
} }
fn is_valid(&self) -> bool { fn is_valid(&self) -> bool {
true !self.value.is_nan() && !self.value.is_infinite()
} }
fn validate(self) -> CalculatorResult<Entry> { fn validate(self) -> CalculatorResult<Entry> {
if self.is_valid() { if self.is_valid() {
@ -59,9 +69,7 @@ impl CalculatorEntry for Number {
} }
fn inverse(&self) -> CalculatorResult<Entry> { fn inverse(&self) -> CalculatorResult<Entry> {
Self { Self {
value: constants::ONE value: self.value.recip(),
.checked_div(self.value)
.ok_or(CalculatorError::ArithmeticError)?,
} }
.validate() .validate()
} }
@ -71,103 +79,70 @@ impl CalculatorEntry for Number {
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: match angle_mode { value: match angle_mode {
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER) CalculatorAngleMode::Degrees => self.value.to_radians().sin(),
.checked_sin() CalculatorAngleMode::Radians => self.value.sin(),
.ok_or(CalculatorError::ArithmeticError)?, CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).sin(),
CalculatorAngleMode::Radians => self
.value
.checked_sin()
.ok_or(CalculatorError::ArithmeticError)?,
CalculatorAngleMode::Grads => (self.value * constants::GRAD_TO_RAD_MULTIPLIER)
.checked_sin()
.ok_or(CalculatorError::ArithmeticError)?,
}, },
})) }))
} }
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: match angle_mode { value: match angle_mode {
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER) CalculatorAngleMode::Degrees => self.value.to_radians().cos(),
.checked_cos() CalculatorAngleMode::Radians => self.value.cos(),
.ok_or(CalculatorError::ArithmeticError)?, CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).cos(),
CalculatorAngleMode::Radians => self
.value
.checked_cos()
.ok_or(CalculatorError::ArithmeticError)?,
CalculatorAngleMode::Grads => (self.value * constants::GRAD_TO_RAD_MULTIPLIER)
.checked_cos()
.ok_or(CalculatorError::ArithmeticError)?,
}, },
})) }))
} }
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: match angle_mode { value: match angle_mode {
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER) CalculatorAngleMode::Degrees => self.value.to_radians().tan(),
.checked_tan() CalculatorAngleMode::Radians => self.value.tan(),
.ok_or(CalculatorError::ArithmeticError)?, CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).tan(),
CalculatorAngleMode::Radians => self
.value
.checked_tan()
.ok_or(CalculatorError::ArithmeticError)?,
CalculatorAngleMode::Grads => (self.value * constants::GRAD_TO_RAD_MULTIPLIER)
.checked_tan()
.ok_or(CalculatorError::ArithmeticError)?,
}, },
})) }))
} }
fn asin(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
// TODO: Implement this Ok(Entry::Number(Self {
Err(CalculatorError::NotYetImplemented) value: match angle_mode {
// Ok(Entry::Number(Self { CalculatorAngleMode::Degrees => self.value.asin().to_degrees(),
// value: match angle_mode { CalculatorAngleMode::Radians => self.value.asin(),
// CalculatorAngleMode::Degrees => self.value.asin().to_degrees(), CalculatorAngleMode::Grads => self.value.asin() * 200.0 / std::f64::consts::PI,
// CalculatorAngleMode::Radians => self.value.asin(), },
// CalculatorAngleMode::Grads => self.value.asin() * 200.0 / std::f64::consts::PI, }))
// },
// }))
} }
fn acos(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
// TODO: Implement this Ok(Entry::Number(Self {
Err(CalculatorError::NotYetImplemented) value: match angle_mode {
// Ok(Entry::Number(Self { CalculatorAngleMode::Degrees => self.value.acos().to_degrees(),
// value: match angle_mode { CalculatorAngleMode::Radians => self.value.acos(),
// CalculatorAngleMode::Degrees => self.value.acos().to_degrees(), CalculatorAngleMode::Grads => self.value.acos() * 200.0 / std::f64::consts::PI,
// CalculatorAngleMode::Radians => self.value.acos(), },
// CalculatorAngleMode::Grads => self.value.acos() * 200.0 / std::f64::consts::PI, }))
// },
// }))
} }
fn atan(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> { fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
// TODO: Implement this Ok(Entry::Number(Self {
Err(CalculatorError::NotYetImplemented) value: match angle_mode {
// Ok(Entry::Number(Self { CalculatorAngleMode::Degrees => self.value.atan().to_degrees(),
// value: match angle_mode { CalculatorAngleMode::Radians => self.value.atan(),
// CalculatorAngleMode::Degrees => self.value.atan().to_degrees(), CalculatorAngleMode::Grads => self.value.atan() * 200.0 / std::f64::consts::PI,
// CalculatorAngleMode::Radians => self.value.atan(), },
// CalculatorAngleMode::Grads => self.value.atan() * 200.0 / std::f64::consts::PI, }))
// },
// }))
} }
fn sqrt(&self) -> CalculatorResult<Entry> { fn sqrt(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: self.value.sqrt().ok_or(CalculatorError::ArithmeticError)?, value: self.value.sqrt(),
})) }))
} }
fn log(&self) -> CalculatorResult<Entry> { fn log(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: self value: self.value.log10(),
.value
.checked_log10()
.ok_or(CalculatorError::ArithmeticError)?,
})) }))
} }
fn ln(&self) -> CalculatorResult<Entry> { fn ln(&self) -> CalculatorResult<Entry> {
Ok(Entry::Number(Self { Ok(Entry::Number(Self {
value: self value: self.value.ln(),
.value
.checked_ln()
.ok_or(CalculatorError::ArithmeticError)?,
})) }))
} }
@ -215,20 +190,8 @@ impl CalculatorEntry for Number {
match arg { match arg {
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::int_divide), Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::int_divide),
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::int_divide), Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::int_divide),
Entry::Number(number) => { Entry::Number(number) => Self {
let q = (self.value / number.value).trunc(); value: self.value.div_euclid(number.value),
Self {
// Implementation based on https://doc.rust-lang.org/src/std/f64.rs.html#263-269
value: if self.value % number.value < constants::ZERO {
if number.value > constants::ZERO {
q - constants::ONE
} else {
q + constants::ONE
}
} else {
q
},
}
} }
.validate(), .validate(),
} }
@ -248,10 +211,7 @@ impl CalculatorEntry for Number {
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::pow), Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::pow),
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::pow), Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::pow),
Entry::Number(number) => Self { Entry::Number(number) => Self {
value: self value: self.value.powf(number.value),
.value
.checked_powd(number.value)
.ok_or(CalculatorError::ArithmeticError)?,
} }
.validate(), .validate(),
} }
@ -259,9 +219,7 @@ impl CalculatorEntry for Number {
} }
impl Number { impl Number {
pub const ZERO: Self = Self { pub const ZERO: Self = Self { value: 0.0_f64 };
value: constants::ZERO,
};
fn iterated_binary_vec( fn iterated_binary_vec(
self, self,
@ -311,7 +269,7 @@ impl fmt::Display for Number {
} }
// Based on https://stackoverflow.com/a/65266882 // Based on https://stackoverflow.com/a/65266882
fn scientific(f: Decimal, precision: usize) -> String { fn scientific(f: f64, precision: usize) -> String {
let mut ret = format!("{:.precision$E}", f, precision = precision); let mut ret = format!("{:.precision$E}", f, precision = precision);
let exp = ret.split_off(ret.find('E').unwrap_or(0)); let exp = ret.split_off(ret.find('E').unwrap_or(0));
let (exp_sign, exp) = exp let (exp_sign, exp) = exp
@ -322,7 +280,7 @@ fn scientific(f: Decimal, precision: usize) -> String {
format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2) format!("{}{} E{}{:0>pad$}", sign, ret, exp_sign, exp, pad = 2)
} }
fn engineering(f: Decimal, precision: usize) -> String { fn engineering(f: f64, precision: usize) -> String {
// Format the string so the first digit is always in the first column, and remove '.'. Requested precision + 2 to account for using 1, 2, or 3 digits for the whole portion of the string // Format the string so the first digit is always in the first column, and remove '.'. Requested precision + 2 to account for using 1, 2, or 3 digits for the whole portion of the string
// 1,000 => 1000E3 // 1,000 => 1000E3
let all = format!(" {:.precision$E}", f, precision = precision) let all = format!(" {:.precision$E}", f, precision = precision)
@ -373,7 +331,7 @@ fn engineering(f: Decimal, precision: usize) -> String {
) )
} }
fn separated(f: Decimal, sep: char) -> String { fn separated(f: f64, sep: char) -> String {
let mut ret = f.to_string(); let mut ret = f.to_string();
let start = if ret.starts_with('-') { 1 } else { 0 }; let start = if ret.starts_with('-') { 1 } else { 0 };
let end = ret.find('.').unwrap_or_else(|| ret.len()); let end = ret.find('.').unwrap_or_else(|| ret.len());

View File

@ -1,11 +1,11 @@
use super::{Entry, Matrix, Number}; use super::{Entry, Matrix, Number};
use crate::{ use crate::{
calc::{ calc::{
entries::CalculatorEntry,
errors::{CalculatorError, CalculatorResult}, errors::{CalculatorError, CalculatorResult},
types::{CalculatorAngleMode, CalculatorDisplayMode}, types::CalculatorAngleMode,
CalculatorDisplayMode,
}, },
constants, CalculatorEntry,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
@ -77,13 +77,11 @@ impl CalculatorEntry for Vector {
self.iterated_unary(Number::negate) self.iterated_unary(Number::negate)
} }
fn abs(&self) -> CalculatorResult<Entry> { fn abs(&self) -> CalculatorResult<Entry> {
let value: Entry = let value: Entry = self
self.values .values
.iter() .iter()
.try_fold(Entry::Number(Number::ZERO), |acc, n2| { .try_fold(Entry::Number(Number::ZERO), |acc, n2| {
acc.add(&n2.pow(&Entry::Number(Number { acc.add(&n2.pow(&Entry::Number(Number { value: 2.0_f64 }))?)
value: constants::TWO,
}))?)
})?; })?;
value.sqrt() value.sqrt()
} }

View File

@ -1,26 +0,0 @@
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
pub const ZERO: Decimal = Decimal::ZERO;
pub const ONE: Decimal = Decimal::ONE;
pub const TWO: Decimal = Decimal::TWO;
pub const PI: Decimal = Decimal::PI;
pub const E: Decimal = Decimal::E;
pub const TAU: Decimal = Decimal::TWO_PI;
pub const RAD_TO_DEC_MULTIPLIER: Decimal = dec!(57.295779513082320876798154814);
pub const DEC_TO_RAD_MULTIPLIER: Decimal = dec!(0.0174532925199432957692369077);
pub const GRAD_TO_RAD_MULTIPLIER: Decimal = dec!(0.0157079632679489661923132169);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constants() {
assert_eq!(RAD_TO_DEC_MULTIPLIER, dec!(180.0) / PI);
assert_eq!(DEC_TO_RAD_MULTIPLIER, PI / dec!(180.0));
assert_eq!(GRAD_TO_RAD_MULTIPLIER, PI / dec!(200.0));
}
}

View File

@ -1,2 +0,0 @@
pub mod calc;
pub mod constants;

View File

@ -5,9 +5,8 @@
// Cannot fix this, so don't warn me about it // Cannot fix this, so don't warn me about it
#![allow(clippy::multiple_crate_versions)] #![allow(clippy::multiple_crate_versions)]
use rpn_rs::{calc, constants}; mod calc;
mod event; mod event;
use tracing::info;
const BORDER_SIZE: u16 = 2; const BORDER_SIZE: u16 = 2;
const HELP_TEXT: &str = "\ const HELP_TEXT: &str = "\
@ -62,7 +61,7 @@ use crossterm::{
use event::{Event, Events}; use event::{Event, Events};
// use io::stdout; // use io::stdout;
use std::{cmp, convert::TryFrom, error::Error, fs::OpenOptions, io, io::Write}; use std::{cmp, convert::TryFrom, error::Error, io, io::Write};
use tui::{ use tui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -309,36 +308,7 @@ impl App {
} }
} }
fn init_logging() {
use tracing_subscriber::{filter::LevelFilter, fmt, prelude::*, EnvFilter};
let file = OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/rpn_rs.log")
.unwrap();
let (non_blocking_file, _guard) = tracing_appender::non_blocking(file);
tracing_subscriber::registry()
.with(fmt::layer().with_writer(non_blocking_file))
// .with(fmt::layer().with_writer(io::stderr))
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::ERROR.into())
.with_env_var("RPN_RS_LOG")
.from_env()
.unwrap(),
)
.init();
info!("Logging initialized");
}
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
if std::env::var("ENABLE_LOGGING").as_ref().map(String::as_str) == Ok("1") {
init_logging();
}
enable_raw_mode()?; enable_raw_mode()?;
let mut stdout = io::stdout(); let mut stdout = io::stdout();
@ -410,10 +380,6 @@ fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult<CalculatorRespon
code: KeyCode::Enter, code: KeyCode::Enter,
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
} }
| KeyEvent {
code: KeyCode::Char('j'),
modifiers: KeyModifiers::CONTROL,
}
| KeyEvent { | KeyEvent {
code: KeyCode::Char(' '), code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE, modifiers: KeyModifiers::NONE,
@ -452,7 +418,7 @@ fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult<CalculatorRespon
app.calculator.take_input(c)?; app.calculator.take_input(c)?;
} }
} }
_key_event => {} _ => {}
}, },
(AppState::Help, _) => match key { (AppState::Help, _) => match key {
KeyEvent { KeyEvent {