Initial Commit
This commit is contained in:
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