FreeRDP
Loading...
Searching...
No Matches
TouchPointerView.java
1/*
2 Android Touch Pointer view
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9*/
10
11package com.freerdp.freerdpcore.presentation;
12
13import android.content.Context;
14import android.graphics.Matrix;
15import android.graphics.RectF;
16import android.os.Handler;
17import android.os.Looper;
18import android.util.AttributeSet;
19import android.view.MotionEvent;
20import android.widget.ImageView;
21
22import com.freerdp.freerdpcore.R;
23import com.freerdp.freerdpcore.utils.GestureDetector;
24
25public class TouchPointerView extends ImageView
26{
27
28 private static final int POINTER_ACTION_CURSOR = 0;
29 private static final int POINTER_ACTION_CLOSE = 3;
30
31 // the touch pointer consists of 9 quadrants with the following functionality:
32 //
33 // -------------
34 // | 0 | 1 | 2 |
35 // -------------
36 // | 3 | 4 | 5 |
37 // -------------
38 // | 6 | 7 | 8 |
39 // -------------
40 //
41 // 0 ... contains the actual pointer (the tip must be centered in the quadrant)
42 // 1 ... is left empty
43 // 2, 3, 5, 6, 7, 8 ... function quadrants that issue a callback
44 // 4 ... pointer center used for left clicks and to drag the pointer
45 private static final int POINTER_ACTION_RCLICK = 2;
46 private static final int POINTER_ACTION_LCLICK = 4;
47 private static final int POINTER_ACTION_MOVE = 4;
48 private static final int POINTER_ACTION_SCROLL = 5;
49 private static final int POINTER_ACTION_RESET = 6;
50 private static final int POINTER_ACTION_KEYBOARD = 7;
51 private static final int POINTER_ACTION_EXTKEYBOARD = 8;
52 private static final float SCROLL_DELTA = 10.0f;
53 private static final int DEFAULT_TOUCH_POINTER_RESTORE_DELAY = 150;
54 private RectF pointerRect;
55 private final RectF[] pointerAreaRects = new RectF[9];
56 private Matrix translationMatrix;
57 private boolean pointerMoving = false;
58 private boolean pointerScrolling = false;
59 private TouchPointerListener listener = null;
60 private final Handler uiHandler = new Handler(Looper.getMainLooper());
61 private final Runnable restorePointerImage =
62 () -> setPointerImage(R.drawable.touch_pointer_default);
63 // gesture detection
64 private GestureDetector gestureDetector;
65 public TouchPointerView(Context context)
66 {
67 super(context);
68 initTouchPointer(context);
69 }
70
71 public TouchPointerView(Context context, AttributeSet attrs)
72 {
73 super(context, attrs);
74 initTouchPointer(context);
75 }
76
77 public TouchPointerView(Context context, AttributeSet attrs, int defStyle)
78 {
79 super(context, attrs, defStyle);
80 initTouchPointer(context);
81 }
82
83 private void initTouchPointer(Context context)
84 {
85 gestureDetector =
86 new GestureDetector(context, new TouchPointerGestureListener(), null, true);
87 gestureDetector.setLongPressTimeout(500);
88 translationMatrix = new Matrix();
89 setScaleType(ScaleType.MATRIX);
90 setImageMatrix(translationMatrix);
91
92 // init rects
93 final float rectSizeWidth = (float)getDrawable().getIntrinsicWidth() / 3.0f;
94 final float rectSizeHeight = (float)getDrawable().getIntrinsicWidth() / 3.0f;
95 for (int i = 0; i < 3; i++)
96 {
97 for (int j = 0; j < 3; j++)
98 {
99 int left = (int)(j * rectSizeWidth);
100 int top = (int)(i * rectSizeHeight);
101 int right = left + (int)rectSizeWidth;
102 int bottom = top + (int)rectSizeHeight;
103 pointerAreaRects[i * 3 + j] = new RectF(left, top, right, bottom);
104 }
105 }
106 pointerRect =
107 new RectF(0, 0, getDrawable().getIntrinsicWidth(), getDrawable().getIntrinsicHeight());
108 }
109
110 public void setTouchPointerListener(TouchPointerListener listener)
111 {
112 this.listener = listener;
113 }
114
115 public int getPointerWidth()
116 {
117 return getDrawable().getIntrinsicWidth();
118 }
119
120 public int getPointerHeight()
121 {
122 return getDrawable().getIntrinsicHeight();
123 }
124
125 public float[] getPointerPosition()
126 {
127 float[] curPos = new float[2];
128 translationMatrix.mapPoints(curPos);
129 return curPos;
130 }
131
132 private void movePointer(float deltaX, float deltaY)
133 {
134 translationMatrix.postTranslate(deltaX, deltaY);
135 setImageMatrix(translationMatrix);
136 }
137
138 private void ensureVisibility(int screen_width, int screen_height)
139 {
140 float[] curPos = new float[2];
141 translationMatrix.mapPoints(curPos);
142
143 if (curPos[0] > (screen_width - pointerRect.width()))
144 curPos[0] = screen_width - pointerRect.width();
145 if (curPos[0] < 0)
146 curPos[0] = 0;
147 if (curPos[1] > (screen_height - pointerRect.height()))
148 curPos[1] = screen_height - pointerRect.height();
149 if (curPos[1] < 0)
150 curPos[1] = 0;
151
152 translationMatrix.setTranslate(curPos[0], curPos[1]);
153 setImageMatrix(translationMatrix);
154 }
155
156 private void displayPointerImageAction(int resId)
157 {
158 setPointerImage(resId);
159 uiHandler.removeCallbacks(restorePointerImage);
160 uiHandler.postDelayed(restorePointerImage, DEFAULT_TOUCH_POINTER_RESTORE_DELAY);
161 }
162
163 private void setPointerImage(int resId)
164 {
165 setImageResource(resId);
166 }
167
168 // returns the pointer area with the current translation matrix applied
169 private RectF getCurrentPointerArea(int area)
170 {
171 RectF transRect = new RectF(pointerAreaRects[area]);
172 translationMatrix.mapRect(transRect);
173 return transRect;
174 }
175
176 private boolean pointerAreaTouched(MotionEvent event, int area)
177 {
178 RectF transRect = new RectF(pointerAreaRects[area]);
179 translationMatrix.mapRect(transRect);
180 return transRect.contains(event.getX(), event.getY());
181 }
182
183 private boolean pointerTouched(MotionEvent event)
184 {
185 RectF transRect = new RectF(pointerRect);
186 translationMatrix.mapRect(transRect);
187 return transRect.contains(event.getX(), event.getY());
188 }
189
190 @Override public boolean onTouchEvent(MotionEvent event)
191 {
192 // check if pointer is being moved or if we are in scroll mode or if the pointer is touched
193 if (!pointerMoving && !pointerScrolling && !pointerTouched(event))
194 return false;
195 return gestureDetector.onTouchEvent(event);
196 }
197
198 @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom)
199 {
200 // ensure touch pointer is visible
201 if (changed)
202 ensureVisibility(right - left, bottom - top);
203 }
204
205 // touch pointer listener - is triggered if an action field is
206 public interface TouchPointerListener {
207 void onTouchPointerClose();
208
209 void onTouchPointerLeftClick(int x, int y, boolean down);
210
211 void onTouchPointerRightClick(int x, int y, boolean down);
212
213 void onTouchPointerMove(int x, int y);
214
215 void onTouchPointerScroll(boolean down);
216
217 void onTouchPointerToggleKeyboard();
218
219 void onTouchPointerToggleExtKeyboard();
220
221 void onTouchPointerResetScrollZoom();
222 }
223
224 private class TouchPointerGestureListener extends GestureDetector.SimpleOnGestureListener
225 {
226
227 private MotionEvent prevEvent = null;
228
229 public boolean onDown(MotionEvent e)
230 {
231 if (pointerAreaTouched(e, POINTER_ACTION_MOVE))
232 {
233 prevEvent = MotionEvent.obtain(e);
234 pointerMoving = true;
235 }
236 else if (pointerAreaTouched(e, POINTER_ACTION_SCROLL))
237 {
238 prevEvent = MotionEvent.obtain(e);
239 pointerScrolling = true;
240 setPointerImage(R.drawable.touch_pointer_scroll);
241 }
242
243 return true;
244 }
245
246 public boolean onUp(MotionEvent e)
247 {
248 if (prevEvent != null)
249 {
250 prevEvent.recycle();
251 prevEvent = null;
252 }
253
254 if (pointerScrolling)
255 setPointerImage(R.drawable.touch_pointer_default);
256
257 pointerMoving = false;
258 pointerScrolling = false;
259 return true;
260 }
261
262 public void onLongPress(MotionEvent e)
263 {
264 if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
265 {
266 setPointerImage(R.drawable.touch_pointer_active);
267 pointerMoving = true;
268 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
269 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
270 }
271 }
272
273 public void onLongPressUp(MotionEvent e)
274 {
275 if (pointerMoving)
276 {
277 setPointerImage(R.drawable.touch_pointer_default);
278 pointerMoving = false;
279 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
280 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
281 }
282 }
283
284 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
285 {
286 if (pointerMoving)
287 {
288 // move pointer graphics
289 movePointer((int)(e2.getX() - prevEvent.getX()),
290 (int)(e2.getY() - prevEvent.getY()));
291 prevEvent.recycle();
292 prevEvent = MotionEvent.obtain(e2);
293
294 // send move notification
295 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
296 listener.onTouchPointerMove((int)rect.centerX(), (int)rect.centerY());
297 return true;
298 }
299 else if (pointerScrolling)
300 {
301 // calc if user scrolled up or down (or if any scrolling happened at all)
302 float deltaY = e2.getY() - prevEvent.getY();
303 if (deltaY > SCROLL_DELTA)
304 {
305 listener.onTouchPointerScroll(true);
306 prevEvent.recycle();
307 prevEvent = MotionEvent.obtain(e2);
308 }
309 else if (deltaY < -SCROLL_DELTA)
310 {
311 listener.onTouchPointerScroll(false);
312 prevEvent.recycle();
313 prevEvent = MotionEvent.obtain(e2);
314 }
315 return true;
316 }
317 return false;
318 }
319
320 public boolean onSingleTapUp(MotionEvent e)
321 {
322 // look what area got touched and fire actions accordingly
323 if (pointerAreaTouched(e, POINTER_ACTION_CLOSE))
324 listener.onTouchPointerClose();
325 else if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
326 {
327 displayPointerImageAction(R.drawable.touch_pointer_lclick);
328 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
329 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
330 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
331 }
332 else if (pointerAreaTouched(e, POINTER_ACTION_RCLICK))
333 {
334 displayPointerImageAction(R.drawable.touch_pointer_rclick);
335 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
336 listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), true);
337 listener.onTouchPointerRightClick((int)rect.centerX(), (int)rect.centerY(), false);
338 }
339 else if (pointerAreaTouched(e, POINTER_ACTION_KEYBOARD))
340 {
341 displayPointerImageAction(R.drawable.touch_pointer_keyboard);
342 listener.onTouchPointerToggleKeyboard();
343 }
344 else if (pointerAreaTouched(e, POINTER_ACTION_EXTKEYBOARD))
345 {
346 displayPointerImageAction(R.drawable.touch_pointer_extkeyboard);
347 listener.onTouchPointerToggleExtKeyboard();
348 }
349 else if (pointerAreaTouched(e, POINTER_ACTION_RESET))
350 {
351 displayPointerImageAction(R.drawable.touch_pointer_reset);
352 listener.onTouchPointerResetScrollZoom();
353 }
354
355 return true;
356 }
357
358 public boolean onDoubleTap(MotionEvent e)
359 {
360 // issue a double click notification if performed in center quadrant
361 if (pointerAreaTouched(e, POINTER_ACTION_LCLICK))
362 {
363 RectF rect = getCurrentPointerArea(POINTER_ACTION_CURSOR);
364 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), true);
365 listener.onTouchPointerLeftClick((int)rect.centerX(), (int)rect.centerY(), false);
366 }
367 return true;
368 }
369 }
370}