text: #ifndef USE_SDL1
  #include <cmath>
  
  #include "options.h"
  #include "touch.h"
  #include "utils/display.h"
  #include "utils/ui_fwd.h"
  
  static int visible_width;
  static int visible_height;
  static int x_borderwidth;
  static int y_borderwidth;
  
  template <typename T>
  inline T clip(T v, T amin, T amax)
  {
  	if (v < amin)
  		return amin;
  	if (v > amax)
  		return amax;
  	else
  		return v;
  }
  
  #define TOUCH_PORT_MAX_NUM 1
  #define NO_TOUCH (-1) // finger id setting if finger is not touching the screen
  
  static void InitTouch();
  static void PreprocessEvents(SDL_Event *event);
  static void PreprocessFingerDown(SDL_Event *event);
  static void PreprocessFingerUp(SDL_Event *event);
  #ifdef __vita__
  static void preprocess_back_finger_down(SDL_Event *event);
  static void preprocess_back_finger_up(SDL_Event *event);
  #endif
  static void PreprocessFingerMotion(SDL_Event *event);
  static void SetMouseButtonEvent(SDL_Event *event, uint32_t type, uint8_t button, int32_t x, int32_t y);
  static void SetMouseMotionEvent(SDL_Event *event, int32_t x, int32_t y, int32_t xrel, int32_t yrel);
  
  static bool touch_initialized = false;
  static unsigned int simulated_click_start_time[TOUCH_PORT_MAX_NUM][2]; // initiation time of last simulated left or right click (zero if no click)
  static bool direct_touch = true;                                       // pointer jumps to finger
  static int mouse_x = 0;                                                // always reflects current mouse position
  static int mouse_y = 0;
  
  enum {
  	// clang-format off
  	MaxNumFingers          =   3, // number of fingers to track per panel
  	MaxTapTime             = 250, // taps longer than this will not result in mouse click events
  	MaxTapMotionDistance   =  10, // max distance finger motion in Vita screen pixels to be considered a tap
  	SimulatedClickDuration =  50, // time in ms how long simulated mouse clicks should be
  	// clang-format on
  };
  
  struct Touch {
  	int id; // -1: not touching
  	uint32_t time_last_down;
  	int last_x;        // last known screen coordinates
  	int last_y;        // last known screen coordinates
  	float last_down_x; // SDL touch coordinates when last pressed down
  	float last_down_y; // SDL touch coordinates when last pressed down
  };
  
  static Touch finger[TOUCH_PORT_MAX_NUM][MaxNumFingers]; // keep track of finger status
  
  enum DraggingType {
  	DragNone,
  	DragTwoFinger,
  	DragThreeFinger,
  };
  
  static DraggingType multi_finger_dragging[TOUCH_PORT_MAX_NUM]; // keep track whether we are currently drag-and-dropping
  
  static void InitTouch()
  {
  	for (int port = 0; port < TOUCH_PORT_MAX_NUM; port++) {
  		for (int i = 0; i < MaxNumFingers; i++) {
  			finger[port][i].id = NO_TOUCH;
  		}
  		multi_finger_dragging[port] = DragNone;
  	}
  
  	for (auto &port : simulated_click_start_time) {
  		for (unsigned int &time : port) {
  			time = 0;
  		}
  	}
  
  	SDL_DisplayMode current;
  	SDL_GetCurrentDisplayMode(0, &current);
  	visible_height = current.h;
  	visible_width = (current.h * devilution::gnScreenWidth) / devilution::gnScreenHeight;
  	x_borderwidth = (current.w - visible_width) / 2;
  	y_borderwidth = (current.h - visible_height) / 2;
  }
  
  static void PreprocessEvents(SDL_Event *event)
  {
  	// Supported touch gestures:
  	// left mouse click: single finger short tap
  	// right mouse click: second finger short tap while first finger is still down
  	// pointer motion: single finger drag
  	// left button drag and drop: dual finger drag
  	// right button drag and drop: triple finger drag
  	if (event->type != SDL_FINGERDOWN && event->type != SDL_FINGERUP && event->type != SDL_FINGERMOTION) {
  		return;
  	}
  
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  	if (port != 0) {
  #ifdef __vita__
  		if (devilution::sgOptions.Controller.bRearTouch) {
  			switch (event->type) {
  			case SDL_FINGERDOWN:
  				preprocess_back_finger_down(event);
  				break;
  			case SDL_FINGERUP:
  				preprocess_back_finger_up(event);
  				break;
  			}
  		}
  #endif
  		return;
  	}
  
  	switch (event->type) {
  	case SDL_FINGERDOWN:
  		PreprocessFingerDown(event);
  		break;
  	case SDL_FINGERUP:
  		PreprocessFingerUp(event);
  		break;
  	case SDL_FINGERMOTION:
  		PreprocessFingerMotion(event);
  		break;
  	}
  }
  
  static void PreprocessFingerDown(SDL_Event *event)
  {
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  	// id (for multitouch)
  	SDL_FingerID id = event->tfinger.fingerId;
  
  	int x = mouse_x;
  	int y = mouse_y;
  
  	if (direct_touch) {
  		x = event->tfinger.x * visible_width + x_borderwidth;
  		y = event->tfinger.y * visible_height + y_borderwidth;
  		devilution::OutputToLogical(&x, &y);
  	}
  
  	// make sure each finger is not reported down multiple times
  	for (int i = 0; i < MaxNumFingers; i++) {
  		if (finger[port][i].id != id) {
  			continue;
  		}
  		finger[port][i].id = NO_TOUCH;
  	}
  
  	// we need the timestamps to decide later if the user performed a short tap (click)
  	// or a long tap (drag)
  	// we also need the last coordinates for each finger to keep track of dragging
  	for (int i = 0; i < MaxNumFingers; i++) {
  		if (finger[port][i].id != NO_TOUCH) {
  			continue;
  		}
  		finger[port][i].id = id;
  		finger[port][i].time_last_down = event->tfinger.timestamp;
  		finger[port][i].last_down_x = event->tfinger.x;
  		finger[port][i].last_down_y = event->tfinger.y;
  		finger[port][i].last_x = x;
  		finger[port][i].last_y = y;
  		break;
  	}
  }
  #ifdef __vita__
  static void preprocess_back_finger_down(SDL_Event *event)
  {
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  
  	if (port != 1)
  		return;
  
  	event->type = SDL_CONTROLLERAXISMOTION;
  	event->caxis.value = 32767;
  	event->caxis.which = 0;
  	if (event->tfinger.x <= 0.5) {
  		;
  		event->caxis.axis = SDL_CONTROLLER_AXIS_TRIGGERLEFT;
  	} else {
  		event->caxis.axis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
  	}
  }
  
  static void preprocess_back_finger_up(SDL_Event *event)
  {
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  
  	if (port != 1)
  		return;
  
  	event->type = SDL_CONTROLLERAXISMOTION;
  	event->caxis.value = 0;
  	event->caxis.which = 0;
  	if (event->tfinger.x <= 0.5) {
  		;
  		event->caxis.axis = SDL_CONTROLLER_AXIS_TRIGGERLEFT;
  	} else {
  		event->caxis.axis = SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
  	}
  }
  #endif
  
  static void PreprocessFingerUp(SDL_Event *event)
  {
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  	// id (for multitouch)
  	SDL_FingerID id = event->tfinger.fingerId;
  
  	// find out how many fingers were down before this event
  	int numFingersDown = 0;
  	for (int i = 0; i < MaxNumFingers; i++) {
  		if (finger[port][i].id >= 0) {
  			numFingersDown++;
  		}
  	}
  
  	int x = mouse_x;
  	int y = mouse_y;
  
  	for (int i = 0; i < MaxNumFingers; i++) {
  		if (finger[port][i].id != id) {
  			continue;
  		}
  
  		finger[port][i].id = NO_TOUCH;
  		if (multi_finger_dragging[port] == DragNone) {
  			if ((event->tfinger.timestamp - finger[port][i].time_last_down) > MaxTapTime) {
  				continue;
  			}
  
  			// short (<MAX_TAP_TIME ms) tap is interpreted as right/left mouse click depending on # fingers already down
  			// but only if the finger hasn't moved since it was pressed down by more than MAX_TAP_MOTION_DISTANCE pixels
  			float xrel = ((event->tfinger.x * devilution::GetOutputSurface()->w) - (finger[port][i].last_down_x * devilution::GetOutputSurface()->w));
  			float yrel = ((event->tfinger.y * devilution::GetOutputSurface()->h) - (finger[port][i].last_down_y * devilution::GetOutputSurface()->h));
  			auto maxRSquared = static_cast<float>(MaxTapMotionDistance * MaxTapMotionDistance);
  			if ((xrel * xrel + yrel * yrel) >= maxRSquared) {
  				continue;
  			}
  
  			if (numFingersDown != 2 && numFingersDown != 1) {
  				continue;
  			}
  
  			Uint8 simulatedButton = 0;
  			if (numFingersDown == 2) {
  				simulatedButton = SDL_BUTTON_RIGHT;
  				// need to raise the button later
  				simulated_click_start_time[port][1] = event->tfinger.timestamp;
  			} else if (numFingersDown == 1) {
  				simulatedButton = SDL_BUTTON_LEFT;
  				// need to raise the button later
  				simulated_click_start_time[port][0] = event->tfinger.timestamp;
  				if (direct_touch) {
  					x = event->tfinger.x * visible_width + x_borderwidth;
  					y = event->tfinger.y * visible_height + y_borderwidth;
  					devilution::OutputToLogical(&x, &y);
  				}
  			}
  			SetMouseButtonEvent(event, SDL_MOUSEBUTTONDOWN, simulatedButton, x, y);
  		} else if (numFingersDown == 1) {
  			// when dragging, and the last finger is lifted, the drag is over
  			Uint8 simulatedButton = 0;
  			if (multi_finger_dragging[port] == DragThreeFinger) {
  				simulatedButton = SDL_BUTTON_RIGHT;
  			} else {
  				simulatedButton = SDL_BUTTON_LEFT;
  			}
  			SetMouseButtonEvent(event, SDL_MOUSEBUTTONUP, simulatedButton, x, y);
  			multi_finger_dragging[port] = DragNone;
  		}
  	}
  }
  
  static void PreprocessFingerMotion(SDL_Event *event)
  {
  	// front (0) or back (1) panel
  	SDL_TouchID port = event->tfinger.touchId;
  	// id (for multitouch)
  	SDL_FingerID id = event->tfinger.fingerId;
  
  	// find out how many fingers were down before this event
  	int numFingersDown = 0;
  	for (int i = 0; i < MaxNumFingers; i++) {
  		if (finger[port][i].id >= 0) {
  			numFingersDown++;
  		}
  	}
  
  	if (numFingersDown == 0) {
  		return;
  	}
  
  	if (numFingersDown >= 1) {
  		int x = mouse_x;
  		int y = mouse_y;
  		int xrel = 0;
  		int yrel = 0;
  
  		if (direct_touch) {
  			x = event->tfinger.x * visible_width + x_borderwidth;
  			y = event->tfinger.y * visible_height + y_borderwidth;
  			devilution::OutputToLogical(&x, &y);
  		} else {
  			// for relative mode, use the pointer speed setting
  			float speedFactor = 1.0;
  
  			// convert touch events to relative mouse pointer events
  			// Whenever an SDL_event involving the mouse is processed,
  			x = (mouse_x + (event->tfinger.dx * 1.25 * speedFactor * devilution::GetOutputSurface()->w));
  			y = (mouse_y + (event->tfinger.dy * 1.25 * speedFactor * devilution::GetOutputSurface()->h));
  		}
  
  		x = clip(x, 0, devilution::GetOutputSurface()->w);
  		y = clip(y, 0, devilution::GetOutputSurface()->h);
  		xrel = x - mouse_x;
  		yrel = y - mouse_y;
  
  		// update the current finger's coordinates so we can track it later
  		for (int i = 0; i < MaxNumFingers; i++) {
  			if (finger[port][i].id != id)
  				continue;
  			finger[port][i].last_x = x;
  			finger[port][i].last_y = y;
  		}
  
  		// If we are starting a multi-finger drag, start holding down the mouse button
  		if (numFingersDown >= 2 && multi_finger_dragging[port] == DragNone) {
  			// only start a multi-finger drag if at least two fingers have been down long enough
  			int numFingersDownlong = 0;
  			for (int i = 0; i < MaxNumFingers; i++) {
  				if (finger[port][i].id == NO_TOUCH) {
  					continue;
  				}
  				if (event->tfinger.timestamp - finger[port][i].time_last_down > MaxTapTime) {
  					numFingersDownlong++;
  				}
  			}
  			if (numFingersDownlong >= 2) {
  				int mouseDownX = mouse_x;
  				int mouseDownY = mouse_y;
  				if (direct_touch) {
  					for (int i = 0; i < MaxNumFingers; i++) {
  						if (finger[port][i].id == id) {
  							uint32_t earliestTime = finger[port][i].time_last_down;
  							for (int j = 0; j < MaxNumFingers; j++) {
  								if (finger[port][j].id >= 0 && (i != j)) {
  									if (finger[port][j].time_last_down < earliestTime) {
  										mouseDownX = finger[port][j].last_x;
  										mouseDownY = finger[port][j].last_y;
  										earliestTime = finger[port][j].time_last_down;
  									}
  								}
  							}
  							break;
  						}
  					}
  				}
  
  				Uint8 simulatedButton = 0;
  				if (numFingersDownlong == 2) {
  					simulatedButton = SDL_BUTTON_LEFT;
  					multi_finger_dragging[port] = DragTwoFinger;
  				} else {
  					simulatedButton = SDL_BUTTON_RIGHT;
  					multi_finger_dragging[port] = DragThreeFinger;
  				}
  				SDL_Event ev;
  				SetMouseButtonEvent(&ev, SDL_MOUSEBUTTONDOWN, simulatedButton, mouseDownX, mouseDownY);
  				SDL_PushEvent(&ev);
  			}
  		}
  
  		if (xrel == 0 && yrel == 0) {
  			return;
  		}
  
  		// check if this is the "oldest" finger down (or the only finger down)
  		// otherwise it will not affect mouse motion
  		bool updatePointer = true;
  		if (numFingersDown > 1) {
  			for (int i = 0; i < MaxNumFingers; i++) {
  				if (finger[port][i].id != id) {
  					continue;
  				}
  				for (int j = 0; j < MaxNumFingers; j++) {
  					if (finger[port][j].id == NO_TOUCH || (j == i)) {
  						continue;
  					}
  					if (finger[port][j].time_last_down < finger[port][i].time_last_down) {
  						updatePointer = false;
  					}
  				}
  			}
  		}
  		if (!updatePointer) {
  			return;
  		}
  		SetMouseMotionEvent(event, x, y, xrel, yrel);
  	}
  }
  
  namespace devilution {
  
  void handle_touch(SDL_Event *event, int currentMouseX, int currentMouseY)
  {
  	mouse_x = currentMouseX;
  	mouse_y = currentMouseY;
  
  	if (!touch_initialized) {
  		InitTouch();
  		touch_initialized = true;
  	}
  	PreprocessEvents(event);
  	if (event->type == SDL_FINGERDOWN || event->type == SDL_FINGERUP || event->type == SDL_FINGERMOTION) {
  		event->type = SDL_USEREVENT;
  		event->user.code = -1; // ensure this event is ignored;
  	}
  }
  
  void finish_simulated_mouse_clicks(int currentMouseX, int currentMouseY)
  {
  	mouse_x = currentMouseX;
  	mouse_y = currentMouseY;
  
  	for (auto &port : simulated_click_start_time) {
  		for (int i = 0; i < 2; i++) {
  			if (port[i] == 0) {
  				continue;
  			}
  
  			Uint32 currentTime = SDL_GetTicks();
  			if (currentTime - port[i] < SimulatedClickDuration) {
  				continue;
  			}
  
  			int simulatedButton;
  			if (i == 0) {
  				simulatedButton = SDL_BUTTON_LEFT;
  			} else {
  				simulatedButton = SDL_BUTTON_RIGHT;
  			}
  			SDL_Event ev;
  			SetMouseButtonEvent(&ev, SDL_MOUSEBUTTONUP, simulatedButton, mouse_x, mouse_y);
  			SDL_PushEvent(&ev);
  
  			port[i] = 0;
  		}
  	}
  }
  
  } // namespace devilution
  
  static void SetMouseButtonEvent(SDL_Event *event, uint32_t type, uint8_t button, int32_t x, int32_t y)
  {
  	event->type = type;
  	event->button.button = button;
  	if (type == SDL_MOUSEBUTTONDOWN) {
  		event->button.state = SDL_PRESSED;
  	} else {
  		event->button.state = SDL_RELEASED;
  	}
  	event->button.x = x;
  	event->button.y = y;
  }
  
  static void SetMouseMotionEvent(SDL_Event *event, int32_t x, int32_t y, int32_t xrel, int32_t yrel)
  {
  	event->type = SDL_MOUSEMOTION;
  	event->motion.x = x;
  	event->motion.y = y;
  	event->motion.xrel = xrel;
  	event->motion.yrel = yrel;
  }
  #endif
  