Add touchscreen support with onscreen keyboard #1
@@ -7,6 +7,7 @@ use crate::window_manager::DrawInstructions;
|
||||
pub mod toggle_button;
|
||||
pub mod highlight_button;
|
||||
pub mod paragraph;
|
||||
pub mod press_button;
|
||||
|
||||
pub trait Component<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
|
||||
fn focusable(&self) -> bool;
|
||||
fn clickable(&self) -> bool;
|
||||
//fn pressable(&self) -> bool; //touch
|
||||
fn name(&self) -> &String; //should be unique
|
||||
}
|
||||
|
||||
|
||||
67
src/components/press_button.rs
Normal file
67
src/components/press_button.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,43 @@
|
||||
use std::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::framebuffer::Dimensions;
|
||||
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)]
|
||||
pub struct OnscreenKeyboard {
|
||||
dimensions: Dimensions,
|
||||
//
|
||||
components: Vec<Box<PressButton<KeyResponse>>>,
|
||||
alt: bool,
|
||||
board: Board,
|
||||
}
|
||||
|
||||
impl WindowLike for OnscreenKeyboard {
|
||||
@@ -17,6 +45,7 @@ impl WindowLike for OnscreenKeyboard {
|
||||
match message {
|
||||
WindowMessage::Init(dimensions) => {
|
||||
self.dimensions = dimensions;
|
||||
self.set_key_components();
|
||||
WindowMessageResponse::JustRedraw
|
||||
},
|
||||
//
|
||||
@@ -26,7 +55,9 @@ impl WindowLike for OnscreenKeyboard {
|
||||
|
||||
fn draw(&self, theme_info: &ThemeInfo) -> Vec<DrawInstructions> {
|
||||
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
|
||||
}
|
||||
//
|
||||
@@ -43,5 +74,58 @@ impl OnscreenKeyboard {
|
||||
pub fn new() -> Self {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::window_manager::{ WindowLike, KeyChar };
|
||||
|
||||
pub enum WindowManagerMessage {
|
||||
KeyChar(KeyChar),
|
||||
Touch(u16, u16),
|
||||
Touch(usize, usize),
|
||||
//
|
||||
}
|
||||
|
||||
@@ -99,5 +99,6 @@ pub enum WindowMessage {
|
||||
Unfocus,
|
||||
FocusClick,
|
||||
ChangeDimensions(Dimensions),
|
||||
Touch(usize, usize), //for onscreen keyboard only
|
||||
//
|
||||
}
|
||||
|
||||
13
src/utils.rs
13
src/utils.rs
@@ -3,8 +3,10 @@ use std::path::PathBuf;
|
||||
use termion::event::Key;
|
||||
|
||||
use crate::window_manager::KeyChar;
|
||||
use crate::framebuffer::{ Dimensions, Point };
|
||||
|
||||
//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> {
|
||||
match key {
|
||||
Key::Char('\n') => Some(KeyChar::Press('𐘂')),
|
||||
@@ -41,7 +43,6 @@ impl Substring for String {
|
||||
break;
|
||||
}
|
||||
byte_end += char_length;
|
||||
|
||||
}
|
||||
&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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use serde::{ Deserialize, Serialize };
|
||||
|
||||
use crate::framebuffer::{ FramebufferWriter, FramebufferInfo, Point, Dimensions, RGBColor };
|
||||
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::proxy_window_like::ProxyWindowLike;
|
||||
use crate::essential::desktop_background::DesktopBackground;
|
||||
@@ -33,6 +33,8 @@ use crate::essential::help::Help;
|
||||
use crate::essential::onscreen_keyboard::OnscreenKeyboard;
|
||||
//use crate::logging::log;
|
||||
|
||||
//todo: a lot of the usize should be changed to u16
|
||||
|
||||
pub const TASKBAR_HEIGHT: usize = 38;
|
||||
pub const INDICATOR_HEIGHT: usize = 20;
|
||||
const WINDOW_TOP_HEIGHT: usize = 26;
|
||||
@@ -46,7 +48,7 @@ pub enum KeyChar {
|
||||
|
||||
enum ThreadMessage {
|
||||
KeyChar(KeyChar),
|
||||
Touch(u16, u16),
|
||||
Touch(usize, usize),
|
||||
Exit,
|
||||
}
|
||||
|
||||
@@ -90,20 +92,21 @@ pub fn init(framebuffer: Framebuffer, framebuffer_info: FramebufferInfo) {
|
||||
});
|
||||
|
||||
let args: Vec<_> = env::args().collect();
|
||||
let touch = args.contains(&"touch".to_string());
|
||||
|
||||
//read touchscreen presses (hopefully)
|
||||
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 reader = BufReader::new(evtest.stdout.as_mut().unwrap());
|
||||
let mut x: Option<u16> = None;
|
||||
let mut y: Option<u16> = None;
|
||||
let mut x: Option<usize> = None;
|
||||
let mut y: Option<usize> = None;
|
||||
for line in reader.lines() {
|
||||
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 = value[value.len() - 1].parse::<u16>().unwrap();
|
||||
let value = value[value.len() - 1].parse::<usize>().unwrap();
|
||||
if line.contains(&"ABS_X") {
|
||||
x = Some(value);
|
||||
} 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 {
|
||||
match message {
|
||||
@@ -199,6 +206,7 @@ pub struct WindowManager {
|
||||
writer: RefCell<FramebufferWriter>,
|
||||
id_count: usize,
|
||||
window_infos: Vec<WindowLikeInfo>,
|
||||
osk: Option<WindowLikeInfo>,
|
||||
dimensions: Dimensions,
|
||||
theme: Themes,
|
||||
focused_id: usize,
|
||||
@@ -216,6 +224,7 @@ impl WindowManager {
|
||||
writer: RefCell::new(writer),
|
||||
id_count: 0,
|
||||
window_infos: Vec::new(),
|
||||
osk: None,
|
||||
dimensions,
|
||||
theme: Themes::Standard,
|
||||
focused_id: 0,
|
||||
@@ -233,10 +242,9 @@ impl WindowManager {
|
||||
let dimensions = dimensions.unwrap_or(window_like.ideal_dimensions(self.dimensions));
|
||||
self.id_count = self.id_count + 1;
|
||||
let id = self.id_count;
|
||||
self.focused_id = id;
|
||||
window_like.handle_message(WindowMessage::Init(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,
|
||||
window_like,
|
||||
top_left,
|
||||
@@ -247,7 +255,13 @@ impl WindowManager {
|
||||
Workspace::All
|
||||
},
|
||||
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> {
|
||||
@@ -585,21 +599,30 @@ impl WindowManager {
|
||||
},
|
||||
WindowManagerMessage::Touch(x, y) => {
|
||||
println!("{}, {}", x, y);
|
||||
if x < 10000 && y < 10000 {
|
||||
if x < 100 && y < 100 {
|
||||
//toggle onscreen keyboard if top left keyboard clicked
|
||||
let osk_index = self.window_infos.iter().position(|w| w.window_like.subtype() == WindowLikeType::OnscreenKeyboard);
|
||||
if let Some(osk_index) = osk_index {
|
||||
self.window_infos.remove(osk_index);
|
||||
if self.osk.is_some() {
|
||||
self.osk = None;
|
||||
} else {
|
||||
let osk = Box::new(OnscreenKeyboard::new());
|
||||
let ideal_dimensions = osk.ideal_dimensions(self.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 {
|
||||
match response {
|
||||
@@ -686,12 +709,15 @@ impl WindowManager {
|
||||
}
|
||||
//get windows to redraw
|
||||
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 redraw_windows = all_in_workspace.iter().filter(|w| {
|
||||
//basically, maybe_redraw_ids was None
|
||||
if redraw_ids.len() > 0 {
|
||||
redraw_ids.contains(&w.id)
|
||||
redraw_ids.contains(&w.id) || w.window_like.subtype() == WindowLikeType::OnscreenKeyboard
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user