diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f9a0b96..bb4c272 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,19 +1,57 @@ -use enigo::{Direction::Click, Enigo, Key, Keyboard, Settings}; +use enigo::{Direction, Enigo, Key, Keyboard, Settings}; +#[cfg(target_os = "linux")] use std::ptr; use tauri::Manager; // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ #[tauri::command] fn greet(name: &str) -> String { - let mut enigo = Enigo::new(&Settings::default()).unwrap(); - enigo.key(Key::Unicode('a'), Click); + // let mut enigo = Enigo::new(&Settings::default()).unwrap(); + // enigo.key(Key::Unicode('a'), Click); format!("Hello, {}! You've been greeted from Rust!", name) } +#[tauri::command] +fn send_key(key: String) -> Result<(), String> { + let mut enigo = Enigo::new(&Settings::default()).map_err(|e| format!("Enigo error: {}", e))?; + + if key.len() == 1 { + let ch = key.chars().next().unwrap(); + + // Check if uppercase letter + if ch.is_uppercase() && ch.is_alphabetic() { + // Send Shift + lowercase letter + enigo.key(Key::Shift, Direction::Press) + .map_err(|e| format!("Shift press error: {}", e))?; + enigo.key(Key::Unicode(ch.to_lowercase().next().unwrap()), Direction::Click) + .map_err(|e| format!("Key error: {}", e))?; + enigo.key(Key::Shift, Direction::Release) + .map_err(|e| format!("Shift release error: {}", e))?; + } else { + // Send character as-is (lowercase or non-letter) + enigo.key(Key::Unicode(ch), Direction::Click) + .map_err(|e| format!("Key error: {}", e))?; + } + } else { + // Special keys + match key.as_str() { + "Enter" => enigo.key(Key::Return, Direction::Click) + .map_err(|e| format!("Key error: {}", e))?, + "Space" => enigo.key(Key::Space, Direction::Click) + .map_err(|e| format!("Key error: {}", e))?, + "Backspace" => enigo.key(Key::Backspace, Direction::Click) + .map_err(|e| format!("Key error: {}", e))?, + _ => return Err("Unknown key".to_string()), + } + } + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|app| { + #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] let window = app.get_webview_window("main").unwrap(); #[cfg(target_os = "linux")] { @@ -40,7 +78,7 @@ pub fn run() { Ok(()) }) .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![greet]) + .invoke_handler(tauri::generate_handler![greet, send_key]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index b077b11..051b909 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,7 +14,11 @@ { "title": "keeb", "width": 800, - "height": 600 + "height": 700, + "minWidth": 800, + "minHeight": 700, + "resizable": true, + "center": true } ], "security": { diff --git a/src/app/app.component.css b/src/app/app.component.css index c68d181..7f3b095 100644 --- a/src/app/app.component.css +++ b/src/app/app.component.css @@ -1,6 +1,4 @@ -.logo.angular:hover { - filter: drop-shadow(0 0 2em #e32727); -} + :root { font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-size: 16px; @@ -33,10 +31,6 @@ transition: 0.75s; } -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - .row { display: flex; justify-content: center; @@ -109,4 +103,56 @@ button { button:active { background-color: #0f0f0f69; } + + .key-button { + background: rgb(58, 56, 56); + color: white; + } +} + +.keyboard { + display: flex; + flex-direction: column; + gap: 8px; + padding: 20px; + background: #f5f5f5; + border-radius: 10px; + margin: 20px auto; +} + +.keyboard-row { + display: flex; + gap: 6px; + justify-content: center; +} + +.key-button { + min-width: 60px; + height: 60px; + font-size: 20px; + font-weight: bold; + border: 2px solid #ddd; + border-radius: 8px; + cursor: pointer; + transition: all 0.1s; +} + +.key-button:hover { + background: #b1b1b1; + border-color: #3d3d3d; +} + +.key-button:active { + background: #d0d0d0; + transform: scale(0.95); +} + +.shift-key.active { + background: #4CAF50; + color: white; + border-color: #45a049; +} + +.space-key { + min-width: 300px; } diff --git a/src/app/app.component.html b/src/app/app.component.html index e447606..3585f69 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,19 +1,37 @@ - Welcome to Tauri + Angular! - - - - - - - - - - Click on the logos to learn more about the frameworks - Greet + {{ greetingMessage }} + + + + + Switch Layout: {{ currentLayout.toUpperCase() }} + + + + + + + + + + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ad8d5ff..0597add 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,24 +1,68 @@ -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterOutlet } from '@angular/router'; import { invoke } from "@tauri-apps/api/core"; +import { QwertyKeyboardComponent } from './keyboards/qwerty-keyboard.component'; +import { DvorakKeyboardComponent } from './keyboards/dvorak-keyboard.component'; +import { CircleKeyboardComponent } from './keyboards/circle-keyboard.component'; @Component({ selector: 'app-root', standalone: true, - imports: [CommonModule, RouterOutlet], + imports: [ + CommonModule, + RouterOutlet, + QwertyKeyboardComponent, + DvorakKeyboardComponent, + CircleKeyboardComponent + ], templateUrl: './app.component.html', styleUrl: './app.component.css' }) -export class AppComponent { +export class AppComponent implements AfterViewInit { greetingMessage = ""; + currentLayout: 'qwerty' | 'dvorak' | 'circle' = 'qwerty'; + shiftActive = false; + + @ViewChild('greetInput') inputElement!: ElementRef; + + ngAfterViewInit() { + this.inputElement.nativeElement.focus(); + } greet(event: SubmitEvent, name: string): void { event.preventDefault(); - - // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/ invoke("greet", { name }).then((text) => { this.greetingMessage = text; }); } + + toggleShift(): void { + this.shiftActive = !this.shiftActive; + } + + switchLayout(): void { + if (this.currentLayout == 'qwerty'){ + this.currentLayout = 'dvorak'; + } else if (this.currentLayout == 'dvorak'){ + this.currentLayout = 'circle'; + } else if (this.currentLayout == 'circle'){ + this.currentLayout = 'qwerty'; + } + } + + async handleKeyPress(key: string): Promise { + this.inputElement.nativeElement.focus(); + + let finalKey = key; + if (key.length === 1) { + finalKey = this.shiftActive ? key.toUpperCase() : key.toLowerCase(); + } + + await invoke("send_key", { key: finalKey }); + + if (this.shiftActive && key.length === 1) { + this.shiftActive = false; + } + } } diff --git a/src/app/keyboards/circle-keyboard.component.css b/src/app/keyboards/circle-keyboard.component.css new file mode 100644 index 0000000..c61d1a4 --- /dev/null +++ b/src/app/keyboards/circle-keyboard.component.css @@ -0,0 +1,161 @@ +.circle-keyboard-container { + position: relative; + width: 400px; + height: 400px; + margin: 50px auto; + background: #f5f5f5; +} + +/* Center Button Container */ +.center-button-container { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 140px; + height: 140px; + z-index: 1; +} + +/* Center Button Sections */ +.center-section { + position: absolute; + background: white; + color: #0f0f0f; + font-size: 20px; + font-weight: bold; + cursor: pointer; + transition: background 0.2s; + border: 2px solid #ddd; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +/* Top-left: Backspace (triangle) */ +.top-left{ + top: 0; + left: 0; + width: 70px; + height: 70px; + border-radius: 70px 0 0 0; +} + +/* Top-right: Enter*/ +.top-right { + top: 0; + right: 0; + width: 70px; + height: 70px; + border-radius: 0 70px 0 0; +} + +/* Bottom: Space */ +.bottom-left { + bottom: 0; + left: 0; + width: 70px; + height: 70px; + border-radius: 0 0 0 70px; +} + +.bottom-left.active { + background: #4CAF50 !important; + color: white; + border-color: #45a049; +} + +.bottom-right { + bottom: 0; + right: 0; + width: 70px; + height: 70px; + border-radius: 0 0 70px 0; +} + +.middle-left { + top: 50%; + left: 50%; + transform: translate(-100%, -50%); + width: 30px; + height: 55px; + border-radius: 35px 0 0 35px; + z-index: 15; +} + +.middle-right { + top: 50%; + left: 50%; + transform: translate(0%, -50%); + width: 30px; + height: 55px; + border-radius: 0 35px 35px 0; + z-index: 15; +} + +/* 8 Circle Buttons */ +.circle-button { + position: absolute; + top: 50%; + left: 50%; + width: 70px; + height: 70px; + margin: -35px 0 0 -35px; + border-radius: 50%; + border: 2px solid #ddd; + background: white; + color: #0f0f0f; + font-size: 24px; + font-weight: bold; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + box-shadow: 0 2px 2px rgba(0,0,0,0.2); + z-index: 1; +} + +.second-ring { + width: 50px; + height: 50px; + font-size: 20px; + font-weight: normal; + margin: -25px 0 0 -25px; +} + +.third-ring { + width: 40px; + height: 40px; + font-size: 18px; + font-weight: normal; + margin: -20px 0 0 -20px; +} + +.fourth-ring { + width: 30px; + height: 30px; + font-size: 16px; + font-weight: lighter; + margin: -20px 0 0 -20px; +} + +.circle-button:hover, .center-section:hover { + background: #b1b1b1; + border-color: #3d3d3d; +} + +.circle-button:active, .center-section:active { + background: #d0d0d0; +} + +@media (prefers-color-scheme: dark) { + .center-section { + background: rgb(58, 56, 56); + color: white; + } + + .circle-button { + background: rgb(58, 56, 56); + color: white; + } +} diff --git a/src/app/keyboards/circle-keyboard.component.ts b/src/app/keyboards/circle-keyboard.component.ts new file mode 100644 index 0000000..391281c --- /dev/null +++ b/src/app/keyboards/circle-keyboard.component.ts @@ -0,0 +1,104 @@ +import { Component, Output, EventEmitter, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +//TODO: Second and fourth ring of keys + +@Component({ + selector: 'app-circle-keyboard', + standalone: true, + imports: [CommonModule], + template: ` + + + + + + ⌫ + + + ↵ + + + + ␣ + + + ⇧ + + + . + + + , + + + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + {{ shiftActive ? circleKeysFourth[0] : circleKeysFourth[0].toLowerCase() }} + + + + {{ shiftActive ? circleKeysFourth[1] : circleKeysFourth[1].toLowerCase() }} + + + `, + styleUrl: './circle-keyboard.component.css' +}) +export class CircleKeyboardComponent { + @Input() shiftActive = false; + @Output() keyPressed = new EventEmitter(); + @Output() shiftToggled = new EventEmitter(); + + // 8 keys arranged in circle + circleKeysFirst = ['E', 'T', 'A', 'O', 'N', 'I', 'H', 'S']; + circleKeysSecond = ['R', 'L', 'D', 'U', 'C', 'M', 'W', 'Y']; + circleKeysThird = ['F', 'G', 'P', 'B', 'V', 'K', 'J', 'X']; + circleKeysFourth = ['Q', 'Z']; + + // Calculate position for each button in circle + getCirclePositionFromTop(index: number, radius: number, offset: number = 0): string { + const angle = ((index * 45) - 90) + offset; // Start from top (0°), 45° apart + const angleRad = (angle * Math.PI) / 180; + const x = Math.cos(angleRad) * radius; + const y = Math.sin(angleRad) * radius; + return `translate(${x}px, ${y}px)`; + } +} diff --git a/src/app/keyboards/dvorak-keyboard.component.ts b/src/app/keyboards/dvorak-keyboard.component.ts new file mode 100644 index 0000000..0c18a98 --- /dev/null +++ b/src/app/keyboards/dvorak-keyboard.component.ts @@ -0,0 +1,68 @@ +import { Component, Output, EventEmitter, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-dvorak-keyboard', + standalone: true, + imports: [CommonModule], + template: ` + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + ⇧ Shift + + + + Leertaste + + + + ⌫ Delete + + + + ↵ Enter + + + + `, + styleUrl: '../app.component.css' +}) +export class DvorakKeyboardComponent { + @Input() shiftActive = false; + @Output() keyPressed = new EventEmitter(); + @Output() shiftToggled = new EventEmitter(); + + row1 = [',', '.', 'P', 'Y', 'F', 'G', 'C', 'R', 'L']; + row2 = ['A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S']; + row3 = ['Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z']; +} diff --git a/src/app/keyboards/qwerty-keyboard.component.ts b/src/app/keyboards/qwerty-keyboard.component.ts new file mode 100644 index 0000000..a6e616d --- /dev/null +++ b/src/app/keyboards/qwerty-keyboard.component.ts @@ -0,0 +1,68 @@ +import { Component, Output, EventEmitter, Input } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-qwerty-keyboard', + standalone: true, + imports: [CommonModule], + template: ` + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + {{ shiftActive ? key : key.toLowerCase() }} + + + + + + ⇧ Shift + + + + Leertaste + + + + ⌫ Delete + + + + ↵ Enter + + + + `, + styleUrl: '../app.component.css' +}) +export class QwertyKeyboardComponent { + @Input() shiftActive = false; + @Output() keyPressed = new EventEmitter(); + @Output() shiftToggled = new EventEmitter(); + + row1 = ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']; + row2 = ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L']; + row3 = ['Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.']; +}
Click on the logos to learn more about the frameworks
{{ greetingMessage }}