File: MultiTouchMinimalDemo.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 (218 lines) | stat: -rw-r--r-- 7,098 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
function MultiTouchMinimalDemo(dev, screenId, verbose)
% MultiTouchMinimalDemo([dev][, screenId=max][, verbose=0]) - A basic 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 the optional 'verbose' flag to 1, then the unique touch
% id and time delta in msecs between touch updates for each touchpoint
% will be printed. This slows the processing down somewhat, as 'DrawText'
% is more slow than most other drawing functions.
%
% 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:
% 05-Oct-2017 mk  Written.
% 03-Aug-2018 mk  Only exit demo on ESC key, not on all keys.
% 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 if no touchscreen found:
    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);

  % Get maximum supported dot diameter for smooth dots:
  [~, maxSmoothPointSize] = Screen('DrawDots', w);

  % Select good diameter for touch point blobs, but no more than what 'DrawDots' supports:
  baseSize = min(RectWidth(rect) / 20, maxSmoothPointSize);

  HideCursor(w);

  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 = {};
    buttonstate = 0;
    blobmin = inf;

    % 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 = rand(3, 1);
          blobcol{id}.mul = 1.0;
          blobcol{id}.x = evt.MappedX;
          blobcol{id}.y = evt.MappedY;
          blobcol{id}.t = evt.Time;
          % Track time delta in msecs between touch point updates:
          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! 3rd party interference or overload?\n');
          Screen('FillRect', w, [1 0 0]);
          Screen('Flip', w);
          Screen('FillRect', w, 0);
          continue;
        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('DrawDots', w, [blobcol{i}.x, blobcol{i}.y], blobcol{i}.mul * baseSize, blobcol{i}.col, [], 1, 1);
          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:
            if verbose
              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]);
            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);

      % This little bit here will provoke stimulus onset timing failures on
      % Windows if something is not quite right.
      if verbose == 2 && IsWin
        WaitSecs(0.025);
        [~, ~, ~, visualtimingmaybesane] = GetMouse(w)
      end

      % Next touch processing -> redraw -> flip cycle:
    end

    TouchQueueStop(dev);
    TouchQueueRelease(dev);
    RestrictKeysForKbCheck([]);
    ShowCursor(w);
    sca;
  catch
    TouchQueueRelease(dev);
    RestrictKeysForKbCheck([]);
    sca;
    psychrethrow(psychlasterror);
  end
end