Compare commits
34 Commits
v1.0
...
7ea92145ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ea92145ea | ||
|
|
7777a3b71c | ||
|
|
84f4ee55c0 | ||
|
|
9e5c82d82c | ||
|
|
64a0934471 | ||
|
|
4ddaf78137 | ||
|
|
d38c62c381 | ||
|
|
f88e9ed3ae | ||
|
|
df6908995b | ||
|
|
1fe3b37504 | ||
|
|
1ef991c90f | ||
|
|
dc0acdd012 | ||
|
|
f1c16f1222 | ||
|
|
afce143022 | ||
|
|
3c1cb5f9c7 | ||
|
|
03c40a3a96 | ||
|
|
97b40d8c18 | ||
|
|
81d496e6ce | ||
|
|
c2673f71bb | ||
|
|
c217589fc4 | ||
|
|
8da2b97ab6 | ||
|
|
e2c25e5f59 | ||
|
|
da4fc7c99c | ||
|
|
05631293be | ||
|
|
31a2da04fb | ||
|
|
cdacf0bc4a | ||
|
|
d1d9a782f8 | ||
|
|
8aca73cd9c | ||
|
|
e8bfcacacb | ||
|
|
86b67f36e4 | ||
|
|
93041a370e | ||
|
|
2b6ea24d5e | ||
|
|
02e6b033c2 | ||
|
|
076816bd12 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,3 +42,6 @@ testem.log
|
||||
Thumbs.db
|
||||
|
||||
TextTestExe/
|
||||
data/raw
|
||||
.Rproj.user
|
||||
figures/
|
||||
|
||||
@@ -15,6 +15,6 @@ Keyboard emulation on most systems requires workarounds for two issues:
|
||||
- not receiving focus when clicked
|
||||
- sending input
|
||||
|
||||
## Logging Setup
|
||||
either use [TextTestExe](https://depts.washington.edu/acelab/proj/texttest/) (local program) or [TextTestPP](https://drustz.com/TextTestPP/) (online).
|
||||
|
||||
## Logging and Statistics
|
||||
For logging, either use [TextTestExe](https://depts.washington.edu/acelab/proj/texttest/) (local program) or [TextTestPP](https://drustz.com/TextTestPP/) (online).
|
||||
The theoretical framework is inspired by https://www.yorku.ca/mack/bit95.html.
|
||||
|
||||
37
data/nasaTLX.csv
Normal file
37
data/nasaTLX.csv
Normal file
@@ -0,0 +1,37 @@
|
||||
layout, mental_demand, physical_demand, performance, effort, frustration
|
||||
dvorak, 19, 18, 13, 18, 18
|
||||
qwerty, 0, 0, 7, 2, 0
|
||||
circle, 15, 12, 12, 15, 10
|
||||
dvorak, 17, 17, 3, 17, 17
|
||||
qwerty, 15, 15, 3, 16, 15
|
||||
circle, 17, 14, 3, 17, 17
|
||||
dvorak, 14, 17, 15, 13, 14
|
||||
qwerty, 3, 3, 12, 12, 11
|
||||
circle, 19, 19, 9, 9, 9
|
||||
dvorak, 14, 14, 12, 15, 13
|
||||
qwerty, 11, 8, 5, 16, 4
|
||||
circle, 7, 5, 5, 11, 9
|
||||
dvorak, 14, 13, 9, 16, 19
|
||||
qwerty, 4, 7, 6, 11, 3
|
||||
circle, 12, 9, 6, 6, 14
|
||||
dvorak, 15, 4, 4, 14, 16
|
||||
qwerty, 1, 2, 1, 2, 1
|
||||
circle, 19, 7, 15, 13, 18
|
||||
dvorak, 7, 2, 12, 11, 11
|
||||
qwerty, 1, 2, 17, 2, 1
|
||||
circle, 6, 5, 13, 12, 17
|
||||
dvorak, 15, 4, 8, 15, 17
|
||||
qwerty, 5, 2, 15, 6, 3
|
||||
circle, 13, 4, 7, 12, 13
|
||||
dvorak, 9, 11, 14, 14, 14
|
||||
qwerty, 0, 0, 5, 7, 2
|
||||
circle, 15, 15, 11, 15, 11
|
||||
dvorak, 16, 14, 6, 16, 15
|
||||
qwerty, 2, 3, 8, 5, 4
|
||||
circle, 14, 12, 8, 10, 12
|
||||
dvorak, 14, 13, 9, 16, 19
|
||||
qwerty, 3, 4, 5, 7, 3
|
||||
circle, 17, 14, 7, 12, 13
|
||||
dvorak, 12, 2, 10, 5, 10
|
||||
qwerty, 1, 1, 3, 1, 1
|
||||
circle, 4, 1, 7, 2, 2
|
||||
|
13
data/results.csv
Normal file
13
data/results.csv
Normal file
@@ -0,0 +1,13 @@
|
||||
id,age,sequence,qwerty_wpm, qwerty_ter, dvorak_wpm, dvorak_ter, circle_wpm, circle_ter
|
||||
en, 24, qwerty-dvorak-circle, 21.6, 0.067, 9.0, 0.023, 8.5, 0.017
|
||||
la, 25, qwerty-circle-dvorak, 18.5, 0.007, 8.8, 0.017, 9.8, 0.022
|
||||
as, 22, dvorak-qwerty-circle, 21.1, 0.049, 10.0, 0.006, 9.0, 0.033
|
||||
hb, 24, dvorak-circle-qwerty, 19.0, 0.019, 6.2, 0.018, 5.8, 0.004
|
||||
gs, 21, circle-qwerty-dvorak, 18.2, 0.034, 10.0, 0.038, 11.3, 0.00
|
||||
ab, 21, circle-dvorak-qwerty, 12.1, 0.034, 5.4, 0.029, 5.8, 0.105
|
||||
mz, 24, qwerty-dvorak-circle, 15.8, 0.063, 9.8, 0.043, 10.3, 0.065
|
||||
oa, 21, qwerty-circle-dvorak, 17.5, 0.011, 9.5, 0.011, 11.6, 0.010
|
||||
dc, 22, dvorak-qwerty-circle, 15.2, 0.033, 7.0, 0.097, 5.1, 0.009
|
||||
pt, 23, dvorak-circle-qwerty, 16.3, 0.027, 6.5, 0.012, 6.2, 0.005
|
||||
rn, 24, circle-qwerty-dvorak, 14.8, 0.052, 8.8, 0.034, 9.5, 0.022
|
||||
ss, 21, dvorak-circle-qwerty, 18.2, 0.034, 10.0, 0.038, 11.3, 0.000
|
||||
|
1
doc/.Rprofile
Normal file
1
doc/.Rprofile
Normal file
@@ -0,0 +1 @@
|
||||
source("renv/activate.R")
|
||||
375
doc/.gitignore
vendored
Normal file
375
doc/.gitignore
vendored
Normal file
@@ -0,0 +1,375 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/tex,r
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=tex,r
|
||||
|
||||
### R ###
|
||||
# History files
|
||||
.Rhistory
|
||||
.Rapp.history
|
||||
|
||||
# Session Data files
|
||||
.RData
|
||||
.RDataTmp
|
||||
|
||||
# User-specific files
|
||||
.Ruserdata
|
||||
|
||||
# Example code in package build process
|
||||
*-Ex.R
|
||||
|
||||
# Output files from R CMD build
|
||||
/*.tar.gz
|
||||
|
||||
# Output files from R CMD check
|
||||
/*.Rcheck/
|
||||
|
||||
# RStudio files
|
||||
.Rproj.user/
|
||||
|
||||
# produced vignettes
|
||||
vignettes/*.html
|
||||
vignettes/*.pdf
|
||||
|
||||
# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
|
||||
.httr-oauth
|
||||
|
||||
# knitr and R markdown default cache directories
|
||||
*_cache/
|
||||
/cache/
|
||||
|
||||
# Temporary files created by R markdown
|
||||
*.utf8.md
|
||||
*.knit.md
|
||||
|
||||
# R Environment Variables
|
||||
.Renviron
|
||||
|
||||
# pkgdown site
|
||||
docs/
|
||||
|
||||
# translation temp files
|
||||
po/*~
|
||||
|
||||
# RStudio Connect folder
|
||||
rsconnect/
|
||||
|
||||
### R.Bookdown Stack ###
|
||||
# R package: bookdown caching files
|
||||
/*_files/
|
||||
|
||||
### TeX ###
|
||||
## Core latex/pdflatex auxiliary files:
|
||||
*.aux
|
||||
*.lof
|
||||
*.log
|
||||
*.lot
|
||||
*.fls
|
||||
*.out
|
||||
*.toc
|
||||
*.fmt
|
||||
*.fot
|
||||
*.cb
|
||||
*.cb2
|
||||
.*.lb
|
||||
|
||||
## Intermediate documents:
|
||||
*.dvi
|
||||
*.xdv
|
||||
*-converted-to.*
|
||||
# these rules might exclude image files for figures etc.
|
||||
# *.ps
|
||||
# *.eps
|
||||
*.pdf
|
||||
|
||||
## Generated if empty string is given at "Please type another file name for output:"
|
||||
.pdf
|
||||
|
||||
## Bibliography auxiliary files (bibtex/biblatex/biber):
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.blg
|
||||
*-blx.aux
|
||||
*-blx.bib
|
||||
*.run.xml
|
||||
|
||||
## Build tool auxiliary files:
|
||||
*.fdb_latexmk
|
||||
*.synctex
|
||||
*.synctex(busy)
|
||||
*.synctex.gz
|
||||
*.synctex.gz(busy)
|
||||
*.pdfsync
|
||||
|
||||
## Build tool directories for auxiliary files
|
||||
# latexrun
|
||||
latex.out/
|
||||
|
||||
## Auxiliary and intermediate files from other packages:
|
||||
# algorithms
|
||||
*.alg
|
||||
*.loa
|
||||
|
||||
# achemso
|
||||
acs-*.bib
|
||||
|
||||
# amsthm
|
||||
*.thm
|
||||
|
||||
# beamer
|
||||
*.nav
|
||||
*.pre
|
||||
*.snm
|
||||
*.vrb
|
||||
|
||||
# changes
|
||||
*.soc
|
||||
|
||||
# comment
|
||||
*.cut
|
||||
|
||||
# cprotect
|
||||
*.cpt
|
||||
|
||||
# elsarticle (documentclass of Elsevier journals)
|
||||
*.spl
|
||||
|
||||
# endnotes
|
||||
*.ent
|
||||
|
||||
# fixme
|
||||
*.lox
|
||||
|
||||
# feynmf/feynmp
|
||||
*.mf
|
||||
*.mp
|
||||
*.t[1-9]
|
||||
*.t[1-9][0-9]
|
||||
*.tfm
|
||||
|
||||
#(r)(e)ledmac/(r)(e)ledpar
|
||||
*.end
|
||||
*.?end
|
||||
*.[1-9]
|
||||
*.[1-9][0-9]
|
||||
*.[1-9][0-9][0-9]
|
||||
*.[1-9]R
|
||||
*.[1-9][0-9]R
|
||||
*.[1-9][0-9][0-9]R
|
||||
*.eledsec[1-9]
|
||||
*.eledsec[1-9]R
|
||||
*.eledsec[1-9][0-9]
|
||||
*.eledsec[1-9][0-9]R
|
||||
*.eledsec[1-9][0-9][0-9]
|
||||
*.eledsec[1-9][0-9][0-9]R
|
||||
|
||||
# glossaries
|
||||
*.acn
|
||||
*.acr
|
||||
*.glg
|
||||
*.glo
|
||||
*.gls
|
||||
*.glsdefs
|
||||
*.lzo
|
||||
*.lzs
|
||||
*.slg
|
||||
*.slo
|
||||
*.sls
|
||||
|
||||
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
|
||||
# *.ist
|
||||
|
||||
# gnuplot
|
||||
*.gnuplot
|
||||
*.table
|
||||
|
||||
# gnuplottex
|
||||
*-gnuplottex-*
|
||||
|
||||
# gregoriotex
|
||||
*.gaux
|
||||
*.glog
|
||||
*.gtex
|
||||
|
||||
# htlatex
|
||||
*.4ct
|
||||
*.4tc
|
||||
*.idv
|
||||
*.lg
|
||||
*.trc
|
||||
*.xref
|
||||
|
||||
# hyperref
|
||||
*.brf
|
||||
|
||||
# knitr
|
||||
*-concordance.tex
|
||||
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
|
||||
# *.tikz
|
||||
*-tikzDictionary
|
||||
|
||||
# listings
|
||||
*.lol
|
||||
|
||||
# luatexja-ruby
|
||||
*.ltjruby
|
||||
|
||||
# makeidx
|
||||
*.idx
|
||||
*.ilg
|
||||
*.ind
|
||||
|
||||
# minitoc
|
||||
*.maf
|
||||
*.mlf
|
||||
*.mlt
|
||||
*.mtc[0-9]*
|
||||
*.slf[0-9]*
|
||||
*.slt[0-9]*
|
||||
*.stc[0-9]*
|
||||
|
||||
# minted
|
||||
_minted*
|
||||
*.pyg
|
||||
|
||||
# morewrites
|
||||
*.mw
|
||||
|
||||
# newpax
|
||||
*.newpax
|
||||
|
||||
# nomencl
|
||||
*.nlg
|
||||
*.nlo
|
||||
*.nls
|
||||
|
||||
# pax
|
||||
*.pax
|
||||
|
||||
# pdfpcnotes
|
||||
*.pdfpc
|
||||
|
||||
# sagetex
|
||||
*.sagetex.sage
|
||||
*.sagetex.py
|
||||
*.sagetex.scmd
|
||||
|
||||
# scrwfile
|
||||
*.wrt
|
||||
|
||||
# svg
|
||||
svg-inkscape/
|
||||
|
||||
# sympy
|
||||
*.sout
|
||||
*.sympy
|
||||
sympy-plots-for-*.tex/
|
||||
|
||||
# pdfcomment
|
||||
*.upa
|
||||
*.upb
|
||||
|
||||
# pythontex
|
||||
*.pytxcode
|
||||
pythontex-files-*/
|
||||
|
||||
# tcolorbox
|
||||
*.listing
|
||||
|
||||
# thmtools
|
||||
*.loe
|
||||
|
||||
# TikZ & PGF
|
||||
*.dpth
|
||||
*.md5
|
||||
*.auxlock
|
||||
|
||||
# titletoc
|
||||
*.ptc
|
||||
|
||||
# todonotes
|
||||
*.tdo
|
||||
|
||||
# vhistory
|
||||
*.hst
|
||||
*.ver
|
||||
|
||||
# easy-todo
|
||||
*.lod
|
||||
|
||||
# xcolor
|
||||
*.xcp
|
||||
|
||||
# xmpincl
|
||||
*.xmpi
|
||||
|
||||
# xindy
|
||||
*.xdy
|
||||
|
||||
# xypic precompiled matrices and outlines
|
||||
*.xyc
|
||||
*.xyd
|
||||
|
||||
# endfloat
|
||||
*.ttt
|
||||
*.fff
|
||||
|
||||
# Latexian
|
||||
TSWLatexianTemp*
|
||||
|
||||
## Editors:
|
||||
# WinEdt
|
||||
*.bak
|
||||
*.sav
|
||||
|
||||
# Texpad
|
||||
.texpadtmp
|
||||
|
||||
# LyX
|
||||
*.lyx~
|
||||
|
||||
# Kile
|
||||
*.backup
|
||||
|
||||
# gummi
|
||||
.*.swp
|
||||
|
||||
# KBibTeX
|
||||
*~[0-9]*
|
||||
|
||||
# TeXnicCenter
|
||||
*.tps
|
||||
|
||||
# auto folder when using emacs and auctex
|
||||
./auto/*
|
||||
*.el
|
||||
|
||||
# expex forward references with \gathertags
|
||||
*-tags.tex
|
||||
|
||||
# standalone packages
|
||||
*.sta
|
||||
|
||||
# Makeindex log files
|
||||
*.lpz
|
||||
|
||||
# xwatermark package
|
||||
*.xwm
|
||||
|
||||
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
|
||||
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
||||
# Uncomment the next line to have this generated file ignored.
|
||||
#*Notes.bib
|
||||
|
||||
### TeX Patch ###
|
||||
# LIPIcs / OASIcs
|
||||
*.vtc
|
||||
|
||||
# glossaries
|
||||
*.glstex
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/tex,r
|
||||
|
||||
*.html
|
||||
bib
|
||||
# Sweave-specific
|
||||
*.tex
|
||||
*-SAVE-ERROR
|
||||
29
doc/.latexmkrc
Normal file
29
doc/.latexmkrc
Normal file
@@ -0,0 +1,29 @@
|
||||
@default_files = ('report.Rnw');
|
||||
$latex = 'latex %O --shell-escape %S';
|
||||
$pdflatex = 'pdflatex --shell-escape %S -synctex=1 %O %S';
|
||||
$pdf_mode = 1;
|
||||
$clean_ext = 'lol nav snm loa bbl* glo ist tex aux bbl blg log out toc Rnw.tex %R-concordance.tex %R.tex';
|
||||
$bibtex_use = 2;
|
||||
|
||||
# only enable when compiling .Rnw or .Rtex file
|
||||
if(grep(/\.(rnw|rtex)$/i, @ARGV)) {
|
||||
$latex = 'internal knitrlatex ' . $latex;
|
||||
$pdflatex = 'internal knitrlatex ' . $pdflatex;
|
||||
my $knitr_compiled = {};
|
||||
sub knitrlatex {
|
||||
for (@_) {
|
||||
next unless -e $_;
|
||||
my $input = $_;
|
||||
next unless $_ =~ s/\.(rnw|rtex)$/.tex/i;
|
||||
my $tex = $_;
|
||||
my $checksum = (fdb_get($input))[-1];
|
||||
if (!$knitr_compiled{$input} || $knitr_compiled{$input} ne $checksum) {
|
||||
my $ret = system("Rscript -e \"knitr::knit('$input')\"");
|
||||
if($ret) { return $ret; }
|
||||
rdb_ensure_file($rule, $tex);
|
||||
$knitr_compiled{$input} = $checksum;
|
||||
}
|
||||
}
|
||||
return system(@_);
|
||||
}
|
||||
}
|
||||
13
doc/hci-doc.Rproj
Normal file
13
doc/hci-doc.Rproj
Normal file
@@ -0,0 +1,13 @@
|
||||
Version: 1.0
|
||||
|
||||
RestoreWorkspace: Default
|
||||
SaveWorkspace: Default
|
||||
AlwaysSaveHistory: Default
|
||||
|
||||
EnableCodeIndexing: Yes
|
||||
UseSpacesForTab: Yes
|
||||
NumSpacesForTab: 2
|
||||
Encoding: UTF-8
|
||||
|
||||
RnwWeave: knitr
|
||||
LaTeX: pdfLaTeX
|
||||
BIN
doc/images/circle-pic.png
Normal file
BIN
doc/images/circle-pic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
BIN
doc/images/dvorak-pic.png
Normal file
BIN
doc/images/dvorak-pic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
BIN
doc/images/qwerty-pic.png
Normal file
BIN
doc/images/qwerty-pic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
doc/images/tauri.png
Normal file
BIN
doc/images/tauri.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 378 KiB |
12
doc/procedure.md
Normal file
12
doc/procedure.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Procedure
|
||||
1. Open `TestTestExe`
|
||||
2. Go to `Test > Options`:
|
||||
1. Tab `Auto`: Stop test after Trial **13**, Switch to test mode after trial **3**
|
||||
2. Tab `Prevention`: Prevent **Punctuation, numbers, capitals**
|
||||
3. determine keyboard testing sequence
|
||||
4. ask participant age
|
||||
5. Repeat for each keyboard:
|
||||
1. Set keyboard
|
||||
2. Participant fulfills typing task
|
||||
3. Note down `wpm` and `ter` in results.csv
|
||||
6. Present reduced NASA-TLX
|
||||
1285
doc/renv.lock
Normal file
1285
doc/renv.lock
Normal file
File diff suppressed because one or more lines are too long
7
doc/renv/.gitignore
vendored
Normal file
7
doc/renv/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
library/
|
||||
local/
|
||||
cellar/
|
||||
lock/
|
||||
python/
|
||||
sandbox/
|
||||
staging/
|
||||
1403
doc/renv/activate.R
Normal file
1403
doc/renv/activate.R
Normal file
File diff suppressed because it is too large
Load Diff
20
doc/renv/settings.json
Normal file
20
doc/renv/settings.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"bioconductor.version": null,
|
||||
"external.libraries": [],
|
||||
"ignored.packages": [],
|
||||
"package.dependency.fields": [
|
||||
"Imports",
|
||||
"Depends",
|
||||
"LinkingTo"
|
||||
],
|
||||
"ppm.enabled": null,
|
||||
"ppm.ignored.urls": [],
|
||||
"r.version": null,
|
||||
"snapshot.dev": false,
|
||||
"snapshot.type": "implicit",
|
||||
"use.cache": true,
|
||||
"vcs.ignore.cellar": true,
|
||||
"vcs.ignore.library": true,
|
||||
"vcs.ignore.local": true,
|
||||
"vcs.manage.ignores": true
|
||||
}
|
||||
476
doc/report.Rnw
Normal file
476
doc/report.Rnw
Normal file
@@ -0,0 +1,476 @@
|
||||
\documentclass[conference,a4paper]{IEEEtran}
|
||||
|
||||
\usepackage{graphicx} % for including figures
|
||||
\usepackage{booktabs} % for nicer tables
|
||||
\usepackage{float}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage[margin=1in]{geometry} % Adjust margins
|
||||
\usepackage{caption}
|
||||
\usepackage{hyperref}
|
||||
\PassOptionsToPackage{hyphens}{url} % allow breaking urls
|
||||
\usepackage{float}
|
||||
\usepackage{wrapfig}
|
||||
\usepackage{subcaption}
|
||||
\usepackage{parskip}
|
||||
|
||||
\usepackage[style=ieee, backend=biber, maxnames=1, minnames=1]{biblatex}
|
||||
\addbibresource{report.bib}
|
||||
|
||||
\title{On-Screen Keyboard Layout Study}
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
\section{Abstract}\label{abstract}
|
||||
We evaluated three on-screen keyboard layouts: QWERTY, Dvorak, and Circle. Objective performance, measured in words per minute (WPM), showed a significant main effect of layout. Post-hoc comparisons revealed that QWERTY was significantly faster than both Dvorak and Circle, while no difference was observed between Dvorak and Circle. Total error rate (TER) did not differ significantly between layouts.
|
||||
Subjective workload ratings assessed via NASA-TLX were similar for Dvorak and Circle, but QWERTY was perceived as less demanding. These results indicate that QWERTY offers superior typing speed, whereas error rates and perceived workload are comparable across layouts.
|
||||
|
||||
\section{Introduction}\label{introduction}
|
||||
On-screen keyboards have become increasingly relevant for applications where traditional physical keyboards are not practical, such as in virtual environments, accessibility tools, or experimental settings. Unlike physical keyboards, on-screen keyboards require users to select keys via a pointing device, such as a mouse, which introduces unique challenges in terms of speed, accuracy, and ergonomic efficiency.
|
||||
|
||||
Traditional physical keyboard layouts, such as QWERTY and Dvorak, have been extensively studied and optimized for mechanical typing. QWERTY, the most commonly used layout, was originally designed for typewriters and provides a familiar arrangement for most users. Dvorak, in contrast, aims to increase typing efficiency by placing frequently used letters on the home row to minimize finger movement. While these layouts are well understood for physical typing, their effectiveness may differ when key selection is performed via a pointing device.
|
||||
|
||||
In addition to established layouts, novel designs have emerged to exploit the flexibility of on-screen interfaces. For example, circular or radial layouts can optimize visual prominence and spatial reachability of keys, potentially improving selection speed and reducing cognitive effort when using a mouse. However, empirical comparisons of these alternative layouts with traditional designs remain limited, particularly with respect to both objective measures such as words per minute (WPM) and total error rate (TER), and subjective workload assessments.
|
||||
|
||||
The present study investigates three on-screen keyboard layouts: QWERTY, Dvorak, and a custom-designed Circle layout. Participants entered text using a mouse to select keys, allowing us to evaluate typing speed, accuracy, and perceived workload across different layouts. The results provide insights into the efficiency and usability of established and novel on-screen keyboard designs in contexts where mouse-based input is required.
|
||||
|
||||
\section{Keyboard Designs}\label{keyboard-designs}
|
||||
Three on-screen keyboard layouts were evaluated in this study: QWERTY, Dvorak, and a custom-designed Circle layout.
|
||||
|
||||
1. QWERTY: The standard layout commonly used in English typing, serving as a baseline for comparison.
|
||||
|
||||
2. Dvorak: An alternative layout designed to increase typing efficiency for physical keyboards
|
||||
by placing frequently used letters in the home row, minimizing finger movements.
|
||||
|
||||
3. Circle Layout: A custom layout developed for this study, in which keys were arranged in a circular pattern. Letters that occur more frequently in English were positioned closer to the center and rendered larger to facilitate faster access. Less frequently used letters were placed toward the periphery and sized smaller, aiming to optimize ergonomic reach and visual salience.
|
||||
|
||||
This design allowed us to investigate both established and novel layouts, comparing objective typing performance, error rates, and subjective workload.
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=0.45\textwidth]{images/qwerty-pic.png}
|
||||
\caption{QWERTY Keyboard Layout}
|
||||
\label{fig:qwerty}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=0.45\textwidth]{images/dvorak-pic.png}
|
||||
\caption{Dvorak Keyboard Layout}
|
||||
\label{fig:dvorak}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=0.3\textwidth]{images/circle-pic.png}
|
||||
\caption{Circle Keyboard Layout}
|
||||
\label{fig:circle}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\section{Experiment}\label{experiment}
|
||||
<<echo=FALSE, message=FALSE>>=
|
||||
# load libraries here
|
||||
library(knitr)
|
||||
library(dplyr)
|
||||
library(tidyr)
|
||||
|
||||
# Read the results CSV
|
||||
results <- read.csv("../data/results.csv", sep=",", header=TRUE)
|
||||
@
|
||||
|
||||
\subsection{Participants}\label{participants}
|
||||
Our experiment was conducted using a small sample of \Sexpr{nrow(results)} participants.
|
||||
All of our participants, predominantly male with an average age of \Sexpr{round(mean(results$age),digits=1)},
|
||||
were students familiar with computers.
|
||||
|
||||
\subsection{Apparatus}\label{apparatus}
|
||||
The main body of our experimental apparatus was our On-Screen Keyboard, implemented using Tauri + Angular.
|
||||
It provides a view of exactly one of the layouts described in \autoref{keyboard-designs} at a time.
|
||||
Text-entry measures were collected using TextTest \cite{texttest}, with everything running on a stationary Windows 11 computer.
|
||||
|
||||
\subsection{Procedure}\label{procedure}
|
||||
Each participant was first provided with an overview of the three keyboard models and design rationale
|
||||
They were then presented all three layouts in a counterbalanced order to mitigate common order effects
|
||||
such as practice, fatigue and boredom.
|
||||
Each keyboard was evaluated using only lowercase letters, space and enter to display the next sentence.
|
||||
Due to our chosen time constraint of 30 minutes, each participant was given three practice sentences per keyboard,
|
||||
followed by 10 recorded sentences for the experiment.
|
||||
After completion of all 3 layouts, the participants were then asked to fill out the NASA Task Load Index \cite{nasatlx}.
|
||||
|
||||
\section{Results}\label{results}
|
||||
|
||||
This section presents the experiment results comparing the three keyboard layouts QWERTY, Dvorak and Circle.
|
||||
Performance was evaluated using typing speed measured in words per minute (WPM) and accuracy assessed through the total error rate (TER).
|
||||
In addition, subjective workload was collected using the NASA-TLX questionnaire.
|
||||
|
||||
\subsection{Descriptive Statistics}\label{descriptive-statistics}
|
||||
|
||||
This section reports descriptive statistics for the objective performance measures and the subjective workload assessments.
|
||||
|
||||
\subsubsection{Objective Measures}\label{objective-measures}
|
||||
|
||||
Objective typing performance was assessed using words per minute (WPM) and total error rate (TER) as shown in table~\ref{tab:wpm} and ~\ref{tab:ter}. Descriptive statistics show that QWERTY clearly outperformed the alternative layouts in terms of typing speed. Participants achieved a mean speed of 17.28 WPM on QWERTY, whereas both DVORAK (8.27 WPM) and CIRCLE (8.45 WPM) resulted in substantially lower average speeds. This indicates that participants typed more than twice as fast on the standard QWERTY layout compared to the other two designs.
|
||||
|
||||
In contrast, accuracy differences between layouts were relatively small. Mean TER values were low across all conditions, with QWERTY showing a slightly higher average error rate (0.036) than DVORAK (0.0298) and CIRCLE (0.0265). Overall, the objective results suggest that layout differences were most pronounced for speed rather than error performance.
|
||||
|
||||
<<echo=FALSE, message=FALSE>>=
|
||||
|
||||
ter_stats <- results %>%
|
||||
summarise(
|
||||
qwerty_min = min(qwerty_ter, na.rm = TRUE),
|
||||
qwerty_q1 = quantile(qwerty_ter, 0.25, na.rm = TRUE),
|
||||
qwerty_median = median(qwerty_ter, na.rm = TRUE),
|
||||
qwerty_mean = mean(qwerty_ter, na.rm = TRUE),
|
||||
qwerty_q3 = quantile(qwerty_ter, 0.75, na.rm = TRUE),
|
||||
qwerty_max = max(qwerty_ter, na.rm = TRUE),
|
||||
|
||||
dvorak_min = min(dvorak_ter, na.rm = TRUE),
|
||||
dvorak_q1 = quantile(dvorak_ter, 0.25, na.rm = TRUE),
|
||||
dvorak_median = median(dvorak_ter, na.rm = TRUE),
|
||||
dvorak_mean = mean(dvorak_ter, na.rm = TRUE),
|
||||
dvorak_q3 = quantile(dvorak_ter, 0.75, na.rm = TRUE),
|
||||
dvorak_max = max(dvorak_ter, na.rm = TRUE),
|
||||
|
||||
circle_min = min(circle_ter, na.rm = TRUE),
|
||||
circle_q1 = quantile(circle_ter, 0.25, na.rm = TRUE),
|
||||
circle_median = median(circle_ter, na.rm = TRUE),
|
||||
circle_mean = mean(circle_ter, na.rm = TRUE),
|
||||
circle_q3 = quantile(circle_ter, 0.75, na.rm = TRUE),
|
||||
circle_max = max(circle_ter, na.rm = TRUE)
|
||||
)
|
||||
ter_tidy <- ter_stats %>%
|
||||
pivot_longer(
|
||||
cols = everything(),
|
||||
names_to = c("layout", ".value"),
|
||||
names_sep = "_"
|
||||
)
|
||||
ter_tidy <- ter_tidy %>%
|
||||
select(layout, min, q1, median, mean, q3, max)
|
||||
|
||||
wpm_stats <- results %>%
|
||||
summarise(
|
||||
qwerty_min = min(qwerty_wpm, na.rm = TRUE),
|
||||
qwerty_q1 = quantile(qwerty_wpm, 0.25, na.rm = TRUE),
|
||||
qwerty_median = median(qwerty_wpm, na.rm = TRUE),
|
||||
qwerty_mean = mean(qwerty_wpm, na.rm = TRUE),
|
||||
qwerty_q3 = quantile(qwerty_wpm, 0.75, na.rm = TRUE),
|
||||
qwerty_max = max(qwerty_wpm, na.rm = TRUE),
|
||||
|
||||
dvorak_min = min(dvorak_wpm, na.rm = TRUE),
|
||||
dvorak_q1 = quantile(dvorak_wpm, 0.25, na.rm = TRUE),
|
||||
dvorak_median = median(dvorak_wpm, na.rm = TRUE),
|
||||
dvorak_mean = mean(dvorak_wpm, na.rm = TRUE),
|
||||
dvorak_q3 = quantile(dvorak_wpm, 0.75, na.rm = TRUE),
|
||||
dvorak_max = max(dvorak_wpm, na.rm = TRUE),
|
||||
|
||||
circle_min = min(circle_wpm, na.rm = TRUE),
|
||||
circle_q1 = quantile(circle_wpm, 0.25, na.rm = TRUE),
|
||||
circle_median = median(circle_wpm, na.rm = TRUE),
|
||||
circle_mean = mean(circle_wpm, na.rm = TRUE),
|
||||
circle_q3 = quantile(circle_wpm, 0.75, na.rm = TRUE),
|
||||
circle_max = max(circle_wpm, na.rm = TRUE)
|
||||
)
|
||||
|
||||
wpm_tidy <- wpm_stats %>%
|
||||
pivot_longer(
|
||||
cols = everything(),
|
||||
names_to = c("layout", ".value"),
|
||||
names_sep = "_"
|
||||
)
|
||||
|
||||
wpm_tidy <- wpm_tidy %>%
|
||||
select(layout, min, q1, median, mean, q3, max)
|
||||
|
||||
@
|
||||
|
||||
|
||||
% WPM table
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Summary of Words per Minute (WPM)}
|
||||
\label{tab:wpm}
|
||||
\resizebox{\columnwidth}{!}{
|
||||
<<results='asis', echo=FALSE>>=
|
||||
kable(wpm_tidy, format="latex", booktabs=TRUE)
|
||||
@
|
||||
}
|
||||
\end{table}
|
||||
|
||||
% TER table
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Summary of Total Error Rate (TER)}
|
||||
\label{tab:ter}
|
||||
\resizebox{\columnwidth}{!}{
|
||||
<<results='asis', echo=FALSE>>=
|
||||
kable(ter_tidy, format="latex", booktabs=TRUE)
|
||||
@
|
||||
}
|
||||
\end{table}
|
||||
|
||||
|
||||
<<echo=FALSE, results='hide'>>=
|
||||
# Create figures directory if it doesn't exist
|
||||
dir.create("../figures", showWarnings=FALSE)
|
||||
|
||||
# Helper functions for standard deviation and confidence intervals
|
||||
mean_sd <- function(x) {
|
||||
m <- mean(x)
|
||||
s <- sd(x)
|
||||
c(mean=m, lower=m-s, upper=m+s)
|
||||
}
|
||||
|
||||
mean_ci <- function(x) {
|
||||
m <- mean(x)
|
||||
se <- sd(x)/sqrt(length(x))
|
||||
ci <- qt(0.975, df=length(x)-1)*se
|
||||
c(mean=m, lower=m-ci, upper=m+ci)
|
||||
}
|
||||
|
||||
# TER stats
|
||||
ter_stats <- rbind(
|
||||
mean_ci(results$qwerty_ter),
|
||||
mean_ci(results$dvorak_ter),
|
||||
mean_ci(results$circle_ter)
|
||||
)
|
||||
|
||||
# Save TER barplot as PDF using LaTeX-compatible fonts
|
||||
suppressMessages(pdf("../figures/ter_plot.pdf"))
|
||||
bar_pos <- barplot(
|
||||
ter_stats[,"mean"],
|
||||
names.arg=c("QWERTY","DVORAK","CIRCLE"),
|
||||
ylab="Total Error Rate (TER)",
|
||||
main="TER of layouts",
|
||||
ylim=c(0, max(ter_stats[,"upper"])*1.1)
|
||||
)
|
||||
# Add confidence intervals
|
||||
arrows(
|
||||
x0=bar_pos, y0=ter_stats[,"lower"],
|
||||
x1=bar_pos, y1=ter_stats[,"upper"],
|
||||
angle=90, code=3, length=0.05
|
||||
)
|
||||
dev.off()
|
||||
|
||||
# WPM stats
|
||||
wpm_stats <- rbind(
|
||||
mean_sd(results$qwerty_wpm),
|
||||
mean_sd(results$dvorak_wpm),
|
||||
mean_sd(results$circle_wpm)
|
||||
)
|
||||
|
||||
# Save WPM barplot as PDF using LaTeX-compatible fonts
|
||||
suppressMessages(pdf("../figures/wpm_plot.pdf"))
|
||||
bar_pos <- barplot(
|
||||
wpm_stats[,"mean"],
|
||||
names.arg=c("QWERTY","DVORAK","CIRCLE"),
|
||||
ylab="Words per minute (WPM)",
|
||||
main="WPM of layouts",
|
||||
ylim=c(0, max(wpm_stats[,"upper"])*1.1)
|
||||
)
|
||||
arrows(
|
||||
x0=bar_pos, y0=wpm_stats[,"lower"],
|
||||
x1=bar_pos, y1=wpm_stats[,"upper"],
|
||||
angle=90, code=3, length=0.05
|
||||
)
|
||||
dev.off()
|
||||
@
|
||||
|
||||
%Include ter and wpm plot
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
|
||||
\includegraphics[width=0.7\columnwidth]{../figures/wpm_plot.pdf}
|
||||
|
||||
\vspace{-0.4cm}
|
||||
|
||||
\includegraphics[width=0.7\columnwidth]{../figures/ter_plot.pdf}
|
||||
|
||||
\caption{TER (top) and WPM (bottom) by keyboard layout.}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\subsubsection{Subjective Measures}\label{subjective-measures}
|
||||
|
||||
Subjective workload was measured using the NASA-TLX dimensions of
|
||||
mental demand, physical demand, effort, frustration, and perceived performance (as shown in \autoref{fig:nasa}).
|
||||
Across all workload categories, QWERTY was consistently rated as the most favorable layout,
|
||||
indicating lower perceived demand and higher user comfort.
|
||||
|
||||
Dvorak and Circle received generally similar subjective evaluations, with no major differences between them. However, Circle was mostly better perceived than Dvorak across all NASA-TLX dimensions, suggesting a modest subjective preference for the circular layout design. Overall, the subjective findings align with the objective performance trends, with QWERTY being clearly preferred by participants.
|
||||
|
||||
<<echo=FALSE, results='hide'>>=
|
||||
# Read NASA-TLX data
|
||||
nasa <- read.csv("../data/nasaTLX.csv")
|
||||
nasa$layout <- factor(nasa$layout)
|
||||
|
||||
# Save boxplots as PDF using LaTeX-compatible fonts
|
||||
suppressMessages(pdf("../figures/nasa_boxplots.pdf", width=10, height=20, pointsize = 24))
|
||||
par(mfrow=c(3,2))
|
||||
boxplot(mental_demand ~ layout, data=nasa, main="Mental Demand")
|
||||
boxplot(physical_demand ~ layout, data=nasa, main="Physical Demand")
|
||||
boxplot(performance ~ layout, data=nasa, main="Performance")
|
||||
boxplot(effort ~ layout, data=nasa, main="Effort")
|
||||
boxplot(frustration ~ layout, data=nasa, main="Frustration")
|
||||
par(mfrow=c(1,1))
|
||||
dev.off()
|
||||
@
|
||||
|
||||
% Include NASA-TLX boxplots
|
||||
\begin{figure}[H]
|
||||
\centering
|
||||
\includegraphics[width=\columnwidth]{../figures/nasa_boxplots.pdf}
|
||||
\caption{NASA-TLX Scores by Keyboard Layout}
|
||||
\label{fig:nasa}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Inferential Statistics}\label{inferential-statistics}
|
||||
To examine the effect of keyboard layout on typing performance, repeated-measures ANOVAs were conducted separately for typing speed (WPM) and total error rate (TER).
|
||||
|
||||
%TODO Friedman test of Subjective
|
||||
|
||||
\subsubsection{Typing speed (WPM)}
|
||||
The repeated-measures ANOVA revealed a significant main effect of layout on typing speed, $F(2, 22) = 120.56, p < .001$ (Table~\ref{tab:anova_wpm}). Post-hoc pairwise comparisons with Bonferroni correction indicated that QWERTY yielded significantly higher typing speeds than both DVORAK ($p < .001$) and CIRCLE ($p < .001$).
|
||||
|
||||
|
||||
%Anova RM for WPM
|
||||
<<echo=FALSE, results='hide'>>=
|
||||
library(tidyr)
|
||||
|
||||
# Add participant ID
|
||||
results$id <- 1:nrow(results)
|
||||
|
||||
# --- WPM Long Format ---
|
||||
wpm_long <- results %>%
|
||||
select(id, qwerty_wpm, dvorak_wpm, circle_wpm) %>%
|
||||
pivot_longer(
|
||||
cols = -id,
|
||||
names_to = "layout",
|
||||
values_to = "wpm"
|
||||
)
|
||||
|
||||
wpm_long$id <- factor(wpm_long$id)
|
||||
|
||||
wpm_long$layout <- factor(wpm_long$layout,
|
||||
levels=c("qwerty_wpm","dvorak_wpm","circle_wpm"),
|
||||
labels=c("QWERTY","DVORAK","CIRCLE"))
|
||||
|
||||
# --- RM ANOVA for WPM ---
|
||||
anova_wpm <- aov(wpm ~ layout + Error(id/layout), data=wpm_long)
|
||||
@
|
||||
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Repeated-Measurements ANOVA for WPM}
|
||||
\label{tab:anova_wpm}
|
||||
\resizebox{\columnwidth}{!}{
|
||||
<<results='asis', echo=FALSE>>=
|
||||
wpm_tab <- summary(anova_wpm)[[2]][[1]]
|
||||
|
||||
wpm_effect <- wpm_tab["layout", , drop=FALSE]
|
||||
|
||||
wpm_effect$`Pr(>F)` <- "$p< .001$"
|
||||
|
||||
colnames(wpm_effect) <- c("Df", "Sum Sq", "Mean Sq", "F value", "p-value")
|
||||
|
||||
kable(wpm_effect,
|
||||
format="latex",
|
||||
booktabs=TRUE,
|
||||
escape=FALSE)
|
||||
@
|
||||
}
|
||||
\end{table}
|
||||
|
||||
\subsubsection{Total Error Rate (TER)}
|
||||
In contrast, the ANOVA for total error rate did not reveal a significant effect of layout, $F(2, 22) = 0.71, p = 0.505$ (Table~\ref{tab:anova_ter}). This indicates that accuracy was comparable across QWERTY, DVORAK, and CIRCLE layouts. Although QWERTY exhibited a slightly higher mean TER than the other layouts, these differences were not statistically significant. Therefore, while QWERTY facilitated faster typing, it did not compromise accuracy.
|
||||
|
||||
%Anova RM for TER
|
||||
<<echo=FALSE, results='hide'>>=
|
||||
|
||||
# --- TER Long Format ---
|
||||
ter_long <- results %>%
|
||||
select(id, qwerty_ter, dvorak_ter, circle_ter) %>%
|
||||
pivot_longer(
|
||||
cols = -id,
|
||||
names_to = "layout",
|
||||
values_to = "ter"
|
||||
)
|
||||
|
||||
ter_long$id <- factor(ter_long$id)
|
||||
|
||||
ter_long$layout <- factor(ter_long$layout,
|
||||
levels=c("qwerty_ter","dvorak_ter","circle_ter"),
|
||||
labels=c("QWERTY","DVORAK","CIRCLE"))
|
||||
|
||||
# --- RM ANOVA for TER ---
|
||||
anova_ter <- aov(ter ~ layout + Error(id/layout), data=ter_long)
|
||||
@
|
||||
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Repeated-Measures ANOVA for TER}
|
||||
\label{tab:anova_ter}
|
||||
\resizebox{\columnwidth}{!}{
|
||||
<<results='asis', echo=FALSE>>=
|
||||
ter_tab <- summary(anova_ter)[[2]][[1]]
|
||||
|
||||
ter_effect <- ter_tab["layout", , drop=FALSE]
|
||||
|
||||
colnames(ter_effect) <- c("Df", "Sum Sq", "Mean Sq", "F value", "p-value")
|
||||
|
||||
kable(ter_effect,
|
||||
format="latex",
|
||||
booktabs=TRUE,
|
||||
escape=FALSE)
|
||||
@
|
||||
}
|
||||
\end{table}
|
||||
|
||||
|
||||
\subsubsection{Post-hoc Comparison for WPM}
|
||||
Post-hoc pairwise comparisons with Bonferroni adjustment were conducted to further explore differences between keyboard layouts (Table~\ref{tab:posthoc}). Results revealed that QWERTY was significantly faster than DVORAK ($p < .001$) and CIRCLE ($p < .001$), confirming the advantage of the standard layout. The difference between DVORAK and CIRCLE was not significant ($p = 1.000$), indicating comparable performance between these alternative layouts. These comparisons highlight that the observed main effect of layout on typing speed is primarily driven by the superior performance of QWERTY, while the two non-standard layouts yield similar typing speeds.
|
||||
|
||||
% Post-Hoc analysis with bonferroni correction for WPM
|
||||
\begin{table}[H]
|
||||
\centering
|
||||
\caption{Post-hoc-comparison of layouts with Bonferroni correction}
|
||||
\label{tab:posthoc}
|
||||
\resizebox{\columnwidth}{!}{
|
||||
<<echo=FALSE, results='asis'>>=
|
||||
suppressMessages(library(emmeans))
|
||||
|
||||
suppressMessages(emm_wpm <- emmeans(anova_wpm, ~ layout))
|
||||
|
||||
posthoc <- pairs(emm_wpm, adjust = "bonferroni")
|
||||
posthoc_df <- as.data.frame(posthoc)
|
||||
|
||||
posthoc_df <- posthoc_df %>%
|
||||
mutate(p.value = ifelse(p.value < 0.001, "$<0.001$", sprintf("%.3f", p.value)))
|
||||
|
||||
kable(
|
||||
posthoc_df,
|
||||
format = "latex",
|
||||
booktabs = TRUE,
|
||||
digits = 3,
|
||||
float=FALSE,
|
||||
escape=FALSE
|
||||
)
|
||||
@
|
||||
}
|
||||
\end{table}
|
||||
\section{Discussion}\label{discussion}
|
||||
The results of this study provide clear evidence that keyboard layout significantly affects typing performance when using a mouse to select keys on an on-screen keyboard. Participants typed substantially faster on the QWERTY layout compared to both the Dvorak and Circle layouts, with mean WPM values more than twice as high. This finding indicates that prior familiarity with the QWERTY layout plays a crucial role in enabling efficient text entry, even when interaction is performed via a pointing device rather than physical key presses.
|
||||
|
||||
In contrast, total error rates were low across all layouts and did not differ significantly. This suggests that participants were able to maintain accuracy regardless of layout, and that the speed advantage of QWERTY did not come at the cost of increased errors. The similar TER values across layouts indicate that the Circle layout, despite its unconventional design, and Dvorak, despite its unfamiliarity, are viable for accurate text entry with a mouse, even if they do not provide a speed advantage.
|
||||
|
||||
Subjective workload assessments using NASA-TLX mirrored the objective performance results. QWERTY was consistently rated as less mentally demanding and effortful, reflecting participants' familiarity and confidence with this layout. Dvorak and Circle were evaluated similarly across most dimensions, with Circle slightly preferred in some cases, indicating that its design may offer modest ergonomic or cognitive benefits compared to Dvorak, although these did not translate into faster typing speeds.
|
||||
|
||||
Overall, the findings highlight several important points for the design of on-screen keyboards for mouse-based input. First, established layouts like QWERTY remain highly efficient due to user familiarity, even in non-traditional input modalities. Second, novel layouts such as Circle can achieve comparable accuracy and subjective comfort, suggesting potential for specialized applications where visual ergonomics or reach optimization is critical. Finally, training and experience are likely necessary for users to achieve performance gains with alternative layouts like Dvorak or Circle.
|
||||
|
||||
In practical terms, these results suggest that for general-purpose applications requiring rapid text entry via a mouse, QWERTY should be the default layout. However, alternative designs could be explored in contexts where visual salience, ergonomics, or user-specific customization is prioritized, provided users are given adequate practice to adapt to the new layout.
|
||||
|
||||
Future research should investigate longer-term adaptation effects, as performance with unfamiliar layouts may improve substantially with training. Additionally, studies could examine the impact of alternative pointing devices (e.g., trackpads, styluses) and larger participant samples to generalize findings across different user populations. Such work would help refine the design principles of on-screen keyboards for a range of input scenarios beyond traditional physical typing.
|
||||
|
||||
\printbibliography
|
||||
\end{document}
|
||||
20
doc/report.bib
Normal file
20
doc/report.bib
Normal file
@@ -0,0 +1,20 @@
|
||||
@article{texttest,
|
||||
title={Analyzing the input stream for character-level errors in unconstrained text entry evaluations},
|
||||
author={Wobbrock, Jacob O and Myers, Brad A},
|
||||
journal={ACM Transactions on Computer-Human Interaction (TOCHI)},
|
||||
volume={13},
|
||||
number={4},
|
||||
pages={458--489},
|
||||
year={2006},
|
||||
publisher={ACM New York, NY, USA},
|
||||
url={https://depts.washington.edu/acelab/proj/texttest/},
|
||||
}
|
||||
@incollection{nasatlx,
|
||||
title={Development of NASA-TLX (Task Load Index): Results of empirical and theoretical research},
|
||||
author={Hart, Sandra G and Staveland, Lowell E},
|
||||
booktitle={Advances in psychology},
|
||||
volume={52},
|
||||
pages={139--183},
|
||||
year={1988},
|
||||
publisher={Elsevier}
|
||||
}
|
||||
132
doc/slides.Rmd
Normal file
132
doc/slides.Rmd
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
title: "On-Screen Keyboard Layout Study"
|
||||
subtitle: "Human-Computer Interfaces"
|
||||
date: "`r Sys.Date()`"
|
||||
output: beamer_presentation
|
||||
---
|
||||
|
||||
```{r setup, include=FALSE}
|
||||
knitr::opts_chunk$set(echo = FALSE)
|
||||
library(tidyr)
|
||||
library(dplyr)
|
||||
results <- read.csv("../data/results.csv", sep=",", header=TRUE)
|
||||
```
|
||||
## Implementation
|
||||

|
||||
|
||||
## Keyboard Design - Qwerty
|
||||

|
||||
|
||||
## Keyboard Design - Dvorak
|
||||

|
||||
|
||||
## Keyboard Design - Circle
|
||||

|
||||
|
||||
## Descriptive Stats - Objective
|
||||

|
||||
|
||||
---
|
||||

|
||||
|
||||
## Descriptive Stats- Subjective
|
||||
|
||||
```{r, echo=FALSE}
|
||||
# Read NASA-TLX data
|
||||
nasa <- read.csv("../data/nasaTLX.csv")
|
||||
nasa$layout <- factor(nasa$layout)
|
||||
|
||||
# Save boxplots as PDF using LaTeX-compatible fonts
|
||||
par(mfrow=c(1,2))
|
||||
boxplot(mental_demand ~ layout, data=nasa, main="Mental Demand")
|
||||
boxplot(physical_demand ~ layout, data=nasa, main="Physical Demand")
|
||||
```
|
||||
|
||||
## Descriptive Stats- Subjective
|
||||
|
||||
```{r, echo=FALSE}
|
||||
par(mfrow=c(1,3))
|
||||
boxplot(performance ~ layout, data=nasa, main="Performance")
|
||||
boxplot(effort ~ layout, data=nasa, main="Effort")
|
||||
boxplot(frustration ~ layout, data=nasa, main="Frustration")
|
||||
par(mfrow=c(1,1))
|
||||
```
|
||||
|
||||
## Inferential Stats
|
||||
WPM:
|
||||
```{r, echo=FALSE}
|
||||
# Add participant ID
|
||||
results$id <- 1:nrow(results)
|
||||
|
||||
# --- WPM Long Format ---
|
||||
wpm_long <- results %>%
|
||||
select(id, qwerty_wpm, dvorak_wpm, circle_wpm) %>%
|
||||
pivot_longer(
|
||||
cols = -id,
|
||||
names_to = "layout",
|
||||
values_to = "wpm"
|
||||
)
|
||||
|
||||
wpm_long$id <- factor(wpm_long$id)
|
||||
|
||||
wpm_long$layout <- factor(wpm_long$layout,
|
||||
levels=c("qwerty_wpm","dvorak_wpm","circle_wpm"),
|
||||
labels=c("QWERTY","DVORAK","CIRCLE"))
|
||||
|
||||
# --- RM ANOVA for WPM ---
|
||||
anova_wpm <- aov(wpm ~ layout + Error(id/layout), data=wpm_long)
|
||||
|
||||
wpm_tab <- summary(anova_wpm)[[2]][[1]]
|
||||
|
||||
wpm_effect <- wpm_tab["layout", , drop=FALSE]
|
||||
|
||||
wpm_effect$`Pr(>F)` <- "$p< .001$"
|
||||
|
||||
colnames(wpm_effect) <- c("Df", "Sum Sq", "Mean Sq", "F value", "p-value")
|
||||
wpm_effect
|
||||
```
|
||||
|
||||
TER:
|
||||
```{r, echo=FALSE}
|
||||
|
||||
# --- TER Long Format ---
|
||||
ter_long <- results %>%
|
||||
select(id, qwerty_ter, dvorak_ter, circle_ter) %>%
|
||||
pivot_longer(
|
||||
cols = -id,
|
||||
names_to = "layout",
|
||||
values_to = "ter"
|
||||
)
|
||||
|
||||
ter_long$id <- factor(ter_long$id)
|
||||
|
||||
ter_long$layout <- factor(ter_long$layout,
|
||||
levels=c("qwerty_ter","dvorak_ter","circle_ter"),
|
||||
labels=c("QWERTY","DVORAK","CIRCLE"))
|
||||
|
||||
# --- RM ANOVA for TER ---
|
||||
anova_ter <- aov(ter ~ layout + Error(id/layout), data=ter_long)
|
||||
ter_tab <- summary(anova_ter)[[2]][[1]]
|
||||
|
||||
ter_effect <- ter_tab["layout", , drop=FALSE]
|
||||
|
||||
colnames(ter_effect) <- c("Df", "Sum Sq", "Mean Sq", "F value", "p-value")
|
||||
ter_effect
|
||||
```
|
||||
|
||||
## Post-Hoc
|
||||
```{r, echo=FALSE, message=FALSE}
|
||||
|
||||
library(emmeans)
|
||||
|
||||
emm_wpm <- emmeans(anova_wpm, ~ layout)
|
||||
|
||||
posthoc <- pairs(emm_wpm, adjust = "bonferroni")
|
||||
posthoc_df <- as.data.frame(posthoc)
|
||||
|
||||
posthoc_df <- posthoc_df %>%
|
||||
mutate(p.value = ifelse(p.value < 0.001, "$<0.001$", sprintf("%.3f", p.value)))
|
||||
|
||||
posthoc_df$SE <- NULL
|
||||
posthoc_df
|
||||
```
|
||||
44
package-lock.json
generated
44
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "keeb",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "keeb",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.1",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^18.2.14",
|
||||
"@angular/common": "^18.2.14",
|
||||
@@ -17,6 +17,7 @@
|
||||
"@angular/platform-browser-dynamic": "^18.2.14",
|
||||
"@angular/router": "^18.2.14",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
@@ -358,7 +359,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.14.tgz",
|
||||
"integrity": "sha512-Kp/MWShoYYO+R3lrrZbZgszbbLGVXHB+39mdJZwnIuZMDkeL3JsIBlSOzyJRTnpS1vITc+9jgHvP/6uKbMrW1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -554,7 +554,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.14.tgz",
|
||||
"integrity": "sha512-ZPRswzaVRiqcfZoowuAM22Hr2/z10ajWOUoFDoQ9tWqz/fH/773kJv2F9VvePIekgNPCzaizqv9gF6tGNqaAwg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -571,7 +570,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.14.tgz",
|
||||
"integrity": "sha512-Mpq3v/mztQzGAQAAFV+wAI1hlXxZ0m8eDBgaN2kD3Ue+r4S6bLm1Vlryw0iyUnt05PcFIdxPT6xkcphq5pl6lw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -593,7 +591,6 @@
|
||||
"integrity": "sha512-BmmjyrFSBSYkm0tBSqpu4cwnJX/b/XvhM36mj2k8jah3tNS5zLDDx5w6tyHmaPJa/1D95MlXx2h6u7K9D+Mhew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.25.2",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
@@ -700,7 +697,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.14.tgz",
|
||||
"integrity": "sha512-BIPrCs93ZZTY9ym7yfoTgAQ5rs706yoYeAdrgc8kh/bDbM9DawxKlgeKBx2FLt09Y0YQ1bFhKVp0cV4gDEaMxQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -735,7 +731,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.14.tgz",
|
||||
"integrity": "sha512-W+JTxI25su3RiZVZT3Yrw6KNUCmOIy7OZIZ+612skPgYK2f2qil7VclnW1oCwG896h50cMJU/lnAfxZxefQgyQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@@ -820,7 +815,6 @@
|
||||
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
@@ -3118,7 +3112,6 @@
|
||||
"integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@inquirer/checkbox": "^2.4.7",
|
||||
"@inquirer/confirm": "^3.1.22",
|
||||
@@ -4581,6 +4574,15 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-cli": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-cli/-/plugin-cli-2.4.1.tgz",
|
||||
"integrity": "sha512-8JXofQFI5cmiGolh1PlU4hzE2YJgrgB1lyaztyBYiiMCy13luVxBXaXChYPeqMkUo46J1UadxvYdjRjj0E8zaw==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-opener": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.0.tgz",
|
||||
@@ -4779,7 +4781,6 @@
|
||||
"integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.14.0"
|
||||
}
|
||||
@@ -5120,7 +5121,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5197,7 +5197,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -5641,7 +5640,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
@@ -8711,8 +8709,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz",
|
||||
"integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
@@ -8838,7 +8835,6 @@
|
||||
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@colors/colors": "1.5.0",
|
||||
"body-parser": "^1.19.0",
|
||||
@@ -9051,7 +9047,6 @@
|
||||
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"copy-anything": "^2.0.1",
|
||||
"parse-node-version": "^1.0.1",
|
||||
@@ -11099,7 +11094,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
@@ -11743,7 +11737,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
@@ -11800,7 +11793,6 @@
|
||||
"integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
@@ -12921,7 +12913,6 @@
|
||||
"integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
@@ -13080,8 +13071,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tuf-js": {
|
||||
"version": "2.2.1",
|
||||
@@ -13138,7 +13128,6 @@
|
||||
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -13969,7 +13958,6 @@
|
||||
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.5",
|
||||
"@webassemblyjs/ast": "^1.12.1",
|
||||
@@ -14199,7 +14187,6 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -14452,8 +14439,7 @@
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
|
||||
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
package.json
13
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keeb",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.1",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
@@ -18,16 +18,18 @@
|
||||
"@angular/platform-browser": "^18.2.14",
|
||||
"@angular/platform-browser-dynamic": "^18.2.14",
|
||||
"@angular/router": "^18.2.14",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2"
|
||||
"zone.js": "~0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^18.2.21",
|
||||
"@angular/cli": "^18.2.21",
|
||||
"@angular/compiler-cli": "^18.2.14",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
@@ -35,7 +37,6 @@
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.4.0",
|
||||
"@tauri-apps/cli": "^2"
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
117
src-tauri/Cargo.lock
generated
117
src-tauri/Cargo.lock
generated
@@ -41,6 +41,56 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
@@ -456,6 +506,39 @@ dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -1750,6 +1833,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -1843,6 +1932,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-cli",
|
||||
"tauri-plugin-opener",
|
||||
"windows 0.62.2",
|
||||
"x11",
|
||||
@@ -2434,6 +2524,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.2"
|
||||
@@ -3754,6 +3850,21 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-cli"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e78fb2c09a81546bcd376d34db4bda5769270d00990daa9f0d6e7ac1107e25"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.0"
|
||||
@@ -4320,6 +4431,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
|
||||
@@ -34,6 +34,9 @@ windows = { version = "0.62.2", features = [
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
x11 = { version = "2.21.0", features = ["xlib"] }
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-cli = "2"
|
||||
|
||||
[profile.dev]
|
||||
incremental = true # Compile your binary in smaller steps.
|
||||
|
||||
|
||||
14
src-tauri/capabilities/desktop.json
Normal file
14
src-tauri/capabilities/desktop.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"identifier": "desktop-capability",
|
||||
"platforms": [
|
||||
"macOS",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"cli:default"
|
||||
]
|
||||
}
|
||||
@@ -4,11 +4,6 @@ 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 {
|
||||
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))?;
|
||||
@@ -19,25 +14,35 @@ fn send_key(key: String) -> Result<(), String> {
|
||||
// Check if uppercase letter
|
||||
if ch.is_uppercase() && ch.is_alphabetic() {
|
||||
// Send Shift + lowercase letter
|
||||
enigo.key(Key::Shift, Direction::Press)
|
||||
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)
|
||||
enigo
|
||||
.key(
|
||||
Key::Unicode(ch.to_lowercase().next().unwrap()),
|
||||
Direction::Click,
|
||||
)
|
||||
.map_err(|e| format!("Key error: {}", e))?;
|
||||
enigo.key(Key::Shift, Direction::Release)
|
||||
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)
|
||||
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)
|
||||
"Enter" => enigo
|
||||
.key(Key::Return, Direction::Click)
|
||||
.map_err(|e| format!("Key error: {}", e))?,
|
||||
"Space" => enigo.key(Key::Space, Direction::Click)
|
||||
"Space" => enigo
|
||||
.key(Key::Space, Direction::Click)
|
||||
.map_err(|e| format!("Key error: {}", e))?,
|
||||
"Backspace" => enigo.key(Key::Backspace, Direction::Click)
|
||||
"Backspace" => enigo
|
||||
.key(Key::Backspace, Direction::Click)
|
||||
.map_err(|e| format!("Key error: {}", e))?,
|
||||
_ => return Err("Unknown key".to_string()),
|
||||
}
|
||||
@@ -48,14 +53,16 @@ fn send_key(key: String) -> Result<(), String> {
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_cli::init())
|
||||
.setup(|app| {
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use windows::Win32::UI::WindowsAndMessaging::{SetWindowPos, HWND_TOPMOST, SWP_NOACTIVATE, GetWindowLongPtrW, SetWindowLongPtrW, GWL_EXSTYLE, WS_EX_NOACTIVATE};
|
||||
use windows::Win32::Foundation::HWND;
|
||||
#![allow(deprecated)]
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use windows::Win32::Foundation::HWND;
|
||||
use windows::Win32::UI::WindowsAndMessaging::{GetWindowLongPtrW, SetWindowLongPtrW, GWL_EXSTYLE, WS_EX_NOACTIVATE,};
|
||||
|
||||
unsafe {
|
||||
if let Ok(RawWindowHandle::Win32(handle)) = window.raw_window_handle() {
|
||||
@@ -82,8 +89,8 @@ pub fn run() {
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use x11::xlib;
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use x11::xlib;
|
||||
|
||||
unsafe {
|
||||
let handle = window.raw_window_handle();
|
||||
@@ -106,7 +113,7 @@ pub fn run() {
|
||||
Ok(())
|
||||
})
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet, send_key])
|
||||
.invoke_handler(tauri::generate_handler![send_key])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -26,6 +26,29 @@
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"cli":{
|
||||
"description": "On-Screen keyboard for Human-Computer Interface studies",
|
||||
"args": [
|
||||
{
|
||||
"name": "order",
|
||||
"short": "o",
|
||||
"takesValue": true,
|
||||
"multiple": true
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"short": "l",
|
||||
"takesValue": true
|
||||
},
|
||||
{
|
||||
"name": "demo",
|
||||
"short": "d",
|
||||
"takesValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
|
||||
@@ -1,35 +1,28 @@
|
||||
<main class="container">
|
||||
<form class="row" (submit)="greet($event, greetInput.value)">
|
||||
<input #greetInput id="greet-input" placeholder="Enter a name..." />
|
||||
<button type="submit">Greet</button>
|
||||
</form>
|
||||
|
||||
<p>{{ greetingMessage }}</p>
|
||||
|
||||
<!-- Layout Switcher -->
|
||||
<div class="layout-controls">
|
||||
<button (click)="switchLayout()" class="layout-button">
|
||||
Switch Layout: {{ currentLayout.toUpperCase() }}
|
||||
Switch Layout: {{ currentLayout.toString() }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Rendering basierend auf currentLayout -->
|
||||
<app-qwerty-keyboard
|
||||
*ngIf="currentLayout === 'qwerty'"
|
||||
*ngIf="currentLayout === Keyboards.QWERTY"
|
||||
[shiftActive]="shiftActive"
|
||||
(keyPressed)="handleKeyPress($event)"
|
||||
(shiftToggled)="toggleShift()">
|
||||
</app-qwerty-keyboard>
|
||||
|
||||
<app-dvorak-keyboard
|
||||
*ngIf="currentLayout === 'dvorak'"
|
||||
*ngIf="currentLayout === Keyboards.DVORAK"
|
||||
[shiftActive]="shiftActive"
|
||||
(keyPressed)="handleKeyPress($event)"
|
||||
(shiftToggled)="toggleShift()">
|
||||
</app-dvorak-keyboard>
|
||||
|
||||
<app-circle-keyboard
|
||||
*ngIf="currentLayout === 'circle'"
|
||||
*ngIf="currentLayout === Keyboards.CIRCLE"
|
||||
[shiftActive]="shiftActive"
|
||||
(keyPressed)="handleKeyPress($event)"
|
||||
(shiftToggled)="toggleShift()">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { getMatches } from '@tauri-apps/plugin-cli';
|
||||
import { QwertyKeyboardComponent } from './keyboards/qwerty-keyboard.component';
|
||||
import { DvorakKeyboardComponent } from './keyboards/dvorak-keyboard.component';
|
||||
import { CircleKeyboardComponent } from './keyboards/circle-keyboard.component';
|
||||
@@ -17,42 +18,34 @@ import { CircleKeyboardComponent } from './keyboards/circle-keyboard.component';
|
||||
CircleKeyboardComponent
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css'
|
||||
styleUrl: './app.component.css',
|
||||
})
|
||||
export class AppComponent implements AfterViewInit {
|
||||
greetingMessage = "";
|
||||
currentLayout: 'qwerty' | 'dvorak' | 'circle' = 'qwerty';
|
||||
export class AppComponent implements OnInit {
|
||||
Keyboards = Keyboards;
|
||||
currentLayout: Keyboards = Keyboards.QWERTY;
|
||||
shiftActive = false;
|
||||
async ngOnInit() {
|
||||
|
||||
const cli = await getMatches();
|
||||
}
|
||||
|
||||
@ViewChild('greetInput') inputElement!: ElementRef;
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.inputElement.nativeElement.focus();
|
||||
}
|
||||
|
||||
greet(event: SubmitEvent, name: string): void {
|
||||
event.preventDefault();
|
||||
invoke<string>("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';
|
||||
if (this.currentLayout === Keyboards.QWERTY){
|
||||
this.currentLayout = Keyboards.DVORAK;
|
||||
} else if (this.currentLayout === Keyboards.DVORAK){
|
||||
this.currentLayout = Keyboards.CIRCLE;
|
||||
} else if (this.currentLayout === Keyboards.CIRCLE){
|
||||
this.currentLayout = Keyboards.QWERTY;
|
||||
}
|
||||
}
|
||||
|
||||
async handleKeyPress(key: string): Promise<void> {
|
||||
this.inputElement.nativeElement.focus();
|
||||
|
||||
let finalKey = key;
|
||||
if (key.length === 1) {
|
||||
@@ -66,3 +59,8 @@ export class AppComponent implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
enum Keyboards{
|
||||
QWERTY,
|
||||
DVORAK,
|
||||
CIRCLE
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user