File: MultiTouchDemo.m

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (266 lines) | stat: -rw-r--r-- 8,916 bytes parent folder | download
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