Initial Commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
/Cargo.lock
|
||||||
18
Cargo.toml
Normal file
18
Cargo.toml
Normal 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"]
|
||||||
25
src/bin/default_layer.ron
Normal file
25
src/bin/default_layer.ron
Normal 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,
|
||||||
|
],
|
||||||
|
)
|
||||||
25
src/bin/default_layout.ron
Normal file
25
src/bin/default_layout.ron
Normal 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
200
src/bin/editor.rs
Normal 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
39
src/button.rs
Normal 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
67
src/keys.rs
Normal 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
9
src/layer.rs
Normal 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
36
src/layout.rs
Normal 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
8
src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
pub mod button;
|
||||||
|
pub mod keys;
|
||||||
|
pub mod layer;
|
||||||
|
pub mod layout;
|
||||||
Reference in New Issue
Block a user