Compare commits
No commits in common. "master" and "v0.6.0" have entirely different histories.
3382
Cargo.lock
generated
3382
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@ -11,24 +11,13 @@ repository = "https://"
|
||||
keywords = ["tui", "cli", "rpn"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"./rpn_rs_tui/",
|
||||
"./rpn_rs_gui/",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
crossterm = "0.18"
|
||||
tui = { version = "0.14", default-features = false, features = ['crossterm'] }
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
# confy = "0.4.0"
|
||||
toml = "0.4.2"
|
||||
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]
|
||||
# TODO: Update this to v0.5.0 when it finally comes out -- for now, use latest git master
|
||||
|
@ -133,7 +133,3 @@ Will I implement these features? I don't know. Lots of these could be done by se
|
||||
* Bases: Not yet
|
||||
* Different math operators like `!` or `sum`: 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
|
||||
|
@ -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"]
|
45
rpn_rs_gui/.github/workflows/pages.yml
vendored
45
rpn_rs_gui/.github/workflows/pages.yml
vendored
@ -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
|
105
rpn_rs_gui/.github/workflows/rust.yml
vendored
105
rpn_rs_gui/.github/workflows/rust.yml
vendored
@ -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
|
2
rpn_rs_gui/.gitignore
vendored
2
rpn_rs_gui/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
/dist
|
2626
rpn_rs_gui/Cargo.lock
generated
2626
rpn_rs_gui/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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.
@ -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).
|
@ -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 |
@ -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 |
@ -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);
|
||||
})
|
||||
);
|
||||
});
|
@ -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
|
@ -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/ -->
|
@ -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
|
||||
}
|
||||
}
|
@ -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"),
|
||||
];
|
@ -1,4 +0,0 @@
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
|
||||
mod app;
|
||||
pub use app::TemplateApp;
|
@ -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");
|
||||
});
|
||||
}
|
@ -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"
|
25
src/calc.rs
25
src/calc.rs
@ -1,12 +1,9 @@
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
pub mod entries;
|
||||
use crate::constants;
|
||||
use lazy_static::lazy_static;
|
||||
pub mod errors;
|
||||
pub mod operations;
|
||||
pub mod types;
|
||||
use crate::calc::entries::CalculatorEntry;
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
use confy::{load, store};
|
||||
use entries::{Entry, Matrix, Number, Vector};
|
||||
@ -200,7 +197,7 @@ impl Default for Calculator {
|
||||
CalculatorConstant {
|
||||
help: String::from("Tau (2pi)"),
|
||||
value: Entry::Number(Number {
|
||||
value: constants::TAU,
|
||||
value: std::f64::consts::TAU,
|
||||
}),
|
||||
},
|
||||
),
|
||||
@ -209,7 +206,7 @@ impl Default for Calculator {
|
||||
CalculatorConstant {
|
||||
help: String::from("Euler's Number e"),
|
||||
value: Entry::Number(Number {
|
||||
value: constants::E,
|
||||
value: std::f64::consts::E,
|
||||
}),
|
||||
},
|
||||
),
|
||||
@ -218,7 +215,7 @@ impl Default for Calculator {
|
||||
CalculatorConstant {
|
||||
help: String::from("Pi"),
|
||||
value: Entry::Number(Number {
|
||||
value: constants::PI,
|
||||
value: std::f64::consts::PI,
|
||||
}),
|
||||
},
|
||||
),
|
||||
@ -482,10 +479,7 @@ impl Calculator {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let f = self
|
||||
.l
|
||||
.parse::<Decimal>()
|
||||
.or(Err(CalculatorError::ParseError))?;
|
||||
let f = self.l.parse::<f64>().or(Err(CalculatorError::ParseError))?;
|
||||
self.push(Entry::Number(Number { value: f }))?;
|
||||
self.l.clear();
|
||||
Ok(true)
|
||||
@ -526,11 +520,14 @@ impl Calculator {
|
||||
Entry::Matrix(_) | Entry::Vector(_) => return Err(CalculatorError::TypeMismatch),
|
||||
Entry::Number(Number { value }) => value,
|
||||
};
|
||||
|
||||
match f.to_usize() {
|
||||
Some(u) => Ok((u, entry)),
|
||||
None => Err(CalculatorError::ArithmeticError),
|
||||
// Ensure this can be cast to a usize
|
||||
if !f.is_finite() || f.is_sign_negative() {
|
||||
return 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
|
||||
pub fn pop_precision(&mut self) -> CalculatorResult<usize> {
|
||||
|
@ -255,8 +255,6 @@ impl fmt::Display for Entry {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rust_decimal::Decimal;
|
||||
use rust_decimal_macros::dec;
|
||||
|
||||
fn valid_square_matrix() -> Entry {
|
||||
Entry::Matrix(Matrix {
|
||||
@ -268,25 +266,25 @@ mod tests {
|
||||
Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: dec!(2.0) },
|
||||
Number { value: dec!(-3.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number { value: 2.0_f64 },
|
||||
Number { value: -3.0_f64 },
|
||||
],
|
||||
},
|
||||
Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(4.0) },
|
||||
Number { value: dec!(-5.0) },
|
||||
Number { value: dec!(0.0) },
|
||||
Number { value: 4.0_f64 },
|
||||
Number { value: -5.0_f64 },
|
||||
Number { value: 0.0_f64 },
|
||||
],
|
||||
},
|
||||
Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(-7.0) },
|
||||
Number { value: dec!(8.0) },
|
||||
Number { value: dec!(9.0) },
|
||||
Number { value: -7.0_f64 },
|
||||
Number { value: 8.0_f64 },
|
||||
Number { value: 9.0_f64 },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -303,25 +301,25 @@ mod tests {
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(-1.0) },
|
||||
Number { value: dec!(-2.0) },
|
||||
Number { value: dec!(3.0) },
|
||||
Number { value: -1.0_f64 },
|
||||
Number { value: -2.0_f64 },
|
||||
Number { value: 3.0_f64 },
|
||||
],
|
||||
}),
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(-4.0) },
|
||||
Number { value: dec!(5.0) },
|
||||
Number { value: dec!(-0.0) },
|
||||
Number { value: -4.0_f64 },
|
||||
Number { value: 5.0_f64 },
|
||||
Number { value: -0.0_f64 },
|
||||
],
|
||||
}),
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(7.0) },
|
||||
Number { value: dec!(-8.0) },
|
||||
Number { value: dec!(-9.0) },
|
||||
Number { value: 7.0_f64 },
|
||||
Number { value: -8.0_f64 },
|
||||
Number { value: -9.0_f64 },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
@ -333,25 +331,25 @@ mod tests {
|
||||
// Entry::Vector(Vector {
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number { value: dec!(1.0) },
|
||||
// Number { value: dec!(2.0) },
|
||||
// Number { value: dec!(3.0) },
|
||||
// Number { value: 1.0_f64 },
|
||||
// Number { value: 2.0_f64 },
|
||||
// Number { value: 3.0_f64 },
|
||||
// ],
|
||||
// }),
|
||||
// Entry::Vector(Vector {
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number { value: dec!(4.0) },
|
||||
// Number { value: dec!(5.0) },
|
||||
// Number { value: dec!(0.0) },
|
||||
// Number { value: 4.0_f64 },
|
||||
// Number { value: 5.0_f64 },
|
||||
// Number { value: 0.0_f64 },
|
||||
// ],
|
||||
// }),
|
||||
// Entry::Vector(Vector {
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number { value: dec!(7.0) },
|
||||
// Number { value: dec!(8.0) },
|
||||
// Number { value: dec!(9.0) },
|
||||
// Number { value: 7.0_f64 },
|
||||
// Number { value: 8.0_f64 },
|
||||
// Number { value: 9.0_f64 },
|
||||
// ],
|
||||
// }),
|
||||
// ]),
|
||||
@ -360,23 +358,23 @@ mod tests {
|
||||
// Entry::Vector(Vector {
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number{value: dec!(1.0},)
|
||||
// Number{value: dec!(2.0},)
|
||||
// Number{value: dec!(-3.0},)
|
||||
// Number{value: 1.0_f64},
|
||||
// Number{value: 2.0_f64},
|
||||
// Number{value: -3.0_f64},
|
||||
// ]}),
|
||||
// Entry::Vector (Vector{
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number{value: dec!(4.0},)
|
||||
// Number{value: dec!(-5.0},)
|
||||
// Number{value: dec!(0.0},)
|
||||
// Number{value: 4.0_f64},
|
||||
// Number{value: -5.0_f64},
|
||||
// Number{value: 0.0_f64},
|
||||
// ]}),
|
||||
// Entry::Vector (Vector{
|
||||
// direction: VectorDirection::Column,
|
||||
// values: vec![
|
||||
// Number{value: dec!(-7.0},)
|
||||
// Number{value: dec!(8.0},)
|
||||
// Number{value: dec!(9.0},)
|
||||
// Number{value: -7.0_f64},
|
||||
// Number{value: 8.0_f64},
|
||||
// Number{value: 9.0_f64},
|
||||
// ])),
|
||||
(
|
||||
"transpose",
|
||||
@ -385,25 +383,25 @@ mod tests {
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: dec!(4.0) },
|
||||
Number { value: dec!(-7.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number { value: 4.0_f64 },
|
||||
Number { value: -7.0_f64 },
|
||||
],
|
||||
}),
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(2.0) },
|
||||
Number { value: dec!(-5.0) },
|
||||
Number { value: dec!(8.0) },
|
||||
Number { value: 2.0_f64 },
|
||||
Number { value: -5.0_f64 },
|
||||
Number { value: 8.0_f64 },
|
||||
],
|
||||
}),
|
||||
Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(-3.0) },
|
||||
Number { value: dec!(0.0) },
|
||||
Number { value: dec!(9.0) },
|
||||
Number { value: -3.0_f64 },
|
||||
Number { value: 0.0_f64 },
|
||||
Number { value: 9.0_f64 },
|
||||
],
|
||||
}),
|
||||
]),
|
||||
@ -418,9 +416,9 @@ mod tests {
|
||||
vectors: vec![Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: dec!(100.0) },
|
||||
Number { value: dec!(64.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number { value: 100.0_f64 },
|
||||
Number { value: 64.0_f64 },
|
||||
],
|
||||
}],
|
||||
})
|
||||
@ -428,9 +426,9 @@ mod tests {
|
||||
Matrix::from(&[Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: dec!(10.0) },
|
||||
Number { value: dec!(8.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number { value: 10.0_f64 },
|
||||
Number { value: 8.0_f64 },
|
||||
],
|
||||
})]),
|
||||
),
|
||||
@ -445,10 +443,10 @@ mod tests {
|
||||
vectors: vec![Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: dec!(100.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number { value: 100.0_f64 },
|
||||
Number {
|
||||
value: dec!(100_000.0),
|
||||
value: 100_000.0_f64,
|
||||
},
|
||||
],
|
||||
}],
|
||||
@ -457,9 +455,9 @@ mod tests {
|
||||
Matrix::from(&[Entry::Vector(Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(0.0) },
|
||||
Number { value: dec!(2.0) },
|
||||
Number { value: dec!(5.0) },
|
||||
Number { value: 0.0_f64 },
|
||||
Number { value: 2.0_f64 },
|
||||
Number { value: 5.0_f64 },
|
||||
],
|
||||
})]),
|
||||
),
|
||||
@ -473,9 +471,9 @@ mod tests {
|
||||
vectors: vec![Vector {
|
||||
direction: VectorDirection::Column,
|
||||
values: vec![
|
||||
Number { value: dec!(1.0) },
|
||||
Number { value: 1.0_f64 },
|
||||
Number {
|
||||
value: constants::E,
|
||||
value: std::f64::consts::E,
|
||||
},
|
||||
],
|
||||
}],
|
||||
@ -483,7 +481,7 @@ mod tests {
|
||||
.ln(),
|
||||
Matrix::from(&[Entry::Vector(Vector {
|
||||
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 }],
|
||||
})]),
|
||||
),
|
||||
] {
|
||||
|
@ -1,8 +1,11 @@
|
||||
use super::{Entry, Number, Vector, VectorDirection};
|
||||
use crate::calc::{
|
||||
entries::CalculatorEntry,
|
||||
use crate::{
|
||||
calc::{
|
||||
errors::{CalculatorError, CalculatorResult},
|
||||
types::{CalculatorAngleMode, CalculatorDisplayMode},
|
||||
types::CalculatorAngleMode,
|
||||
CalculatorDisplayMode,
|
||||
},
|
||||
CalculatorEntry,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
@ -1,27 +1,37 @@
|
||||
use super::{Entry, Matrix, Vector};
|
||||
use crate::{
|
||||
calc::{
|
||||
entries::CalculatorEntry,
|
||||
errors::{CalculatorError, CalculatorResult},
|
||||
types::{CalculatorAngleMode, CalculatorDisplayMode},
|
||||
types::CalculatorAngleMode,
|
||||
CalculatorDisplayMode,
|
||||
},
|
||||
constants,
|
||||
CalculatorEntry,
|
||||
};
|
||||
use rust_decimal::{Decimal, MathematicalOps};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Number {
|
||||
pub value: Decimal,
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
// impl PartialEq for Number {
|
||||
// fn eq(&self, other: &Self) -> bool {
|
||||
// (self.value - other.value).abs() < f64::EPSILON
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl PartialEq for Number {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
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 {
|
||||
fn to_editable_string(&self) -> CalculatorResult<String> {
|
||||
@ -39,7 +49,7 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
}
|
||||
fn is_valid(&self) -> bool {
|
||||
true
|
||||
!self.value.is_nan() && !self.value.is_infinite()
|
||||
}
|
||||
fn validate(self) -> CalculatorResult<Entry> {
|
||||
if self.is_valid() {
|
||||
@ -59,9 +69,7 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
fn inverse(&self) -> CalculatorResult<Entry> {
|
||||
Self {
|
||||
value: constants::ONE
|
||||
.checked_div(self.value)
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
value: self.value.recip(),
|
||||
}
|
||||
.validate()
|
||||
}
|
||||
@ -71,103 +79,70 @@ impl CalculatorEntry for Number {
|
||||
fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER)
|
||||
.checked_sin()
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
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)?,
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().sin(),
|
||||
CalculatorAngleMode::Radians => self.value.sin(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).sin(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER)
|
||||
.checked_cos()
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
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)?,
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().cos(),
|
||||
CalculatorAngleMode::Radians => self.value.cos(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).cos(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => (self.value * constants::DEC_TO_RAD_MULTIPLIER)
|
||||
.checked_tan()
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
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)?,
|
||||
CalculatorAngleMode::Degrees => self.value.to_radians().tan(),
|
||||
CalculatorAngleMode::Radians => self.value.tan(),
|
||||
CalculatorAngleMode::Grads => (self.value * std::f64::consts::PI / 200.0).tan(),
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn asin(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
// TODO: Implement this
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
// Ok(Entry::Number(Self {
|
||||
// value: match angle_mode {
|
||||
// CalculatorAngleMode::Degrees => self.value.asin().to_degrees(),
|
||||
// CalculatorAngleMode::Radians => self.value.asin(),
|
||||
// CalculatorAngleMode::Grads => self.value.asin() * 200.0 / std::f64::consts::PI,
|
||||
// },
|
||||
// }))
|
||||
fn asin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.asin().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.asin(),
|
||||
CalculatorAngleMode::Grads => self.value.asin() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn acos(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
// TODO: Implement this
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
// Ok(Entry::Number(Self {
|
||||
// value: match angle_mode {
|
||||
// CalculatorAngleMode::Degrees => self.value.acos().to_degrees(),
|
||||
// CalculatorAngleMode::Radians => self.value.acos(),
|
||||
// CalculatorAngleMode::Grads => self.value.acos() * 200.0 / std::f64::consts::PI,
|
||||
// },
|
||||
// }))
|
||||
fn acos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.acos().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.acos(),
|
||||
CalculatorAngleMode::Grads => self.value.acos() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn atan(&self, _angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
// TODO: Implement this
|
||||
Err(CalculatorError::NotYetImplemented)
|
||||
// Ok(Entry::Number(Self {
|
||||
// value: match angle_mode {
|
||||
// CalculatorAngleMode::Degrees => self.value.atan().to_degrees(),
|
||||
// CalculatorAngleMode::Radians => self.value.atan(),
|
||||
// CalculatorAngleMode::Grads => self.value.atan() * 200.0 / std::f64::consts::PI,
|
||||
// },
|
||||
// }))
|
||||
fn atan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: match angle_mode {
|
||||
CalculatorAngleMode::Degrees => self.value.atan().to_degrees(),
|
||||
CalculatorAngleMode::Radians => self.value.atan(),
|
||||
CalculatorAngleMode::Grads => self.value.atan() * 200.0 / std::f64::consts::PI,
|
||||
},
|
||||
}))
|
||||
}
|
||||
fn sqrt(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self.value.sqrt().ok_or(CalculatorError::ArithmeticError)?,
|
||||
value: self.value.sqrt(),
|
||||
}))
|
||||
}
|
||||
fn log(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self
|
||||
.value
|
||||
.checked_log10()
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
value: self.value.log10(),
|
||||
}))
|
||||
}
|
||||
fn ln(&self) -> CalculatorResult<Entry> {
|
||||
Ok(Entry::Number(Self {
|
||||
value: self
|
||||
.value
|
||||
.checked_ln()
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
value: self.value.ln(),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -215,20 +190,8 @@ impl CalculatorEntry for Number {
|
||||
match arg {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::int_divide),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::int_divide),
|
||||
Entry::Number(number) => {
|
||||
let q = (self.value / number.value).trunc();
|
||||
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
|
||||
},
|
||||
}
|
||||
Entry::Number(number) => Self {
|
||||
value: self.value.div_euclid(number.value),
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
@ -248,10 +211,7 @@ impl CalculatorEntry for Number {
|
||||
Entry::Matrix(matrix) => self.iterated_binary_mat(matrix, Self::pow),
|
||||
Entry::Vector(vector) => self.iterated_binary_vec(vector, Self::pow),
|
||||
Entry::Number(number) => Self {
|
||||
value: self
|
||||
.value
|
||||
.checked_powd(number.value)
|
||||
.ok_or(CalculatorError::ArithmeticError)?,
|
||||
value: self.value.powf(number.value),
|
||||
}
|
||||
.validate(),
|
||||
}
|
||||
@ -259,9 +219,7 @@ impl CalculatorEntry for Number {
|
||||
}
|
||||
|
||||
impl Number {
|
||||
pub const ZERO: Self = Self {
|
||||
value: constants::ZERO,
|
||||
};
|
||||
pub const ZERO: Self = Self { value: 0.0_f64 };
|
||||
|
||||
fn iterated_binary_vec(
|
||||
self,
|
||||
@ -311,7 +269,7 @@ impl fmt::Display for Number {
|
||||
}
|
||||
|
||||
// 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 exp = ret.split_off(ret.find('E').unwrap_or(0));
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
// 1,000 => 1000E3
|
||||
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 start = if ret.starts_with('-') { 1 } else { 0 };
|
||||
let end = ret.find('.').unwrap_or_else(|| ret.len());
|
||||
|
@ -1,11 +1,11 @@
|
||||
use super::{Entry, Matrix, Number};
|
||||
use crate::{
|
||||
calc::{
|
||||
entries::CalculatorEntry,
|
||||
errors::{CalculatorError, CalculatorResult},
|
||||
types::{CalculatorAngleMode, CalculatorDisplayMode},
|
||||
types::CalculatorAngleMode,
|
||||
CalculatorDisplayMode,
|
||||
},
|
||||
constants,
|
||||
CalculatorEntry,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
@ -77,13 +77,11 @@ impl CalculatorEntry for Vector {
|
||||
self.iterated_unary(Number::negate)
|
||||
}
|
||||
fn abs(&self) -> CalculatorResult<Entry> {
|
||||
let value: Entry =
|
||||
self.values
|
||||
let value: Entry = self
|
||||
.values
|
||||
.iter()
|
||||
.try_fold(Entry::Number(Number::ZERO), |acc, n2| {
|
||||
acc.add(&n2.pow(&Entry::Number(Number {
|
||||
value: constants::TWO,
|
||||
}))?)
|
||||
acc.add(&n2.pow(&Entry::Number(Number { value: 2.0_f64 }))?)
|
||||
})?;
|
||||
value.sqrt()
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pub mod calc;
|
||||
pub mod constants;
|
@ -5,9 +5,8 @@
|
||||
// Cannot fix this, so don't warn me about it
|
||||
#![allow(clippy::multiple_crate_versions)]
|
||||
|
||||
use rpn_rs::{calc, constants};
|
||||
mod calc;
|
||||
mod event;
|
||||
use tracing::info;
|
||||
|
||||
const BORDER_SIZE: u16 = 2;
|
||||
const HELP_TEXT: &str = "\
|
||||
@ -62,7 +61,7 @@ use crossterm::{
|
||||
use event::{Event, Events};
|
||||
// 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::{
|
||||
backend::CrosstermBackend,
|
||||
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>> {
|
||||
if std::env::var("ENABLE_LOGGING").as_ref().map(String::as_str) == Ok("1") {
|
||||
init_logging();
|
||||
}
|
||||
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut stdout = io::stdout();
|
||||
@ -410,10 +380,6 @@ fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult<CalculatorRespon
|
||||
code: KeyCode::Enter,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}
|
||||
| KeyEvent {
|
||||
code: KeyCode::Char('j'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}
|
||||
| KeyEvent {
|
||||
code: KeyCode::Char(' '),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@ -452,7 +418,7 @@ fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult<CalculatorRespon
|
||||
app.calculator.take_input(c)?;
|
||||
}
|
||||
}
|
||||
_key_event => {}
|
||||
_ => {}
|
||||
},
|
||||
(AppState::Help, _) => match key {
|
||||
KeyEvent {
|
Loading…
Reference in New Issue
Block a user