Add touchscreen support with onscreen keyboard #1

Merged
stjet merged 21 commits from dev into master 2025-02-10 05:03:49 +00:00
6 changed files with 217 additions and 26 deletions
Showing only changes of commit cb8d5abdbf - Show all commits

View File

@@ -7,6 +7,7 @@ use crate::window_manager::DrawInstructions;
pub mod toggle_button; pub mod toggle_button;
pub mod highlight_button; pub mod highlight_button;
pub mod paragraph; pub mod paragraph;
pub mod press_button;
pub trait Component<T> { pub trait Component<T> {
fn handle_message(&mut self, message: WindowMessage) -> Option<T>; fn handle_message(&mut self, message: WindowMessage) -> Option<T>;
@@ -17,6 +18,7 @@ pub trait Component<T> {
//focusing for components is purely to give a visual representation //focusing for components is purely to give a visual representation
fn focusable(&self) -> bool; fn focusable(&self) -> bool;
fn clickable(&self) -> bool; fn clickable(&self) -> bool;
//fn pressable(&self) -> bool; //touch
fn name(&self) -> &String; //should be unique fn name(&self) -> &String; //should be unique
} }

View File

@@ -0,0 +1,67 @@
use std::vec;
use std::vec::Vec;
use crate::components::Component;
use crate::framebuffer::{ Dimensions, Point };
use crate::themes::ThemeInfo;
use crate::messages::WindowMessage;
use crate::window_manager::{ DrawInstructions };
const MONO_WIDTH: u8 = 10;
pub struct PressButton<T> {
top_left: Point,
size: Dimensions,
text: String,
press_return: T,
}
impl<T: Clone> Component<T> for PressButton<T> {
//
fn handle_message(&mut self, message: WindowMessage) -> Option<T> {
match message {
WindowMessage::Touch(x, y) => {
Some(self.press_return.clone())
},
_ => None,
}
}
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let half = self.size[0] / 2 - self.text.len() * MONO_WIDTH as usize / 2;
vec![
DrawInstructions::Rect(self.top_left, [self.size[0], 1], theme_info.border_left_top),
DrawInstructions::Rect(self.top_left, [1, self.size[1]], theme_info.border_left_top),
DrawInstructions::Rect([self.top_left[0], self.top_left[1] + self.size[1]], [self.size[0], 1], theme_info.border_right_bottom),
DrawInstructions::Rect([self.top_left[0] + self.size[0], self.top_left[1]], [1, self.size[1]], theme_info.border_right_bottom),
//assume normal background colour
DrawInstructions::Text([self.top_left[0] + half, self.top_left[1] + 8], vec!["times-new-romono".to_string()], self.text.clone(), theme_info.text, theme_info.background, Some(0), Some(MONO_WIDTH)),
]
}
//properties
fn focusable(&self) -> bool {
false
}
fn clickable(&self) -> bool {
false
}
fn name(&self) -> &String {
//sorry
&self.text
}
}
impl<T> PressButton<T> {
pub fn new(top_left: Point, size: Dimensions, text: String, press_return: T) -> Self {
Self {
top_left,
size,
text,
press_return,
}
}
}

View File

@@ -1,15 +1,43 @@
use std::vec; use std::vec;
use std::vec::Vec; use std::vec::Vec;
use std::collections::HashMap;
use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType }; use crate::window_manager::{ DrawInstructions, WindowLike, WindowLikeType, KeyChar };
use crate::messages::{ WindowMessage, WindowMessageResponse }; use crate::messages::{ WindowMessage, WindowMessageResponse };
use crate::framebuffer::Dimensions; use crate::framebuffer::Dimensions;
use crate::themes::ThemeInfo; use crate::themes::ThemeInfo;
use crate::components::Component;
use crate::components::press_button::PressButton;
const padding_y: usize = 15;
const padding_x: usize = 15;
//padding in between keys in the x direction
const key_padding_x: usize = 5;
const key_padding_y: usize = 5;
#[derive(Default, Eq, PartialEq, Hash)]
enum Board {
#[default]
Regular,
Shift,
Symbols,
SymbolsShift,
}
#[derive(Clone)]
enum KeyResponse {
Key(char),
Alt,
Ctrl,
SwitchBoard,
}
#[derive(Default)] #[derive(Default)]
pub struct OnscreenKeyboard { pub struct OnscreenKeyboard {
dimensions: Dimensions, dimensions: Dimensions,
// components: Vec<Box<PressButton<KeyResponse>>>,
alt: bool,
board: Board,
} }
impl WindowLike for OnscreenKeyboard { impl WindowLike for OnscreenKeyboard {
@@ -17,6 +45,7 @@ impl WindowLike for OnscreenKeyboard {
match message { match message {
WindowMessage::Init(dimensions) => { WindowMessage::Init(dimensions) => {
self.dimensions = dimensions; self.dimensions = dimensions;
self.set_key_components();
WindowMessageResponse::JustRedraw WindowMessageResponse::JustRedraw
}, },
// //
@@ -26,7 +55,9 @@ impl WindowLike for OnscreenKeyboard {
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> { fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
let mut instructions = vec![DrawInstructions::Rect([0, 0], self.dimensions, theme_info.background)]; let mut instructions = vec![DrawInstructions::Rect([0, 0], self.dimensions, theme_info.background)];
// for component in &self.components {
instructions.extend(component.draw(theme_info));
}
instructions instructions
} }
// //
@@ -43,5 +74,58 @@ impl OnscreenKeyboard {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
fn set_key_components(&mut self) {
self.components = Vec::new();
let rows: [HashMap<Board, Vec<char>>; 4] = [
HashMap::from([
(Board::Regular, vec!['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p']),
(Board::Shift, vec!['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P']),
(Board::Symbols, vec!['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']),
(Board::SymbolsShift, vec![]), //empty
]),
HashMap::from([
(Board::Regular, vec!['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l']),
(Board::Shift, vec!['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L']),
(Board::Symbols, vec!['|', '=', '$', '%', '&', '-', '+', '(', ')']),
(Board::SymbolsShift, vec!['`', '@', '#', '^']),
]),
HashMap::from([
(Board::Regular, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
(Board::Shift, vec!['𐘃', 'z', 'x', 'c', 'v', 'b', 'n', 'm', '𐘁']), //escape and backspace
(Board::Symbols, vec!['~', '*', '"', '\'', ':', ';', '!', '?', '𐘁']), //basckspace
(Board::SymbolsShift, vec!['[', ']', '{', '}', '𐘁']), //backspace
]),
HashMap::from([
(Board::Regular, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
(Board::Shift, vec!['𐘧', '𐘎', ' ', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
(Board::Symbols, vec!['𐘧', '𐘎', ',', '_', ' ', '/', '.', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), enter
(Board::SymbolsShift, vec!['𐘧', '𐘎', '\\', '<', ' ', '>', '𐘾', '𐘂']), //switch board (special case, not a real key), alt (special case, not a real key), ctrl (special case, not a real key), enter
]),
];
//hardcoded for now
let mut y = padding_y;
let key_height = (self.dimensions[1] - padding_y * 2 - key_padding_y * (rows.len() - 1)) / rows.len();
let reg_key_width = (self.dimensions[0] - padding_x * 2 - key_padding_x * (10 - 1)) / 10;
for row in rows {
let row_keys = &row[&self.board];
//centre
let mut x = padding_x + (10 - row_keys.len()) * (reg_key_width + key_padding_x) / 2;
for key in row_keys {
let press_return = if key == &'𐘧' {
KeyResponse::SwitchBoard
} else if key == &'𐘎' {
KeyResponse::Alt
} else if key == &'𐘾' {
KeyResponse::Ctrl
} else {
KeyResponse::Key(*key)
};
self.components.push(Box::new(PressButton::new([x, y], [reg_key_width, key_height], key.to_string(), press_return)));
x += reg_key_width + key_padding_x;
}
y += key_height + key_padding_y;
}
}
} }

View File

@@ -9,7 +9,7 @@ use crate::window_manager::{ WindowLike, KeyChar };
pub enum WindowManagerMessage { pub enum WindowManagerMessage {
KeyChar(KeyChar), KeyChar(KeyChar),
Touch(u16, u16), Touch(usize, usize),
// //
} }
@@ -99,5 +99,6 @@ pub enum WindowMessage {
Unfocus, Unfocus,
FocusClick, FocusClick,
ChangeDimensions(Dimensions), ChangeDimensions(Dimensions),
Touch(usize, usize), //for onscreen keyboard only
// //
} }

View File

@@ -3,8 +3,10 @@ use std::path::PathBuf;
use termion::event::Key; use termion::event::Key;
use crate::window_manager::KeyChar; use crate::window_manager::KeyChar;
use crate::framebuffer::{ Dimensions, Point };
//use Linear A for escape, backspace, enter //use Linear A for escape, backspace, enter
//Linear A used only internally in onscreen keyboard: 𐘎 is alt, 𐘧 is switch board, 𐘾 is ctrl
pub fn key_to_char(key: Key) -> Option<KeyChar> { pub fn key_to_char(key: Key) -> Option<KeyChar> {
match key { match key {
Key::Char('\n') => Some(KeyChar::Press('𐘂')), Key::Char('\n') => Some(KeyChar::Press('𐘂')),
@@ -41,7 +43,6 @@ impl Substring for String {
break; break;
} }
byte_end += char_length; byte_end += char_length;
} }
&self[byte_start..byte_end] &self[byte_start..byte_end]
} }
@@ -146,3 +147,13 @@ pub fn is_hex(c: char) -> bool {
HEX_CHARS.iter().position(|hc| hc == &c).is_some() HEX_CHARS.iter().position(|hc| hc == &c).is_some()
} }
pub fn point_inside(point: Point, top_left: Point, size: Dimensions) -> bool {
let x = point[0];
let y = point[1];
let x2 = top_left[0];
let y2 = top_left[1];
let x3 = x2 + size[0];
let y3 = y2 + size[1];
x >= x2 && y >= x2 && x <= x3 && y <= y3
}

View File

@@ -20,7 +20,7 @@ use serde::{ Deserialize, Serialize };
use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor }; use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor };
use crate::themes::{ ThemeInfo, Themes, get_theme_info }; use crate::themes::{ ThemeInfo, Themes, get_theme_info };
use crate::utils::{ min, key_to_char }; use crate::utils::{ min, key_to_char, point_inside };
use crate::messages::*; use crate::messages::*;
use crate::proxy_window_like::ProxyWindowLike; use crate::proxy_window_like::ProxyWindowLike;
use crate::essential::desktop_background::DesktopBackground; use crate::essential::desktop_background::DesktopBackground;
@@ -33,6 +33,8 @@ use crate::essential::help::Help;
use crate::essential::onscreen_keyboard::OnscreenKeyboard; use crate::essential::onscreen_keyboard::OnscreenKeyboard;
//use crate::logging::log; //use crate::logging::log;
//todo: a lot of the usize should be changed to u16
pub const TASKBAR_HEIGHT: usize = 38; pub const TASKBAR_HEIGHT: usize = 38;
pub const INDICATOR_HEIGHT: usize = 20; pub const INDICATOR_HEIGHT: usize = 20;
const WINDOW_TOP_HEIGHT: usize = 26; const WINDOW_TOP_HEIGHT: usize = 26;
@@ -46,7 +48,7 @@ pub enum KeyChar {
enum ThreadMessage { enum ThreadMessage {
KeyChar(KeyChar), KeyChar(KeyChar),
Touch(u16, u16), Touch(usize, usize),
Exit, Exit,
} }
@@ -90,20 +92,21 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
}); });
let args: Vec<_> = env::args().collect(); let args: Vec<_> = env::args().collect();
let touch = args.contains(&"touch".to_string());
//read touchscreen presses (hopefully) //read touchscreen presses (hopefully)
thread::spawn(move || { thread::spawn(move || {
if args.contains(&"touch".to_string()) { //spawn evtest, parse it for touch coords
//spawn evtest, parse it for touch coords if touch {
let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap(); let mut evtest = Command::new("evtest").arg("/dev/input/by-path/first-touchscreen").stdout(Stdio::piped()).spawn().unwrap();
let reader = BufReader::new(evtest.stdout.as_mut().unwrap()); let reader = BufReader::new(evtest.stdout.as_mut().unwrap());
let mut x: Option<u16> = None; let mut x: Option<usize> = None;
let mut y: Option<u16> = None; let mut y: Option<usize> = None;
for line in reader.lines() { for line in reader.lines() {
let line = line.unwrap(); let line = line.unwrap();
if line.contains(&"ABS_X") || line.contains(&"ABS_Y") { if line.contains(&"ABS_X), value ") || line.contains(&"ABS_Y), value ") {
let value: Vec<_> = line.split("), value ").collect(); let value: Vec<_> = line.split("), value ").collect();
let value = value[value.len() - 1].parse::<u16>().unwrap(); let value = value[value.len() - 1].parse::<usize>().unwrap();
if line.contains(&"ABS_X") { if line.contains(&"ABS_X") {
x = Some(value); x = Some(value);
} else { } else {
@@ -119,6 +122,10 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
} }
} }
}); });
if touch {
//opens osk
wm.handle_message(WindowManagerMessage::Touch(1, 1));
}
for message in rx { for message in rx {
match message { match message {
@@ -199,6 +206,7 @@ pub struct WindowManager {
writer: RefCell<FramebufferWriter>, writer: RefCell<FramebufferWriter>,
id_count: usize, id_count: usize,
window_infos: Vec<WindowLikeInfo>, window_infos: Vec<WindowLikeInfo>,
osk: Option<WindowLikeInfo>,
dimensions: Dimensions, dimensions: Dimensions,
theme: Themes, theme: Themes,
focused_id: usize, focused_id: usize,
@@ -216,6 +224,7 @@ impl WindowManager {
writer: RefCell::new(writer), writer: RefCell::new(writer),
id_count: 0, id_count: 0,
window_infos: Vec::new(), window_infos: Vec::new(),
osk: None,
dimensions, dimensions,
theme: Themes::Standard, theme: Themes::Standard,
focused_id: 0, focused_id: 0,
@@ -233,10 +242,9 @@ impl WindowManager {
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions)); let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
self.id_count = self.id_count + 1; self.id_count = self.id_count + 1;
let id = self.id_count; let id = self.id_count;
self.focused_id = id;
window_like.handle_message(WindowMessage::Init(dimensions)); window_like.handle_message(WindowMessage::Init(dimensions));
let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions }; let dimensions = if window_like.subtype() == WindowLikeType::Window { [dimensions[0], dimensions[1] + WINDOW_TOP_HEIGHT] } else { dimensions };
self.window_infos.push(WindowLikeInfo { let window_info = WindowLikeInfo {
id, id,
window_like, window_like,
top_left, top_left,
@@ -247,7 +255,13 @@ impl WindowManager {
Workspace::All Workspace::All
}, },
fullscreen: false, fullscreen: false,
}); };
if subtype == WindowLikeType::OnscreenKeyboard {
self.osk = Some(window_info);
} else {
self.focused_id = id;
self.window_infos.push(window_info);
}
} }
fn get_focused_index(&self) -> Option<usize> { fn get_focused_index(&self) -> Option<usize> {
@@ -585,21 +599,30 @@ impl WindowManager {
}, },
WindowManagerMessage::Touch(x, y) => { WindowManagerMessage::Touch(x, y) => {
println!("{}, {}", x, y); println!("{}, {}", x, y);
if x < 10000 && y < 10000 { if x < 100 && y < 100 {
//toggle onscreen keyboard if top left keyboard clicked //toggle onscreen keyboard if top left keyboard clicked
let osk_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::OnscreenKeyboard); if self.osk.is_some() {
if let Some(osk_index) = osk_index { self.osk = None;
self.window_infos.remove(osk_index);
} else { } else {
let osk = Box::new(OnscreenKeyboard::new()); let osk = Box::new(OnscreenKeyboard::new());
let ideal_dimensions = osk.ideal_dimensions(self.dimensions); let ideal_dimensions = osk.ideal_dimensions(self.dimensions);
self.add_window_like(osk, [175, self.dimensions[1] - TASKBAR_HEIGHT - 250], Some(ideal_dimensions)); self.add_window_like(osk, [175, self.dimensions[1] - TASKBAR_HEIGHT - 250], Some(ideal_dimensions));
} }
WindowMessageResponse::JustRedraw
} else {
//see if in onscreen keyboard, if so send to it after offsetting coords
if self.osk.is_some() {
let mut osk = self.osk.as_mut().unwrap();
if point_inside([x, y], osk.top_left, osk.dimensions) {
osk.window_like.handle_message(WindowMessage::Touch(x - osk.top_left[0], y - osk.top_left[1]))
} else {
WindowMessageResponse::DoNothing
}
} else {
WindowMessageResponse::DoNothing
}
} }
//see if in onscreen keyboard, if so send to it after offsetting coords }
//
WindowMessageResponse::JustRedraw
},
}; };
if response != WindowMessageResponse::DoNothing { if response != WindowMessageResponse::DoNothing {
match response { match response {
@@ -686,12 +709,15 @@ impl WindowManager {
} }
//get windows to redraw //get windows to redraw
let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new()); let redraw_ids = maybe_redraw_ids.unwrap_or(Vec::new());
let all_in_workspace = self.get_windows_in_workspace(true); let mut all_in_workspace = self.get_windows_in_workspace(true);
if let Some(osk) = &self.osk {
all_in_workspace.push(osk);
}
let maybe_length = all_in_workspace.len(); let maybe_length = all_in_workspace.len();
let redraw_windows = all_in_workspace.iter().filter(|w| { let redraw_windows = all_in_workspace.iter().filter(|w| {
//basically, maybe_redraw_ids was None //basically, maybe_redraw_ids was None
if redraw_ids.len() > 0 { if redraw_ids.len() > 0 {
redraw_ids.contains(&w.id) redraw_ids.contains(&w.id) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard
} else { } else {
true true
} }