From ef740ca9ddffdd4c7677013218bc0ac1e7bb74a9 Mon Sep 17 00:00:00 2001 From: lukasadrion Date: Thu, 4 Dec 2025 18:54:10 +0100 Subject: [PATCH] add first implementation of circle keyboard and better switching of keyboards --- src-tauri/src/lib.rs | 31 ++- src/app/app.component.html | 80 +++----- src/app/app.component.ts | 46 ++--- .../keyboards/circle-keyboard.component.ts | 190 ++++++++++++++++++ .../keyboards/dvorak-keyboard.component.ts | 68 +++++++ .../keyboards/qwerty-keyboard.component.ts | 68 +++++++ 6 files changed, 393 insertions(+), 90 deletions(-) create mode 100644 src/app/keyboards/circle-keyboard.component.ts create mode 100644 src/app/keyboards/dvorak-keyboard.component.ts create mode 100644 src/app/keyboards/qwerty-keyboard.component.ts diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a60f0d7..bb4c272 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -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; @@ -6,8 +6,8 @@ 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) } @@ -16,17 +16,30 @@ 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 { - // Single letter (a-z, A-Z) - enigo.key(Key::Unicode(key.chars().next().unwrap()), Click) - .map_err(|e| format!("Key error: {}", e))?; + 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, Click) + "Enter" => enigo.key(Key::Return, Direction::Click) .map_err(|e| format!("Key error: {}", e))?, - "Space" => enigo.key(Key::Space, Click) + "Space" => enigo.key(Key::Space, Direction::Click) .map_err(|e| format!("Key error: {}", e))?, - "Backspace" => enigo.key(Key::Backspace, Click) + "Backspace" => enigo.key(Key::Backspace, Direction::Click) .map_err(|e| format!("Key error: {}", e))?, _ => return Err("Unknown key".to_string()), } diff --git a/src/app/app.component.html b/src/app/app.component.html index f641c00..3585f69 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -3,65 +3,35 @@ +

{{ greetingMessage }}

-
- -
-
- -
- -
+ + + - -
- -
+ + - -
- - - - - - - -
- - -
- -
-
+ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9c357c2..0597add 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,62 +2,56 @@ 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 implements AfterViewInit { greetingMessage = ""; + currentLayout: 'qwerty' | 'dvorak' | 'circle' = 'qwerty'; + shiftActive = false; @ViewChild('greetInput') inputElement!: ElementRef; ngAfterViewInit() { - // Beim Start fokussieren 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; }); } - // Keyboard Layout State - currentLayout: 'qwerty' | 'dvorak' = 'qwerty'; - - // QWERTY Layout - qwertyRow1 = ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']; - qwertyRow2 = ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L']; - qwertyRow3 = ['Z', 'X', 'C', 'V', 'B', 'N', 'M', ",", "."]; - - // DVORAK Layout - dvorakRow1 = [',', '.', 'P', 'Y', 'F', 'G', 'C', 'R', 'L']; - dvorakRow2 = ['A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S']; - dvorakRow3 = ['Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z']; - - shiftActive = false; - - // Getters for current layout - get row1() { return this.currentLayout === 'qwerty' ? this.qwertyRow1 : this.dvorakRow1; } - get row2() { return this.currentLayout === 'qwerty' ? this.qwertyRow2 : this.dvorakRow2; } - get row3() { return this.currentLayout === 'qwerty' ? this.qwertyRow3 : this.dvorakRow3; } - toggleShift(): void { this.shiftActive = !this.shiftActive; } switchLayout(): void { - this.currentLayout = this.currentLayout === 'qwerty' ? 'dvorak' : 'qwerty'; + if (this.currentLayout == 'qwerty'){ + this.currentLayout = 'dvorak'; + } else if (this.currentLayout == 'dvorak'){ + this.currentLayout = 'circle'; + } else if (this.currentLayout == 'circle'){ + this.currentLayout = 'qwerty'; + } } - async sendKey(key: string): Promise { + async handleKeyPress(key: string): Promise { this.inputElement.nativeElement.focus(); let finalKey = key; diff --git a/src/app/keyboards/circle-keyboard.component.ts b/src/app/keyboards/circle-keyboard.component.ts new file mode 100644 index 0000000..7494420 --- /dev/null +++ b/src/app/keyboards/circle-keyboard.component.ts @@ -0,0 +1,190 @@ +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: ` +
+ +
+ + + + + + +
+ + + + + +
+ `, + styles: [` + .circle-keyboard-container { + position: relative; + width: 400px; + height: 400px; + margin: 50px auto; + } + + /* 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; + 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; + } + + /* 8 Circle Buttons */ + .circle-button { + position: absolute; + top: 50%; + left: 50%; + width: 60px; + height: 60px; + margin: -30px 0 0 -30px; + border-radius: 50%; + border: 2px solid #ddd; + background: white; + font-size: 20px; + font-weight: bold; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + z-index: 1; + } + + .third-ring { + width: 40px; + height: 40px; + margin: -20px 0 0 -20px; + } + + .fourth-ring { + width: 30px; + height: 30px; + margin: -20px 0 0 -20px; + } + + .circle-button:hover, .center-section:hover { + background: #e8e8e8; + border-color: #999; + } + + .circle-button:active, .center-section:active { + background: #d0d0d0; + } + `] +}) +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): string { + const angle = (index * 45) - 90; // 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: ` +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + + +
+
+ `, + 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: ` +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + + +
+
+ `, + 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', ',', '.']; +}