diff --git a/Cargo.lock b/Cargo.lock index 768c2b3..83265c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "hashbrown 0.16.1", "static_assertions", "windows", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -1409,7 +1409,7 @@ dependencies = [ "roxmltree", "smallvec", "windows", - "windows-core 0.62.2", + "windows-core", "yeslogic-fontconfig-sys", ] @@ -2109,7 +2109,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core", ] [[package]] @@ -3904,10 +3904,11 @@ dependencies = [ [[package]] name = "rcalc" -version = "0.1.11" +version = "0.1.20" dependencies = [ "slint", "slint-build", + "winresource", ] [[package]] @@ -5430,7 +5431,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.62.2", + "windows-core", "windows-future", "windows-numerics", ] @@ -5441,20 +5442,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -5463,11 +5451,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-result", + "windows-strings", ] [[package]] @@ -5476,22 +5464,11 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -5503,17 +5480,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -5537,19 +5503,10 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -5559,16 +5516,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.5.1" @@ -5805,6 +5752,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/Cargo.toml b/Cargo.toml index 36addfb..b55fb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rcalc" -version = "0.1.12" +version = "0.1.21" edition = "2021" build = "build.rs" @@ -9,3 +9,4 @@ slint = "1.11" [build-dependencies] slint-build = "1.11" +winresource = "0.1" diff --git a/assets/icon.ico b/assets/icon.ico new file mode 100644 index 0000000..41982f0 Binary files /dev/null and b/assets/icon.ico differ diff --git a/build.rs b/build.rs index 3c966ff..d7c37fe 100644 --- a/build.rs +++ b/build.rs @@ -7,9 +7,35 @@ fn main() { let version = resolve_app_version(); println!("cargo:rustc-env=APP_VERSION={version}"); + embed_windows_icon(); + slint_build::compile("ui/app.slint").expect("Slint build failed"); } +/// Shell icon for `rcalc.exe` in Explorer (needs `assets/icon.ico` + windres/rc). +fn embed_windows_icon() { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + if target_os != "windows" { + return; + } + + println!("cargo:rerun-if-changed=assets/icon.ico"); + + let mut res = winresource::WindowsResource::new(); + res.set_icon("assets/icon.ico"); + + // Cross-compile from Linux: mingw windres is prefixed. + if cfg!(not(windows)) { + let target = env::var("TARGET").unwrap_or_default(); + if target.contains("windows-gnu") { + res.set_windres_path("x86_64-w64-mingw32-windres"); + } + } + + res.compile() + .expect("failed to embed Windows icon (is windres/rc available?)"); +} + /// Window / package version shown as `Rcalc-{version}`. /// /// On each `release` build the patch in `Cargo.toml` is incremented once diff --git a/dist/libwinpthread-1.dll b/dist/libwinpthread-1.dll index d87e531..7dbabd0 100755 Binary files a/dist/libwinpthread-1.dll and b/dist/libwinpthread-1.dll differ diff --git a/dist/rcalc b/dist/rcalc index c28d62c..37d272e 100755 Binary files a/dist/rcalc and b/dist/rcalc differ diff --git a/dist/rcalc.exe b/dist/rcalc.exe index 6628869..b5b625e 100755 Binary files a/dist/rcalc.exe and b/dist/rcalc.exe differ diff --git a/dist/rcalc_0.1.20.tar b/dist/rcalc_0.1.20.tar new file mode 100644 index 0000000..069bff0 Binary files /dev/null and b/dist/rcalc_0.1.20.tar differ diff --git a/dist/rcalc_0120.zip b/dist/rcalc_0120.zip new file mode 100644 index 0000000..fcec75c Binary files /dev/null and b/dist/rcalc_0120.zip differ diff --git a/dist/win_rcalc.zip b/dist/win_rcalc.zip index 07759c9..c38ca2d 100644 Binary files a/dist/win_rcalc.zip and b/dist/win_rcalc.zip differ diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..dbc4ceb --- /dev/null +++ b/src/config.rs @@ -0,0 +1,248 @@ +//! Persistent app settings (`~/.config/rcalc/config.json` or `%APPDATA%\rcalc\config.json`). + +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ThemeId { + Dark = 0, + Light = 1, + Blue = 2, +} + +impl ThemeId { + pub fn from_i32(v: i32) -> Self { + match v { + 1 => Self::Light, + 2 => Self::Blue, + _ => Self::Dark, + } + } + + pub fn as_i32(self) -> i32 { + self as i32 + } + + pub fn label(self) -> &'static str { + match self { + Self::Dark => "Dark", + Self::Light => "Light", + Self::Blue => "Blue", + } + } +} + +#[derive(Debug, Clone)] +pub struct AppConfig { + /// Button size: 1 = compact, 2 = normal, 3 = large. + pub button_size: u8, + pub theme: ThemeId, + /// Last calculator mode index (0..4). + pub last_mode: u8, + pub show_bits: bool, + /// Programmer word size label: QWORD, DWORD, DINT, WORD, INT, BYTE. + pub word_size: String, +} + +impl Default for AppConfig { + fn default() -> Self { + Self { + button_size: 2, + theme: ThemeId::Dark, + last_mode: 0, + show_bits: true, + word_size: "QWORD".into(), + } + } +} + +impl AppConfig { + pub fn load() -> Self { + let path = config_path(); + let Ok(raw) = fs::read_to_string(&path) else { + return Self::default(); + }; + Self::from_json(&raw).unwrap_or_default() + } + + pub fn save(&self) { + let path = config_path(); + if let Some(parent) = path.parent() { + let _ = fs::create_dir_all(parent); + } + let _ = fs::write(path, self.to_json()); + } + + pub fn clamp_button_size(size: u8) -> u8 { + size.clamp(1, 3) + } + + fn to_json(&self) -> String { + format!( + "{{\n \"button_size\": {},\n \"theme\": {},\n \"last_mode\": {},\n \"show_bits\": {},\n \"word_size\": \"{}\"\n}}\n", + self.button_size, + self.theme.as_i32(), + self.last_mode, + self.show_bits, + self.word_size + ) + } + + fn from_json(raw: &str) -> Option { + let mut cfg = Self::default(); + for line in raw.lines() { + let line = line.trim().trim_end_matches(','); + if let Some(v) = json_u8(line, "button_size") { + cfg.button_size = Self::clamp_button_size(v); + } else if let Some(v) = json_i32(line, "theme") { + cfg.theme = ThemeId::from_i32(v); + } else if let Some(v) = json_u8(line, "last_mode") { + cfg.last_mode = v.min(4); + } else if let Some(v) = json_bool(line, "show_bits") { + cfg.show_bits = v; + } else if let Some(v) = json_string(line, "word_size") { + let upper = v.to_ascii_uppercase(); + if matches!( + upper.as_str(), + "QWORD" | "DWORD" | "DINT" | "WORD" | "INT" | "BYTE" + ) { + cfg.word_size = upper; + } + } + } + Some(cfg) + } +} + +fn json_u8(line: &str, key: &str) -> Option { + json_i32(line, key)?.try_into().ok() +} + +fn json_i32(line: &str, key: &str) -> Option { + let prefix = format!("\"{key}\""); + let idx = line.find(&prefix)?; + let rest = &line[idx + prefix.len()..]; + let rest = rest.trim_start().trim_start_matches(':').trim_start(); + rest.split(|c: char| c == ',' || c == '}' || c.is_whitespace()) + .next()? + .parse() + .ok() +} + +fn json_bool(line: &str, key: &str) -> Option { + let prefix = format!("\"{key}\""); + let idx = line.find(&prefix)?; + let rest = &line[idx + prefix.len()..]; + let rest = rest.trim_start().trim_start_matches(':').trim_start(); + if rest.starts_with("true") { + Some(true) + } else if rest.starts_with("false") { + Some(false) + } else { + None + } +} + +fn json_string(line: &str, key: &str) -> Option { + let prefix = format!("\"{key}\""); + let idx = line.find(&prefix)?; + let rest = &line[idx + prefix.len()..]; + let rest = rest.trim_start().trim_start_matches(':').trim_start(); + let rest = rest.strip_prefix('"')?; + let end = rest.find('"')?; + Some(rest[..end].to_string()) +} + +pub fn config_path() -> PathBuf { + config_dir().join("config.json") +} + +fn config_dir() -> PathBuf { + #[cfg(windows)] + { + if let Some(appdata) = std::env::var_os("APPDATA") { + return PathBuf::from(appdata).join("rcalc"); + } + } + #[cfg(not(windows))] + { + if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME") { + return PathBuf::from(xdg).join("rcalc"); + } + if let Some(home) = std::env::var_os("HOME") { + return PathBuf::from(home).join(".config").join("rcalc"); + } + } + PathBuf::from(".") +} + +/// RGB colors for a theme (applied to Slint global). +pub struct ThemeColors { + pub bg: (u8, u8, u8), + pub surface: (u8, u8, u8), + pub btn: (u8, u8, u8), + pub btn_hover: (u8, u8, u8), + pub digit: (u8, u8, u8), + pub digit_hover: (u8, u8, u8), + pub accent: (u8, u8, u8), + pub accent_hover: (u8, u8, u8), + pub text: (u8, u8, u8), + pub text_muted: (u8, u8, u8), + pub text_dim: (u8, u8, u8), + pub menu_active: (u8, u8, u8), + pub eq_text: (u8, u8, u8), + pub border: (u8, u8, u8), +} + +pub fn theme_colors(id: ThemeId) -> ThemeColors { + match id { + ThemeId::Dark => ThemeColors { + bg: (0x20, 0x20, 0x20), + surface: (0x2a, 0x2a, 0x2a), + btn: (0x32, 0x32, 0x32), + btn_hover: (0x46, 0x46, 0x46), + digit: (0x3b, 0x3b, 0x3b), + digit_hover: (0x50, 0x50, 0x50), + accent: (0x60, 0xcd, 0xff), + accent_hover: (0x78, 0xd7, 0xff), + text: (0xff, 0xff, 0xff), + text_muted: (0xc8, 0xc8, 0xc8), + text_dim: (0x9a, 0x9a, 0x9a), + menu_active: (0x2f, 0x3a, 0x42), + eq_text: (0x14, 0x14, 0x14), + border: (0x40, 0x40, 0x40), + }, + ThemeId::Light => ThemeColors { + bg: (0xf3, 0xf3, 0xf3), + surface: (0xff, 0xff, 0xff), + btn: (0xe6, 0xe6, 0xe6), + btn_hover: (0xd0, 0xd0, 0xd0), + digit: (0xff, 0xff, 0xff), + digit_hover: (0xee, 0xee, 0xee), + accent: (0x00, 0x78, 0xd4), + accent_hover: (0x1a, 0x86, 0xda), + text: (0x1a, 0x1a, 0x1a), + text_muted: (0x55, 0x55, 0x55), + text_dim: (0x77, 0x77, 0x77), + menu_active: (0xd6, 0xeb, 0xf8), + eq_text: (0xff, 0xff, 0xff), + border: (0xcc, 0xcc, 0xcc), + }, + ThemeId::Blue => ThemeColors { + bg: (0x0f, 0x1c, 0x2e), + surface: (0x16, 0x28, 0x40), + btn: (0x1e, 0x3a, 0x5f), + btn_hover: (0x2a, 0x4d, 0x7a), + digit: (0x24, 0x45, 0x6e), + digit_hover: (0x2f, 0x58, 0x88), + accent: (0x4f, 0xc3, 0xf7), + accent_hover: (0x81, 0xd4, 0xfa), + text: (0xec, 0xf5, 0xff), + text_muted: (0xb0, 0xc4, 0xde), + text_dim: (0x7f, 0x9b, 0xb8), + menu_active: (0x1a, 0x3a, 0x5c), + eq_text: (0x0a, 0x14, 0x22), + border: (0x2a, 0x4a, 0x6e), + }, + } +} diff --git a/src/main.rs b/src/main.rs index 28bb7eb..82e7c5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ // Hide the extra console window on Windows GUI builds. #![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] +mod config; mod convert; mod crc; mod engine; @@ -10,11 +11,12 @@ mod programmer; use std::cell::RefCell; use std::rc::Rc; +use config::{theme_colors, AppConfig, ThemeId}; use convert::{ConvField, ConvPanel, Converter, Endian, RatioWidth}; use crc::CrcTool; use engine::{Calculator, Op, StdPanel}; use engineering::Engineering; -use programmer::{Base, ProgOp, Programmer}; +use programmer::{Base, ProgOp, Programmer, WordSize}; use slint::{ComponentHandle, ModelRc, VecModel}; slint::include_modules!(); @@ -26,6 +28,42 @@ enum Mode { Programmer, Convert, Crc, + Settings, +} + +impl Mode { + fn from_u8(v: u8) -> Self { + match v { + 1 => Self::Engineering, + 2 => Self::Programmer, + 3 => Self::Convert, + 4 => Self::Crc, + 5 => Self::Settings, + _ => Self::Standard, + } + } + + fn as_u8(self) -> u8 { + match self { + Self::Standard => 0, + Self::Engineering => 1, + Self::Programmer => 2, + Self::Convert => 3, + Self::Crc => 4, + Self::Settings => 5, + } + } + + fn title(self) -> &'static str { + match self { + Self::Standard => "Standard", + Self::Engineering => "Engineering", + Self::Programmer => "Programmer", + Self::Convert => "Convert", + Self::Crc => "CRC", + Self::Settings => "Settings", + } + } } struct State { @@ -37,19 +75,26 @@ struct State { crc: CrcTool, show_bits: bool, win_profile: u32, + config: AppConfig, } impl Default for State { fn default() -> Self { + let config = AppConfig::load(); + let mut prog = Programmer::new(); + if let Some(ws) = WordSize::from_label(&config.word_size) { + prog.set_word_size(ws); + } Self { - mode: Mode::Standard, + mode: Mode::from_u8(config.last_mode), std: Calculator::new(), eng: Engineering::new(), - prog: Programmer::new(), + prog, conv: Converter::new(), crc: CrcTool::new(), - show_bits: true, + show_bits: config.show_bits, win_profile: u32::MAX, + config, } } } @@ -85,8 +130,11 @@ fn run_app() -> Result<(), slint::PlatformError> { ui.set_app_title(format!("Rcalc-{}", env!("APP_VERSION")).into()); let state = Rc::new(RefCell::new(State::default())); - - refresh(&ui, &mut state.borrow_mut()); + { + let mut s = state.borrow_mut(); + apply_ui_style(&ui, &s.config); + refresh(&ui, &mut s); + } let ui_weak = ui.as_weak(); let state_keys = state.clone(); @@ -94,6 +142,7 @@ fn run_app() -> Result<(), slint::PlatformError> { let mut s = state_keys.borrow_mut(); handle_key(&mut s, id.as_str()); if let Some(ui) = ui_weak.upgrade() { + apply_ui_style(&ui, &s.config); refresh(&ui, &mut s); } }); @@ -111,6 +160,44 @@ fn run_app() -> Result<(), slint::PlatformError> { ui.run() } +fn rgb(c: (u8, u8, u8)) -> slint::Color { + slint::Color::from_rgb_u8(c.0, c.1, c.2) +} + +fn apply_ui_style(ui: &AppWindow, cfg: &AppConfig) { + let style = ui.global::(); + style.set_size(i32::from(cfg.button_size)); + style.set_theme(cfg.theme.as_i32()); + let c = theme_colors(cfg.theme); + style.set_bg(rgb(c.bg)); + style.set_surface(rgb(c.surface)); + style.set_btn(rgb(c.btn)); + style.set_btn_hover(rgb(c.btn_hover)); + style.set_digit(rgb(c.digit)); + style.set_digit_hover(rgb(c.digit_hover)); + style.set_accent(rgb(c.accent)); + style.set_accent_hover(rgb(c.accent_hover)); + style.set_text(rgb(c.text)); + style.set_text_muted(rgb(c.text_muted)); + style.set_text_dim(rgb(c.text_dim)); + style.set_menu_active(rgb(c.menu_active)); + style.set_eq_text(rgb(c.eq_text)); + style.set_border(rgb(c.border)); + + ui.set_cfg_button_size(i32::from(cfg.button_size)); + ui.set_cfg_theme(cfg.theme.as_i32()); +} + +fn persist_config(state: &mut State) { + if state.mode != Mode::Settings { + state.config.last_mode = state.mode.as_u8(); + } + state.config.show_bits = state.show_bits; + state.config.word_size = state.prog.word_size().label().to_string(); + state.config.button_size = AppConfig::clamp_button_size(state.config.button_size); + state.config.save(); +} + fn install_panic_hook() { let default = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { @@ -173,13 +260,7 @@ fn bit_row_model(row: &[(bool, u32)]) -> ModelRc { } fn window_profile(state: &State) -> u32 { - let mode = match state.mode { - Mode::Standard => 0, - Mode::Engineering => 1, - Mode::Programmer => 2, - Mode::Convert => 3, - Mode::Crc => 4, - }; + let mode = u32::from(state.mode.as_u8()); let bits = if state.show_bits { state.prog.word_size().bits() } else { @@ -190,7 +271,9 @@ fn window_profile(state: &State) -> u32 { ConvPanel::Ratio => 1, ConvPanel::Ascii => 2, }; - mode * 1000 + bits * 10 + panel + let size = u32::from(state.config.button_size); + let theme = state.config.theme.as_i32() as u32; + mode * 100_000 + bits * 1000 + panel * 100 + size * 10 + theme } fn fit_window(ui: &AppWindow, state: &mut State) { @@ -200,6 +283,12 @@ fn fit_window(ui: &AppWindow, state: &mut State) { } state.win_profile = profile; + let scale = match state.config.button_size { + 1 => 0.92, + 3 => 1.12, + _ => 1.0, + }; + let (w, h) = match state.mode { Mode::Standard => (360.0, 580.0), Mode::Engineering => (420.0, 720.0), @@ -218,9 +307,10 @@ fn fit_window(ui: &AppWindow, state: &mut State) { } Mode::Convert => (420.0, 820.0), Mode::Crc => (460.0, 560.0), + Mode::Settings => (420.0, 420.0), }; ui.window() - .set_size(slint::LogicalSize::new(w as f32, h as f32)); + .set_size(slint::LogicalSize::new((w * scale) as f32, (h * scale) as f32)); } fn apply_crc_results(ui: &AppWindow, state: &State) { @@ -236,24 +326,11 @@ fn apply_crc_results(ui: &AppWindow, state: &State) { } fn refresh(ui: &AppWindow, state: &mut State) { - ui.set_mode(match state.mode { - Mode::Standard => 0, - Mode::Engineering => 1, - Mode::Programmer => 2, - Mode::Convert => 3, - Mode::Crc => 4, - }); - ui.set_mode_title( - match state.mode { - Mode::Standard => "Standard", - Mode::Engineering => "Engineering", - Mode::Programmer => "Programmer", - Mode::Convert => "Convert", - Mode::Crc => "CRC", - } - .into(), - ); + ui.set_mode(i32::from(state.mode.as_u8())); + ui.set_mode_title(state.mode.title().into()); ui.set_menu_open(false); + ui.set_cfg_button_size(i32::from(state.config.button_size)); + ui.set_cfg_theme(state.config.theme.as_i32()); fit_window(ui, state); @@ -335,29 +412,75 @@ fn refresh(ui: &AppWindow, state: &mut State) { ui.set_crc_input(state.crc.input().into()); apply_crc_results(ui, state); } + Mode::Settings => {} } } +fn set_mode(state: &mut State, mode: Mode) { + state.mode = mode; + persist_config(state); +} + fn handle_key(state: &mut State, id: &str) { match id { "mode:std" => { - state.mode = Mode::Standard; + set_mode(state, Mode::Standard); return; } "mode:eng" => { - state.mode = Mode::Engineering; + set_mode(state, Mode::Engineering); return; } "mode:prog" => { - state.mode = Mode::Programmer; + set_mode(state, Mode::Programmer); return; } "mode:conv" => { - state.mode = Mode::Convert; + set_mode(state, Mode::Convert); return; } "mode:crc" => { - state.mode = Mode::Crc; + set_mode(state, Mode::Crc); + return; + } + "mode:settings" => { + state.mode = Mode::Settings; + return; + } + "cfg:size:1" => { + state.config.button_size = 1; + state.win_profile = u32::MAX; + persist_config(state); + return; + } + "cfg:size:2" => { + state.config.button_size = 2; + state.win_profile = u32::MAX; + persist_config(state); + return; + } + "cfg:size:3" => { + state.config.button_size = 3; + state.win_profile = u32::MAX; + persist_config(state); + return; + } + "cfg:theme:0" => { + state.config.theme = ThemeId::Dark; + state.win_profile = u32::MAX; + persist_config(state); + return; + } + "cfg:theme:1" => { + state.config.theme = ThemeId::Light; + state.win_profile = u32::MAX; + persist_config(state); + return; + } + "cfg:theme:2" => { + state.config.theme = ThemeId::Blue; + state.win_profile = u32::MAX; + persist_config(state); return; } _ => {} @@ -368,7 +491,7 @@ fn handle_key(state: &mut State, id: &str) { Mode::Engineering => handle_engineering(&mut state.eng, id), Mode::Programmer => handle_programmer(state, id), Mode::Convert => handle_convert(&mut state.conv, id), - Mode::Crc => {} + Mode::Crc | Mode::Settings => {} } } @@ -456,6 +579,13 @@ fn handle_standard(calc: &mut Calculator, id: &str) { fn handle_programmer(state: &mut State, id: &str) { if id == "bits" { state.show_bits = !state.show_bits; + persist_config(state); + return; + } + + if id == "word" { + state.prog.cycle_word_size(); + persist_config(state); return; } @@ -476,7 +606,6 @@ fn handle_programmer(state: &mut State, id: &str) { "base:dec" => calc.set_base(Base::Dec), "base:oct" => calc.set_base(Base::Oct), "base:bin" => calc.set_base(Base::Bin), - "word" => calc.cycle_word_size(), "+" => calc.set_op(ProgOp::Add), "-" | "−" => calc.set_op(ProgOp::Sub), "*" | "×" => calc.set_op(ProgOp::Mul), diff --git a/src/programmer.rs b/src/programmer.rs index dbe1c24..3c5683e 100644 --- a/src/programmer.rs +++ b/src/programmer.rs @@ -75,6 +75,18 @@ impl WordSize { } } + pub fn from_label(s: &str) -> Option { + match s.trim().to_ascii_uppercase().as_str() { + "QWORD" => Some(WordSize::Qword), + "DWORD" => Some(WordSize::Dword), + "DINT" => Some(WordSize::Dint), + "WORD" => Some(WordSize::Word), + "INT" => Some(WordSize::Int), + "BYTE" => Some(WordSize::Byte), + _ => None, + } + } + pub fn cycle(self) -> Self { match self { WordSize::Qword => WordSize::Dword, @@ -310,7 +322,17 @@ impl Programmer { } self.typing = false; } - self.word_size = self.word_size.cycle(); + self.set_word_size(self.word_size.cycle()); + } + + pub fn set_word_size(&mut self, size: WordSize) { + if self.typing { + if let Ok(v) = parse_entry(&self.entry, self.base) { + self.value = v; + } + self.typing = false; + } + self.word_size = size; self.value &= self.word_size.mask(); if let Some(p) = self.pending.as_mut() { *p &= self.word_size.mask(); diff --git a/ui/app.slint b/ui/app.slint index f95ab8a..0f8865c 100644 --- a/ui/app.slint +++ b/ui/app.slint @@ -3,17 +3,46 @@ export struct BitInfo { index: int, } +export global UiStyle { + in-out property size: 2; + in-out property theme: 0; + + in-out property bg: #202020; + in-out property surface: #2a2a2a; + in-out property btn: #323232; + in-out property btn-hover: #464646; + in-out property digit: #3b3b3b; + in-out property digit-hover: #505050; + in-out property accent: #60cdff; + in-out property accent-hover: #78d7ff; + in-out property text: #ffffff; + in-out property text-muted: #c8c8c8; + in-out property text-dim: #9a9a9a; + in-out property menu-active: #2f3a42; + in-out property eq-text: #141414; + in-out property border: #404040; + + in-out property btn-h: UiStyle.size == 1 ? 34px : (UiStyle.size == 3 ? 52px : 42px); + in-out property btn-h-sm: UiStyle.size == 1 ? 28px : (UiStyle.size == 3 ? 40px : 32px); + in-out property mem-h: UiStyle.size == 1 ? 26px : (UiStyle.size == 3 ? 38px : 32px); + in-out property tab-h: UiStyle.size == 1 ? 26px : (UiStyle.size == 3 ? 34px : 30px); + in-out property font-sm: UiStyle.size == 1 ? 12px : (UiStyle.size == 3 ? 15px : 13px); + in-out property font-md: UiStyle.size == 1 ? 13px : (UiStyle.size == 3 ? 16px : 14px); + in-out property font-digit: UiStyle.size == 1 ? 14px : (UiStyle.size == 3 ? 18px : 16px); + in-out property font-eq: UiStyle.size == 1 ? 16px : (UiStyle.size == 3 ? 20px : 18px); +} + component BitCell inherits Rectangle { callback toggled(); in property on; width: 14px; height: 16px; border-radius: 2px; - background: touch.has-hover ? #3a4550 : transparent; + background: touch.has-hover ? UiStyle.menu-active : transparent; Text { text: root.on ? "1" : "0"; - color: root.on ? #60cdff : #6a6a6a; + color: root.on ? UiStyle.accent : UiStyle.text-dim; font-size: 10px; font-weight: 700; font-family: "Cascadia Mono, Consolas, DejaVu Sans Mono, monospace"; @@ -78,14 +107,14 @@ component BitLine inherits Rectangle { component MemBtn inherits Rectangle { callback clicked(); in property label; - height: 32px; - background: touch.has-hover ? #323232 : transparent; + height: UiStyle.mem-h; + background: touch.has-hover ? UiStyle.btn : transparent; border-radius: 4px; Text { text: root.label; - color: #c8c8c8; - font-size: 13px; + color: UiStyle.text-muted; + font-size: UiStyle.font-sm; horizontal-alignment: center; vertical-alignment: center; } @@ -99,16 +128,16 @@ component FuncBtn inherits Rectangle { callback clicked(); in property label; in property enabled: true; - in property min-h: 42px; - background: !root.enabled ? #2a2a2a : (touch.has-hover ? #464646 : #323232); + in property min-h: UiStyle.btn-h; + background: !root.enabled ? UiStyle.surface : (touch.has-hover ? UiStyle.btn-hover : UiStyle.btn); border-radius: 4px; min-height: root.min-h; opacity: root.enabled ? 1.0 : 0.45; Text { text: root.label; - color: white; - font-size: 14px; + color: UiStyle.text; + font-size: UiStyle.font-md; horizontal-alignment: center; vertical-alignment: center; } @@ -123,16 +152,16 @@ component DigitBtn inherits Rectangle { callback clicked(); in property label; in property enabled: true; - in property min-h: 42px; - background: !root.enabled ? #2a2a2a : (touch.has-hover ? #505050 : #3b3b3b); + in property min-h: UiStyle.btn-h; + background: !root.enabled ? UiStyle.surface : (touch.has-hover ? UiStyle.digit-hover : UiStyle.digit); border-radius: 4px; min-height: root.min-h; opacity: root.enabled ? 1.0 : 0.4; Text { text: root.label; - color: white; - font-size: 16px; + color: UiStyle.text; + font-size: UiStyle.font-digit; font-weight: 600; horizontal-alignment: center; vertical-alignment: center; @@ -146,15 +175,15 @@ component DigitBtn inherits Rectangle { component EqBtn inherits Rectangle { callback clicked(); - in property min-h: 42px; - background: touch.has-hover ? #78d7ff : #60cdff; + in property min-h: UiStyle.btn-h; + background: touch.has-hover ? UiStyle.accent-hover : UiStyle.accent; border-radius: 4px; min-height: root.min-h; Text { text: "="; - color: #141414; - font-size: 18px; + color: UiStyle.eq-text; + font-size: UiStyle.font-eq; font-weight: 700; horizontal-alignment: center; vertical-alignment: center; @@ -170,11 +199,11 @@ component MenuRow inherits Rectangle { in property label; in property active; height: 36px; - background: root.active ? #2f3a42 : (touch.has-hover ? #323232 : transparent); + background: root.active ? UiStyle.menu-active : (touch.has-hover ? UiStyle.btn : transparent); Text { text: root.label; - color: root.active ? #60cdff : #e0e0e0; + color: root.active ? UiStyle.accent : UiStyle.text; font-size: 14px; font-weight: root.active ? 700 : 500; vertical-alignment: center; @@ -193,14 +222,14 @@ component ModeTab inherits Rectangle { callback clicked(); in property label; in property active; - height: 30px; + height: UiStyle.tab-h; border-radius: 4px; - background: root.active ? #3b3b3b : (touch.has-hover ? #2c2c2c : transparent); + background: root.active ? UiStyle.btn : (touch.has-hover ? UiStyle.surface : transparent); Text { text: root.label; - color: root.active ? #60cdff : #c8c8c8; - font-size: 13px; + color: root.active ? UiStyle.accent : UiStyle.text-muted; + font-size: UiStyle.font-sm; font-weight: root.active ? 700 : 500; horizontal-alignment: center; vertical-alignment: center; @@ -325,12 +354,14 @@ export component AppWindow inherits Window { preferred-height: 640px; min-width: 380px; min-height: 520px; - background: #202020; + background: UiStyle.bg; - // 0=standard 1=engineering 2=programmer 3=convert 4=crc + // 0=standard 1=engineering 2=programmer 3=convert 4=crc 5=settings in-out property mode: 0; in-out property menu-open: false; in property mode-title: "Standard"; + in property cfg-button-size: 2; + in property cfg-theme: 0; in property display-text: "0"; in property expression-text: ""; @@ -414,7 +445,7 @@ export component AppWindow inherits Window { width: 34px; height: 34px; border-radius: 4px; - background: menu-touch.has-hover || root.menu-open ? #3b3b3b : transparent; + background: menu-touch.has-hover || root.menu-open ? UiStyle.btn : transparent; // Drawn icon — Unicode ☰ is missing in many Windows fonts. VerticalLayout { @@ -424,9 +455,9 @@ export component AppWindow inherits Window { padding-right: 8px; spacing: 4px; - Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; } - Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; } - Rectangle { height: 2px; background: #e8e8e8; border-radius: 1px; } + Rectangle { height: 2px; background: UiStyle.text; border-radius: 1px; } + Rectangle { height: 2px; background: UiStyle.text; border-radius: 1px; } + Rectangle { height: 2px; background: UiStyle.text; border-radius: 1px; } } menu-touch := TouchArea { @@ -444,14 +475,14 @@ export component AppWindow inherits Window { x: 0; y: 38px; width: 210px; - height: 220px; + height: 260px; close-on-click: true; Rectangle { - background: #2a2a2a; + background: UiStyle.surface; border-radius: 6px; border-width: 1px; - border-color: #404040; + border-color: UiStyle.border; VerticalLayout { padding: 6px; @@ -502,6 +533,15 @@ export component AppWindow inherits Window { mode-menu.close(); } } + MenuRow { + label: "Settings"; + active: root.mode == 5; + clicked => { + root.press("mode:settings"); + root.menu-open = false; + mode-menu.close(); + } + } } } } @@ -509,7 +549,7 @@ export component AppWindow inherits Window { Text { text: root.mode-title; - color: white; + color: UiStyle.text; font-size: 18px; font-weight: 700; vertical-alignment: center; @@ -521,7 +561,7 @@ export component AppWindow inherits Window { height: 18px; Text { text: root.has-memory && (root.mode == 0 || root.mode == 1) ? "M" : ""; - color: #b4b4b4; + color: UiStyle.text-muted; font-size: 12px; horizontal-alignment: left; vertical-alignment: center; @@ -529,13 +569,13 @@ export component AppWindow inherits Window { } Text { text: root.mode == 2 ? root.word-size-label : ""; - color: #60cdff; + color: UiStyle.accent; font-size: 11px; vertical-alignment: center; } Text { text: root.expression-text; - color: #aaaaaa; + color: UiStyle.text-dim; font-size: 13px; horizontal-alignment: right; vertical-alignment: center; @@ -544,7 +584,7 @@ export component AppWindow inherits Window { if root.mode == 0 || root.mode == 1 || root.mode == 2: Text { text: root.display-text; - color: white; + color: UiStyle.text; font-size: root.mode == 2 ? 28px : 34px; font-weight: 700; horizontal-alignment: right; @@ -1141,85 +1181,85 @@ export component AppWindow inherits Window { spacing: 4px; Row { FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "2nd●" : "2nd"; clicked => { root.press("eng:2nd"); } } - FuncBtn { min-h: 32px; label: "π"; clicked => { root.press("eng:pi"); } } - FuncBtn { min-h: 32px; label: "e"; clicked => { root.press("eng:e"); } } - FuncBtn { min-h: 32px; label: "C"; clicked => { root.press("C"); } } - FuncBtn { min-h: 32px; label: "⌫"; clicked => { root.press("BS"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "π"; clicked => { root.press("eng:pi"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "e"; clicked => { root.press("eng:e"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "C"; clicked => { root.press("C"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "⌫"; clicked => { root.press("BS"); } } } Row { FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "sin⁻¹" : "sin"; clicked => { root.press(root.eng-second ? "eng:asin" : "eng:sin"); } } FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "cos⁻¹" : "cos"; clicked => { root.press(root.eng-second ? "eng:acos" : "eng:cos"); } } FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "tan⁻¹" : "tan"; clicked => { root.press(root.eng-second ? "eng:atan" : "eng:tan"); } } FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "³√x" : "√x"; clicked => { root.press(root.eng-second ? "eng:cbrt" : "eng:sqrt"); } } - FuncBtn { min-h: 32px; label: "÷"; clicked => { root.press("/"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "÷"; clicked => { root.press("/"); } } } Row { FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "x³" : "x²"; clicked => { root.press(root.eng-second ? "eng:cube" : "eng:sq"); } } - FuncBtn { min-h: 32px; label: "1/x"; clicked => { root.press("eng:inv"); } } - FuncBtn { min-h: 32px; label: "|x|"; clicked => { root.press("eng:abs"); } } - FuncBtn { min-h: 32px; label: "n!"; clicked => { root.press("eng:fact"); } } - FuncBtn { min-h: 32px; label: "×"; clicked => { root.press("*"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "1/x"; clicked => { root.press("eng:inv"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "|x|"; clicked => { root.press("eng:abs"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "n!"; clicked => { root.press("eng:fact"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "×"; clicked => { root.press("*"); } } } Row { - FuncBtn { min-h: 32px; label: "x^y"; clicked => { root.press("^"); } } - FuncBtn { min-h: 32px; label: "("; clicked => { root.press("("); } } - FuncBtn { min-h: 32px; label: ")"; clicked => { root.press(")"); } } - FuncBtn { min-h: 32px; label: "CE"; clicked => { root.press("CE"); } } - FuncBtn { min-h: 32px; label: "−"; clicked => { root.press("-"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "x^y"; clicked => { root.press("^"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "("; clicked => { root.press("("); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: ")"; clicked => { root.press(")"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "CE"; clicked => { root.press("CE"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "−"; clicked => { root.press("-"); } } } Row { FuncBtn { - min-h: 32px; + min-h: UiStyle.btn-h-sm; label: root.eng-second ? "eˣ" : "10ˣ"; clicked => { root.press(root.eng-second ? "eng:exp" : "eng:tenpow"); } } - DigitBtn { min-h: 32px; label: "7"; clicked => { root.press("7"); } } - DigitBtn { min-h: 32px; label: "8"; clicked => { root.press("8"); } } - DigitBtn { min-h: 32px; label: "9"; clicked => { root.press("9"); } } - FuncBtn { min-h: 32px; label: "+"; clicked => { root.press("+"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "7"; clicked => { root.press("7"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "8"; clicked => { root.press("8"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "9"; clicked => { root.press("9"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "+"; clicked => { root.press("+"); } } } Row { - FuncBtn { min-h: 32px; label: "log"; clicked => { root.press("eng:log"); } } - DigitBtn { min-h: 32px; label: "4"; clicked => { root.press("4"); } } - DigitBtn { min-h: 32px; label: "5"; clicked => { root.press("5"); } } - DigitBtn { min-h: 32px; label: "6"; clicked => { root.press("6"); } } - DigitBtn { min-h: 32px; label: "±"; clicked => { root.press("neg"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "log"; clicked => { root.press("eng:log"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "4"; clicked => { root.press("4"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "5"; clicked => { root.press("5"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "6"; clicked => { root.press("6"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "±"; clicked => { root.press("neg"); } } } Row { - FuncBtn { min-h: 32px; label: "ln"; clicked => { root.press("eng:ln"); } } - DigitBtn { min-h: 32px; label: "1"; clicked => { root.press("1"); } } - DigitBtn { min-h: 32px; label: "2"; clicked => { root.press("2"); } } - DigitBtn { min-h: 32px; label: "3"; clicked => { root.press("3"); } } - EqBtn { min-h: 32px; clicked => { root.press("="); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "ln"; clicked => { root.press("eng:ln"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "1"; clicked => { root.press("1"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "2"; clicked => { root.press("2"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "3"; clicked => { root.press("3"); } } + EqBtn { min-h: UiStyle.btn-h-sm; clicked => { root.press("="); } } } Row { - DigitBtn { min-h: 32px; label: "0"; clicked => { root.press("0"); } } - DigitBtn { min-h: 32px; label: "."; clicked => { root.press("."); } } - FuncBtn { min-h: 32px; label: "exp"; clicked => { root.press("eng:exp"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "0"; clicked => { root.press("0"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "."; clicked => { root.press("."); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "exp"; clicked => { root.press("eng:exp"); } } Rectangle { min-height: 32px; background: transparent; } Rectangle { min-height: 32px; background: transparent; } } @@ -1295,62 +1335,136 @@ export component AppWindow inherits Window { if root.mode == 2: GridLayout { spacing: 4px; Row { - FuncBtn { min-h: 34px; label: root.word-size-label; clicked => { root.press("word"); } } - FuncBtn { min-h: 34px; label: "CE"; clicked => { root.press("CE"); } } - FuncBtn { min-h: 34px; label: "C"; clicked => { root.press("clear"); } } - FuncBtn { min-h: 34px; label: "⌫"; clicked => { root.press("BS"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: root.word-size-label; clicked => { root.press("word"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "CE"; clicked => { root.press("CE"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "C"; clicked => { root.press("clear"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "⌫"; clicked => { root.press("BS"); } } } Row { - FuncBtn { min-h: 34px; label: "AND"; clicked => { root.press("AND"); } } - FuncBtn { min-h: 34px; label: "OR"; clicked => { root.press("OR"); } } - FuncBtn { min-h: 34px; label: "NOT"; clicked => { root.press("NOT"); } } - FuncBtn { min-h: 34px; label: "NAND"; clicked => { root.press("NAND"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "AND"; clicked => { root.press("AND"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "OR"; clicked => { root.press("OR"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "NOT"; clicked => { root.press("NOT"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "NAND"; clicked => { root.press("NAND"); } } } Row { - FuncBtn { min-h: 34px; label: "NOR"; clicked => { root.press("NOR"); } } - FuncBtn { min-h: 34px; label: "XOR"; clicked => { root.press("XOR"); } } - FuncBtn { min-h: 34px; label: "Lsh"; clicked => { root.press("Lsh"); } } - FuncBtn { min-h: 34px; label: "Rsh"; clicked => { root.press("Rsh"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "NOR"; clicked => { root.press("NOR"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "XOR"; clicked => { root.press("XOR"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "Lsh"; clicked => { root.press("Lsh"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "Rsh"; clicked => { root.press("Rsh"); } } } Row { - DigitBtn { min-h: 34px; label: "A"; enabled: root.allow-a; clicked => { root.press("A"); } } - DigitBtn { min-h: 34px; label: "B"; enabled: root.allow-a; clicked => { root.press("B"); } } - DigitBtn { min-h: 34px; label: "C"; enabled: root.allow-a; clicked => { root.press("C"); } } - FuncBtn { min-h: 34px; label: "÷"; clicked => { root.press("/"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "A"; enabled: root.allow-a; clicked => { root.press("A"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "B"; enabled: root.allow-a; clicked => { root.press("B"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "C"; enabled: root.allow-a; clicked => { root.press("C"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "÷"; clicked => { root.press("/"); } } } Row { - DigitBtn { min-h: 34px; label: "D"; enabled: root.allow-a; clicked => { root.press("D"); } } - DigitBtn { min-h: 34px; label: "E"; enabled: root.allow-a; clicked => { root.press("E"); } } - DigitBtn { min-h: 34px; label: "F"; enabled: root.allow-a; clicked => { root.press("F"); } } - FuncBtn { min-h: 34px; label: "×"; clicked => { root.press("*"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "D"; enabled: root.allow-a; clicked => { root.press("D"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "E"; enabled: root.allow-a; clicked => { root.press("E"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "F"; enabled: root.allow-a; clicked => { root.press("F"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "×"; clicked => { root.press("*"); } } } Row { - DigitBtn { min-h: 34px; label: "7"; enabled: root.allow-2; clicked => { root.press("7"); } } - DigitBtn { min-h: 34px; label: "8"; enabled: root.allow-8; clicked => { root.press("8"); } } - DigitBtn { min-h: 34px; label: "9"; enabled: root.allow-8; clicked => { root.press("9"); } } - FuncBtn { min-h: 34px; label: "−"; clicked => { root.press("-"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "7"; enabled: root.allow-2; clicked => { root.press("7"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "8"; enabled: root.allow-8; clicked => { root.press("8"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "9"; enabled: root.allow-8; clicked => { root.press("9"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "−"; clicked => { root.press("-"); } } } Row { - DigitBtn { min-h: 34px; label: "4"; enabled: root.allow-2; clicked => { root.press("4"); } } - DigitBtn { min-h: 34px; label: "5"; enabled: root.allow-2; clicked => { root.press("5"); } } - DigitBtn { min-h: 34px; label: "6"; enabled: root.allow-2; clicked => { root.press("6"); } } - FuncBtn { min-h: 34px; label: "+"; clicked => { root.press("+"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "4"; enabled: root.allow-2; clicked => { root.press("4"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "5"; enabled: root.allow-2; clicked => { root.press("5"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "6"; enabled: root.allow-2; clicked => { root.press("6"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "+"; clicked => { root.press("+"); } } } Row { - DigitBtn { min-h: 34px; label: "1"; clicked => { root.press("1"); } } - DigitBtn { min-h: 34px; label: "2"; enabled: root.active-base != 3; clicked => { root.press("2"); } } - DigitBtn { min-h: 34px; label: "3"; enabled: root.allow-2; clicked => { root.press("3"); } } - FuncBtn { min-h: 34px; label: "%"; clicked => { root.press("%"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "1"; clicked => { root.press("1"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "2"; enabled: root.active-base != 3; clicked => { root.press("2"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "3"; enabled: root.allow-2; clicked => { root.press("3"); } } + FuncBtn { min-h: UiStyle.btn-h-sm; label: "%"; clicked => { root.press("%"); } } } Row { - DigitBtn { min-h: 34px; label: "±"; clicked => { root.press("neg"); } } - DigitBtn { min-h: 34px; label: "0"; clicked => { root.press("0"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "±"; clicked => { root.press("neg"); } } + DigitBtn { min-h: UiStyle.btn-h-sm; label: "0"; clicked => { root.press("0"); } } FuncBtn { - min-h: 34px; + min-h: UiStyle.btn-h-sm; label: root.show-bits ? "Bits●" : "Bits"; clicked => { root.press("bits"); } } - EqBtn { min-h: 34px; clicked => { root.press("="); } } + EqBtn { min-h: UiStyle.btn-h-sm; clicked => { root.press("="); } } + } + } + + if root.mode == 5: VerticalLayout { + spacing: 14px; + padding-top: 8px; + + Text { + text: "Appearance"; + color: UiStyle.accent; + font-size: 16px; + font-weight: 700; + } + + Text { + text: "Button size"; + color: UiStyle.text-dim; + font-size: 12px; + } + HorizontalLayout { + spacing: 6px; + ModeTab { + label: "1 Compact"; + active: root.cfg-button-size == 1; + clicked => { root.press("cfg:size:1"); } + } + ModeTab { + label: "2 Normal"; + active: root.cfg-button-size == 2; + clicked => { root.press("cfg:size:2"); } + } + ModeTab { + label: "3 Large"; + active: root.cfg-button-size == 3; + clicked => { root.press("cfg:size:3"); } + } + } + + Text { + text: "Theme"; + color: UiStyle.text-dim; + font-size: 12px; + } + HorizontalLayout { + spacing: 6px; + ModeTab { + label: "Dark"; + active: root.cfg-theme == 0; + clicked => { root.press("cfg:theme:0"); } + } + ModeTab { + label: "Light"; + active: root.cfg-theme == 1; + clicked => { root.press("cfg:theme:1"); } + } + ModeTab { + label: "Blue"; + active: root.cfg-theme == 2; + clicked => { root.press("cfg:theme:2"); } + } + } + + Text { + text: "Settings are saved automatically."; + color: UiStyle.text-dim; + font-size: 12px; + } + + HorizontalLayout { + spacing: 6px; + FuncBtn { + label: "Back"; + clicked => { root.press("mode:std"); } + } } } }