/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "MouseStat.h" #include "ServiceBroker.h" #include "input/Key.h" #include "utils/TimeUtils.h" #include "windowing/WinSystem.h" #include #include CMouseStat::CMouseStat() { SetEnabled(); m_Key = KEY_MOUSE_NOOP; } CMouseStat::~CMouseStat() = default; void CMouseStat::Initialize() { // Set the default resolution (PAL) SetResolution(720, 576, 1, 1); } void CMouseStat::HandleEvent(const XBMC_Event& newEvent) { // Save the mouse position and the size of the last move int dx, dy; if (newEvent.type == XBMC_MOUSEMOTION) { dx = newEvent.motion.x - m_mouseState.x; dy = newEvent.motion.y - m_mouseState.y; } else if (newEvent.type == XBMC_MOUSEBUTTONDOWN || newEvent.type == XBMC_MOUSEBUTTONUP) { dx = newEvent.button.x - m_mouseState.x; dy = newEvent.button.y - m_mouseState.y; } else { return; } m_mouseState.dx = dx; m_mouseState.dy = dy; m_mouseState.x = std::max(0, std::min(m_maxX, m_mouseState.x + dx)); m_mouseState.y = std::max(0, std::min(m_maxY, m_mouseState.y + dy)); // Fill in the public members if (newEvent.type == XBMC_MOUSEBUTTONDOWN) { if (newEvent.button.button == XBMC_BUTTON_LEFT) m_mouseState.button[MOUSE_LEFT_BUTTON] = true; if (newEvent.button.button == XBMC_BUTTON_RIGHT) m_mouseState.button[MOUSE_RIGHT_BUTTON] = true; if (newEvent.button.button == XBMC_BUTTON_MIDDLE) m_mouseState.button[MOUSE_MIDDLE_BUTTON] = true; if (newEvent.button.button == XBMC_BUTTON_X1) m_mouseState.button[MOUSE_EXTRA_BUTTON1] = true; if (newEvent.button.button == XBMC_BUTTON_X2) m_mouseState.button[MOUSE_EXTRA_BUTTON2] = true; if (newEvent.button.button == XBMC_BUTTON_X3) m_mouseState.button[MOUSE_EXTRA_BUTTON3] = true; if (newEvent.button.button == XBMC_BUTTON_X4) m_mouseState.button[MOUSE_EXTRA_BUTTON4] = true; if (newEvent.button.button == XBMC_BUTTON_WHEELUP) m_mouseState.dz = 1; if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) m_mouseState.dz = -1; } else if (newEvent.type == XBMC_MOUSEBUTTONUP) { if (newEvent.button.button == XBMC_BUTTON_LEFT) m_mouseState.button[MOUSE_LEFT_BUTTON] = false; if (newEvent.button.button == XBMC_BUTTON_RIGHT) m_mouseState.button[MOUSE_RIGHT_BUTTON] = false; if (newEvent.button.button == XBMC_BUTTON_MIDDLE) m_mouseState.button[MOUSE_MIDDLE_BUTTON] = false; if (newEvent.button.button == XBMC_BUTTON_X1) m_mouseState.button[MOUSE_EXTRA_BUTTON1] = false; if (newEvent.button.button == XBMC_BUTTON_X2) m_mouseState.button[MOUSE_EXTRA_BUTTON2] = false; if (newEvent.button.button == XBMC_BUTTON_X3) m_mouseState.button[MOUSE_EXTRA_BUTTON3] = false; if (newEvent.button.button == XBMC_BUTTON_X4) m_mouseState.button[MOUSE_EXTRA_BUTTON4] = false; if (newEvent.button.button == XBMC_BUTTON_WHEELUP) m_mouseState.dz = 0; if (newEvent.button.button == XBMC_BUTTON_WHEELDOWN) m_mouseState.dz = 0; } // Now check the current message and the previous state to find out if // this is a click, doubleclick, drag etc uint32_t now = CTimeUtils::GetFrameTime(); bool bNothingDown = true; for (int i = 0; i < MOUSE_MAX_BUTTON; i++) { bClick[i] = false; bLongClick[i] = false; bDoubleClick[i] = false; m_hold[i] = HoldAction::NONE; // CButtonState::Update does the hard work of checking the button state // and spotting drags, doubleclicks etc CButtonState::BUTTON_ACTION action = m_buttonState[i].Update(now, m_mouseState.x, m_mouseState.y, m_mouseState.button[i]); switch (action) { case CButtonState::MB_SHORT_CLICK: bClick[i] = true; bNothingDown = false; break; case CButtonState::MB_LONG_CLICK: bLongClick[i] = true; bNothingDown = false; break; case CButtonState::MB_DOUBLE_CLICK: bDoubleClick[i] = true; bNothingDown = false; break; case CButtonState::MB_DRAG_START: m_hold[i] = HoldAction::DRAG_START; bNothingDown = false; break; case CButtonState::MB_DRAG: m_hold[i] = HoldAction::DRAG; bNothingDown = false; break; case CButtonState::MB_DRAG_END: m_hold[i] = HoldAction::DRAG_END; bNothingDown = false; break; default: break; } } // Now work out what action ID to send to XBMC. // ignore any mouse messages by default m_Key = KEY_MOUSE_NOOP; for (int button = 0; button < MOUSE_MAX_BUTTON; ++button) { // The bClick array is set true if CButtonState::Update spots a click // i.e. a button down followed by a button up. if (bClick[button]) m_Key = KEY_MOUSE_CLICK + button; // The bDoubleClick array is set true if CButtonState::Update spots a // button down within double_click_time (500ms) of the last click else if (bDoubleClick[button]) m_Key = KEY_MOUSE_DOUBLE_CLICK + button; else if (bLongClick[button]) m_Key = KEY_MOUSE_LONG_CLICK + button; if (m_Key != KEY_MOUSE_NOOP) break; } if (m_Key == KEY_MOUSE_NOOP) { // The m_hold array is set to the drag action if (m_hold[MOUSE_LEFT_BUTTON] != HoldAction::NONE) { switch (m_hold[MOUSE_LEFT_BUTTON]) { case HoldAction::DRAG: m_Key = KEY_MOUSE_DRAG; break; case HoldAction::DRAG_START: m_Key = KEY_MOUSE_DRAG_START; break; case HoldAction::DRAG_END: m_Key = KEY_MOUSE_DRAG_END; break; default: break; } } else if (m_hold[MOUSE_RIGHT_BUTTON] != HoldAction::NONE) { switch (m_hold[MOUSE_RIGHT_BUTTON]) { case HoldAction::DRAG: m_Key = KEY_MOUSE_RDRAG; break; case HoldAction::DRAG_START: m_Key = KEY_MOUSE_RDRAG_START; break; case HoldAction::DRAG_END: m_Key = KEY_MOUSE_RDRAG_END; break; default: break; } } // dz is +1 on wheel up and -1 on wheel down else if (m_mouseState.dz > 0) m_Key = KEY_MOUSE_WHEEL_UP; else if (m_mouseState.dz < 0) m_Key = KEY_MOUSE_WHEEL_DOWN; // Check for a mouse move that isn't a drag, ignoring messages with no movement at all else if (newEvent.type == XBMC_MOUSEMOTION && (m_mouseState.dx || m_mouseState.dy)) m_Key = KEY_MOUSE_MOVE; } // activate the mouse pointer if we have an action or the mouse has moved far enough if ((MovedPastThreshold() && m_Key == KEY_MOUSE_MOVE) || (m_Key != KEY_MOUSE_NOOP && m_Key != KEY_MOUSE_MOVE)) SetActive(); // reset the mouse state if nothing is held down if (bNothingDown) SetState(MOUSE_STATE_NORMAL); } void CMouseStat::SetResolution(int maxX, int maxY, float speedX, float speedY) { m_maxX = maxX; m_maxY = maxY; // speed is currently unused m_speedX = speedX; m_speedY = speedY; } void CMouseStat::SetActive(bool active /*=true*/) { m_lastActiveTime = CTimeUtils::GetFrameTime(); m_mouseState.active = active; // we show the OS mouse if: // 1. The mouse is active (it has been moved) AND // 2. The XBMC mouse is disabled in settings AND // 3. XBMC is not in fullscreen. CWinSystemBase* winSystem = CServiceBroker::GetWinSystem(); if (winSystem) winSystem->ShowOSMouse(m_mouseState.active && !IsEnabled() && !CServiceBroker::GetWinSystem()->IsFullScreen()); } // IsActive - returns true if we have been active in the last MOUSE_ACTIVE_LENGTH period bool CMouseStat::IsActive() { if (m_mouseState.active && (CTimeUtils::GetFrameTime() - m_lastActiveTime > MOUSE_ACTIVE_LENGTH)) SetActive(false); return (m_mouseState.active && IsEnabled()); } void CMouseStat::SetEnabled(bool enabled) { m_mouseEnabled = enabled; SetActive(enabled); } // IsEnabled - returns true if mouse is enabled bool CMouseStat::IsEnabled() const { return m_mouseEnabled; } bool CMouseStat::MovedPastThreshold() const { return (m_mouseState.dx * m_mouseState.dx + m_mouseState.dy * m_mouseState.dy >= MOUSE_MINIMUM_MOVEMENT * MOUSE_MINIMUM_MOVEMENT); } uint32_t CMouseStat::GetKey() const { return m_Key; } HoldAction CMouseStat::GetHold(int ButtonID) const { switch (ButtonID) { case MOUSE_LEFT_BUTTON: return m_hold[MOUSE_LEFT_BUTTON]; } return HoldAction::NONE; } CMouseStat::CButtonState::CButtonState() { m_state = STATE_RELEASED; m_time = 0; m_x = 0; m_y = 0; } bool CMouseStat::CButtonState::InClickRange(int x, int y) const { int dx = x - m_x; int dy = y - m_y; return (unsigned int)(dx * dx + dy * dy) <= click_confines * click_confines; } CMouseStat::CButtonState::BUTTON_ACTION CMouseStat::CButtonState::Update(unsigned int time, int x, int y, bool down) { if (m_state == STATE_IN_DRAG) { if (down) return MB_DRAG; m_state = STATE_RELEASED; return MB_DRAG_END; } else if (m_state == STATE_RELEASED) { if (down) { m_state = STATE_IN_CLICK; m_time = time; m_x = x; m_y = y; } } else if (m_state == STATE_IN_CLICK) { if (down) { if (!InClickRange(x, y)) { // beginning a drag m_state = STATE_IN_DRAG; return MB_DRAG_START; } } else { // button up if (time - m_time < short_click_time) { // single click m_state = STATE_IN_DOUBLE_CLICK; m_time = time; // double click time and positioning is measured from the m_x = x; // end of a single click m_y = y; return MB_SHORT_CLICK; } else { // long click m_state = STATE_RELEASED; return MB_LONG_CLICK; } } } else if (m_state == STATE_IN_DOUBLE_CLICK) { if (time - m_time > double_click_time || !InClickRange(x, y)) { // too long, or moved to much - reset to released state and re-update, as we may be starting a // new click m_state = STATE_RELEASED; return Update(time, x, y, down); } if (down) { m_state = STATE_IN_DOUBLE_IGNORE; return MB_DOUBLE_CLICK; } } else if (m_state == STATE_IN_DOUBLE_IGNORE) { if (!down) m_state = STATE_RELEASED; } return MB_NONE; }