1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
function MultiTouchDemo(dev, screenId, verbose)
% MultiTouchDemo([dev][, screenId=max][, verbose=0]) - A advanced demo for multi-touch touchscreens.
%
% Run it. Pressing the ESCape key will stop it.
%
% Touch the screen and watch the nice colorful happy blobs
% sprinkle to life :)
%
% The demo will try to use the first available touchscreen, or if
% there isn't any touchscreen, the first available touchpad. You
% can also select a specific touch device by passing in its 'dev'
% device handle. Use of touchpads usually needs special configuration.
% See "help TouchInput" for more info.
%
% You can select a specific screen to display on - usually the screenId
% of the touch screen display surface - with the optional 'screenId' parameter,
% or it will select the default maximum screenId if omitted.
%
% If you set 'verbose' to 1, then all the available info about each
% touch point will be displayed close to the touch point. As drawing
% so much text is very slow, the demo will only update touchpoints
% very slowly!
%
% The demo not only tracks and display touches and their location, as
% well as their timing. It also uses info about the shape of the contact,
% visualizing this as aspect ratio of the drawn rectangle. It tries to
% get info about the contacts orientation and draws accordingly. It tries
% to get (or derive) info about touch pressure and modulates the brightness
% of the rectangle accordingly. One property not used, but available would
% be distance for fingers or tools hovering over a touch surface, if that
% surface provides that info.
%
% This demo currently works on Linux + X11 display system, not on Linux + Wayland.
% It also works on MS-Windows 10 and later.
%
% For background info on capabilities and setup see "help TouchInput".
%
% History:
% 01-Oct-2017 mk Written.
% 03-Aug-2018 mk Only exit demo on ESC key, not on all keys. Doc fixes.
% 05-Dec-2020 mk Add screenId selection parameter and max screenId default.
% Setup useful PTB defaults:
PsychDefaultSetup(2);
if nargin < 1
dev = [];
end
if nargin < 2 || isempty(screenId)
screenId = max(Screen('Screens'));
end
if nargin < 3 || isempty(verbose)
verbose = 0;
end
% If no user-specified 'dev' was given, try to auto-select:
if isempty(dev)
% Get first touchscreen:
dev = min(GetTouchDeviceIndices([], 1));
end
if isempty(dev)
% Get first touchpad:
dev = min(GetTouchDeviceIndices([], 0));
end
if isempty(dev) || ~ismember(dev, GetTouchDeviceIndices)
fprintf('No touch input device found, or invalid dev given. Bye.\n');
return;
else
fprintf('Touch device properties:\n');
info = GetTouchDeviceInfo(dev);
disp(info);
end
% Open a default onscreen window with black background color and
% 0-1 color range:
[w, rect] = PsychImaging('OpenWindow', screenId, 0)
baseSize = RectWidth(rect) / 20;
% No place for you, little mouse cursor:
HideCursor(w);
% Create a 5 pixel texture, just to define a shape we can easily scale and rotate:
finger = Screen('MakeTexture', w, [1; 0.6; 0.4; 0.4; 0.4]);
try
% Create and start touch queue for window and device:
TouchQueueCreate(w, dev);
TouchQueueStart(dev);
% Wait for the go!
KbReleaseWait;
% blobcol tracks active touch points - and dying ones:
blobcol = {};
blobmin = inf;
buttonstate = 0;
colmap = [ 1, 0, 0; 0, 1, 0; 0, 0, 1; 1, 1, 0; 1, 0, 1; 0, 1, 1; 1, 1, 1];
% Only ESCape allows to exit the demo:
RestrictKeysForKbCheck(KbName('ESCAPE'));
% Main loop: Run until keypress:
while ~KbCheck
% Process all currently pending touch events:
while TouchEventAvail(dev)
% Process next touch event 'evt':
evt = TouchEventGet(dev, w);
% Touch blob id - Unique in the session at least as
% long as the finger stays on the screen:
id = evt.Keycode;
% Keep the id's low, so we have to iterate over less blobcol slots
% to save computation time:
if isinf(blobmin)
blobmin = id - 1;
end
id = id - blobmin;
if evt.Type == 0
% Not a touch point, but a button press or release on a
% physical (or emulated) button associated with the touch device:
buttonstate = evt.Pressed;
continue;
end
if evt.Type == 1
% Not really a touch point, but movement of the
% simulated mouse cursor, driven by the primary
% touch-point:
Screen('DrawDots', w, [evt.MappedX; evt.MappedY], baseSize, [1,1,1], [], 1, 1);
continue;
end
if evt.Type == 2
% New touch point -> New blob!
blobcol{id}.col = colmap(mod(id, 7) + 1, :);
blobcol{id}.mul = 1.0;
blobcol{id}.x = evt.MappedX;
blobcol{id}.y = evt.MappedY;
blobcol{id}.t = evt.Time;
blobcol{id}.dt = 0;
end
if evt.Type == 3
% Moving touch point -> Moving blob!
blobcol{id}.x = evt.MappedX;
blobcol{id}.y = evt.MappedY;
blobcol{id}.dt = ceil((evt.Time - blobcol{id}.t) * 1000);
blobcol{id}.t = evt.Time;
end
if evt.Type == 4
% Touch released - finger taken off the screen -> Dying blob!
blobcol{id}.mul = 0.999;
blobcol{id}.x = evt.MappedX;
blobcol{id}.y = evt.MappedY;
end
if evt.Type == 5
% Lost touch data for some reason:
% Flush screen red for one video refresh cycle.
fprintf('Ooops - Sequence data loss!\n');
Screen('FillRect', w, [1 0 0]);
Screen('Flip', w);
Screen('FillRect', w, 0);
continue;
end
if ismember(evt.Type, [2,3,4])
evt = GetTouchValuators(evt, info);
if isfield(evt, 'TouchMajor') && isfield(evt, 'TouchMinor')
% Shape of primary touch ellipse known:
if evt.TouchMajor > 0 && evt.TouchMinor > 0
aspect = evt.TouchMajor / evt.TouchMinor;
else
aspect = 1;
end
blobcol{id}.rect = [0, 0, baseSize, baseSize * aspect];
if isfield(evt, 'WidthMajor') && isfield(evt, 'WidthMinor')
% Shape of approach ellipse known: Can use this to approximate
% pressure if pressure doesn't get reported:
blobcol{id}.pressure = evt.TouchMajor / evt.WidthMajor;
else
blobcol{id}.pressure = 1;
end
else
% Shape unknown, so assume unit shape:
blobcol{id}.rect = [0 0 baseSize baseSize];
blobcol{id}.pressure = 1;
end
if isfield(evt, 'Orientation')
blobcol{id}.orientation = evt.Orientation;
else
blobcol{id}.orientation = 0;
end
if isfield(evt, 'Pressure')
blobcol{id}.pressure = evt.Pressure;
end
end
if verbose
if IsOctave
blobcol{id}.text = disp(evt);
else
blobcol{id}.text = evalc('disp(evt)');
end
end
end
% Now that all touches for this iteration are processed, repaint screen
% with all live blobs at their new positions, and fade out the dying/orphaned
% blobs:
for i=1:length(blobcol)
if ~isempty(blobcol{i}) && blobcol{i}.mul > 0.1
% Draw it: .mul defines size of the blob:
Screen('DrawTexture', w, finger, [], CenterRectOnPointd(blobcol{i}.rect * blobcol{i}.mul, blobcol{i}.x, blobcol{i}.y), ...
blobcol{i}.orientation, [], [], blobcol{i}.col * blobcol{i}.pressure);
if blobcol{i}.mul < 1
% An orphaned blob with no finger touching anymore, so slowly fade it out:
blobcol{i}.mul = blobcol{i}.mul * 0.95;
else
% An active touch. Print its unique touch id and dT timestamp delta between updates in msecs:
Screen('DrawText', w, num2str(i), blobcol{i}.x, blobcol{i}.y, [1 1 0]);
Screen('DrawText', w, num2str(blobcol{i}.dt), blobcol{i}.x, blobcol{i}.y - 100, [1 1 0]);
if verbose
DrawFormattedText(w, blobcol{i}.text, blobcol{i}.x, blobcol{i}.y + 30, [1 1 0]);
end
end
else
% Below threshold: Kill the blob:
blobcol{i} = [];
end
end
if buttonstate
Screen('FrameRect', w, [1, 1, 0], [], 5);
end
% Done repainting - Show it:
Screen('Flip', w);
% Next touch processing -> redraw -> flip cycle:
end
TouchQueueStop(dev);
TouchQueueRelease(dev);
RestrictKeysForKbCheck([]);
ShowCursor(w);
sca;
catch
TouchQueueRelease(dev);
RestrictKeysForKbCheck([]);
sca;
psychrethrow(psychlasterror);
end
end
|