From e22588c2cb139864fcd3be1087350803343c622b Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Fri, 17 Mar 2023 23:33:34 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 2 + Cargo.toml | 18 ++++ README.md | 3 + src/bin/default_layer.ron | 25 +++++ src/bin/default_layout.ron | 25 +++++ src/bin/editor.rs | 200 +++++++++++++++++++++++++++++++++++++ src/button.rs | 39 ++++++++ src/keys.rs | 67 +++++++++++++ src/layer.rs | 9 ++ src/layout.rs | 36 +++++++ src/lib.rs | 8 ++ 11 files changed, 432 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/bin/default_layer.ron create mode 100644 src/bin/default_layout.ron create mode 100644 src/bin/editor.rs create mode 100644 src/button.rs create mode 100644 src/keys.rs create mode 100644 src/layer.rs create mode 100644 src/layout.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d0eaa08 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tgnt" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "editor" +required-features = ["eframe", "egui", "egui_extras"] + +[dependencies] +eframe = { version = "0.21.3", features = ["persistence", "ron", "serde"], optional = true } +egui = { version = "0.21.0", features = ["persistence", "ron", "serde"], optional = true } +egui_extras = { version = "0.21.0", optional = true } +ron = { version = "0.8.0", optional = true } +serde = { version = "1.0.156", default-features = false, features = ["alloc", "derive"] } + +[features] +default = ["eframe", "egui", "egui_extras", "ron"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..9de00e7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tgnt + +Keyboard configuration library and editor. diff --git a/src/bin/default_layer.ron b/src/bin/default_layer.ron new file mode 100644 index 0000000..feac760 --- /dev/null +++ b/src/bin/default_layer.ron @@ -0,0 +1,25 @@ +Layer( + buttons: [ + Keycode(0x04), + Keycode(0x05), + Keycode(0x06), + Keycode(0x07), + Keycode(0x08), + + Keycode(0x09), + Keycode(0x0A), + Keycode(0x0B), + Keycode(0x0C), + Keycode(0x0D), + + Keycode(0x0E), + Keycode(0x0F), + Keycode(0x10), + Keycode(0x11), + ModTap( keycode: 0x12, modifier: LMod), + + Modifier(LShift), + Modifier(LCtrl), + NextLayer, + ], +) diff --git a/src/bin/default_layout.ron b/src/bin/default_layout.ron new file mode 100644 index 0000000..bbb8546 --- /dev/null +++ b/src/bin/default_layout.ron @@ -0,0 +1,25 @@ +Layout( + buttons: [ + (x: 0.0, y: 0.4), + (x: 1.0, y: 0.2), + (x: 2.0, y: 0.0), + (x: 3.0, y: 0.2), + (x: 4.0, y: 0.4), + + (x: 0.0, y: 1.4), + (x: 1.0, y: 1.2), + (x: 2.0, y: 1.0), + (x: 3.0, y: 1.2), + (x: 4.0, y: 1.4), + + (x: 0.0, y: 2.4), + (x: 1.0, y: 2.2), + (x: 2.0, y: 2.0), + (x: 3.0, y: 2.2), + (x: 4.0, y: 2.4), + + (x: 3.0, y: 3.2), + (x: 4.0, y: 3.4), + (x: 5.0, y: 3.6), + ] +) diff --git a/src/bin/editor.rs b/src/bin/editor.rs new file mode 100644 index 0000000..e7871c7 --- /dev/null +++ b/src/bin/editor.rs @@ -0,0 +1,200 @@ +use egui::{Button, Color32, Frame, Rect, ScrollArea, Slider, TextEdit, Vec2}; +use serde::{Deserialize, Serialize}; +use tgnt::{layer::Layer, layout::Layout}; + +fn main() { + let native_options = eframe::NativeOptions::default(); + eframe::run_native( + "tgnt keyboard editor", + native_options, + Box::new(|cc| Box::new(App::new(cc))), + ) + .expect("Failed to run program"); +} + +/// We derive Deserialize/Serialize so we can persist app state on shutdown. +#[derive(Default, Deserialize, Serialize)] +#[serde(default)] // if we add new fields, give them default values when deserializing old state +pub struct App { + layout: RonEdit, + + layers: Vec>, + + u1: f32, + margin: f32, +} + +#[derive(Deserialize, Serialize)] +struct RonEdit { + pub t: T, + pub ron: String, + pub error: String, +} + +impl RonEdit { + pub fn new(t: T) -> Self { + RonEdit { + ron: ron::ser::to_string_pretty(&t, Default::default()).unwrap(), + error: String::new(), + t, + } + } +} + +impl Default for RonEdit { + fn default() -> Self { + RonEdit::new(Default::default()) + } +} + +impl App { + /// 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. + if let Some(storage) = cc.storage { + return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + } + + let layer_str = include_str!("default_layer.ron"); + let layer: Layer = ron::from_str(&layer_str).expect("Failed to deserialize default layer"); + + let layout_str = include_str!("default_layout.ron"); + let layout: Layout = + ron::from_str(&layout_str).expect("Failed to deserialize default layout"); + + Self { + layers: vec![RonEdit::new(layer)], + layout: RonEdit::new(layout), + u1: 75.0, + margin: 2.0, + } + } +} + +impl eframe::App for App { + /// 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 { + margin, + u1, + layout, + layers, + } = self; + + #[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages! + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + // The top panel is often a good place for a menu bar: + egui::menu::bar(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + _frame.close(); + } + }); + }); + }); + + egui::SidePanel::left("side_panel") + .resizable(true) + .width_range(250.0..=500.0) + .show(ctx, |ui| { + ScrollArea::vertical().show(ui, |ui| { + ui.heading("Side Panel"); + ui.label("u1"); + ui.add(Slider::new(u1, 20.0..=150.0)); + ui.label("margin"); + ui.add(Slider::new(margin, 0.0..=20.0)); + + ui.collapsing("Layout", |ui| { + if ui + .add(TextEdit::multiline(&mut layout.ron).code_editor()) + .changed() + { + layout.error.clear(); + match ron::from_str(&layout.ron) { + Ok(new) => layout.t = new, + Err(e) => layout.error = e.to_string(), + } + } + + if !layout.error.is_empty() { + ui.add( + TextEdit::multiline(&mut layout.error) + .interactive(false) + .text_color(Color32::RED), + ); + } + }); + + let mut to_delete = None; + for (i, layer) in layers.iter_mut().enumerate() { + ui.collapsing(format!("Layer {i}"), |ui| { + if ui.button("Delete").clicked() { + to_delete = Some(i); + } + + if ui + .add(TextEdit::multiline(&mut layer.ron).code_editor()) + .changed() + { + layer.error.clear(); + match ron::from_str(&layer.ron) { + Ok(new) => layer.t = new, + Err(e) => layer.error = e.to_string(), + } + } + + if !layer.error.is_empty() { + ui.add( + TextEdit::multiline(&mut layer.error) + .interactive(false) + .text_color(Color32::RED), + ); + } + }); + } + + if let Some(i) = to_delete { + layers.remove(i); + } + + if ui.button("Add layer").clicked() { + layers.push(RonEdit::default()); + } + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + ScrollArea::both().show(ui, |ui| { + for layer in layers { + Frame::none().show(ui, |ui| { + for (i, button) in layer.t.buttons.iter().enumerate() { + if let Some(geometry) = layout.t.buttons.get(i) { + let size = Vec2::new( + *u1 * geometry.w - *margin * 2.0, + *u1 * geometry.h - *margin * 2.0, + ); + let offset = Vec2::new( + *u1 * geometry.x + *margin, + *u1 * geometry.y + *margin, + ); + let rect = Rect::from_min_size(ui.min_rect().min + offset, size); + ui.put(rect, Button::new(button.to_string())); + } + } + }); + ui.separator(); + } + }) + }); + } +} diff --git a/src/button.rs b/src/button.rs new file mode 100644 index 0000000..bc066cb --- /dev/null +++ b/src/button.rs @@ -0,0 +1,39 @@ +use core::fmt::{self, Debug, Display}; +use serde::{Deserialize, Serialize}; + +use crate::keys::Key; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Modifier { + LShift, + LCtrl, + LAlt, + LMod, + RShift, + RCtrl, + RAlt, + RMod, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Button { + Mod(Modifier), + Key(Key), + ModTap { keycode: Key, modifier: Modifier }, + NextLayer, + PrevLayer, + None, +} + +impl Display for Button { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Button::Mod(modifier) => Debug::fmt(&modifier, f), + Button::Key(keycode) => write!(f, "{keycode:?}"), + Button::ModTap { keycode, modifier } => write!(f, "{keycode:?}/{modifier:?}"), + Button::NextLayer => write!(f, "↗"), + Button::PrevLayer => write!(f, "↘"), + Button::None => write!(f, "Ø"), + } + } +} diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..9258ee4 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,67 @@ +use serde::{Deserialize, Serialize}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +// https://usb.org/sites/default/files/hut1_3_0.pdf +pub enum Key { + A = 0x04, + B = 0x05, + C = 0x06, + D = 0x07, + E = 0x08, + F = 0x09, + G = 0x0A, + H = 0x0B, + I = 0x0C, + J = 0x0D, + K = 0x0E, + L = 0x0F, + M = 0x10, + N = 0x11, + O = 0x12, + P = 0x13, + Q = 0x14, + R = 0x15, + S = 0x16, + T = 0x17, + U = 0x18, + V = 0x19, + W = 0x1A, + X = 0x1B, + Y = 0x1C, + Z = 0x1D, + + D1 = 0x1E, + D2 = 0x1F, + D3 = 0x20, + D4 = 0x21, + D5 = 0x22, + D6 = 0x23, + D7 = 0x24, + D8 = 0x25, + D9 = 0x26, + D0 = 0x27, + + Colon = 0x33, + Apostrophe = 0x34, + Comma = 0x36, + Period = 0x37, + + Space = 0x2C, + Return = 0x28, + + LCtrl = 0xE0, + RCtrl = 0xE4, + + LShift = 0xE1, + RShift = 0xE5, + + LAlt = 0xE2, + RAlt = 0xE6, +} + +impl From for u8 { + fn from(key: Key) -> u8 { + key as u8 + } +} diff --git a/src/layer.rs b/src/layer.rs new file mode 100644 index 0000000..cbd8f04 --- /dev/null +++ b/src/layer.rs @@ -0,0 +1,9 @@ +use alloc::vec::Vec; +use serde::{Deserialize, Serialize}; + +use crate::button::Button; + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct Layer { + pub buttons: Vec