Initial Commit

This commit is contained in:
2023-03-17 23:33:34 +01:00
commit e22588c2cb
11 changed files with 432 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

18
Cargo.toml Normal file
View File

@ -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"]

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# tgnt
Keyboard configuration library and editor.

25
src/bin/default_layer.ron Normal file
View File

@ -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,
],
)

View File

@ -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),
]
)

200
src/bin/editor.rs Normal file
View File

@ -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<Layout>,
layers: Vec<RonEdit<Layer>>,
u1: f32,
margin: f32,
}
#[derive(Deserialize, Serialize)]
struct RonEdit<T> {
pub t: T,
pub ron: String,
pub error: String,
}
impl<T: Serialize> RonEdit<T> {
pub fn new(t: T) -> Self {
RonEdit {
ron: ron::ser::to_string_pretty(&t, Default::default()).unwrap(),
error: String::new(),
t,
}
}
}
impl<T: Default + Serialize> Default for RonEdit<T> {
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();
}
})
});
}
}

39
src/button.rs Normal file
View File

@ -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, "Ø"),
}
}
}

67
src/keys.rs Normal file
View File

@ -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<Key> for u8 {
fn from(key: Key) -> u8 {
key as u8
}
}

9
src/layer.rs Normal file
View File

@ -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<Button>,
}

36
src/layout.rs Normal file
View File

@ -0,0 +1,36 @@
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Layout {
pub buttons: Vec<Rect>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Rect {
pub x: f32,
pub y: f32,
#[serde(default = "default_width")]
pub w: f32,
#[serde(default = "default_height")]
pub h: f32,
}
impl Default for Rect {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
w: default_width(),
h: default_height(),
}
}
}
fn default_width() -> f32 {
1.0
}
fn default_height() -> f32 {
1.0
}

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#![no_std]
extern crate alloc;
pub mod button;
pub mod keys;
pub mod layer;
pub mod layout;