* Copyright 2008-2011, Clemens Zeidler <haiku@clemens-zeidler.de>
* Copyright 2022, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "movement_maker.h"
#include <stdlib.h>
#include <math.h>
#include <KernelExport.h>
#ifdef TRACE_MOVEMENT_MAKER
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...)
#endif
#define SYN_WIDTH (4100)
#define SYN_HEIGHT (3140)
static int32
make_small(float value)
{
if (value > 0)
return (int32)floorf(value);
else
return (int32)ceilf(value);
}
void
MovementMaker::SetSettings(const touchpad_settings& settings)
{
fSettings = settings;
}
void
MovementMaker::SetSpecs(const touchpad_specs& specs)
{
fSpecs = specs;
fAreaWidth = fSpecs.areaEndX - fSpecs.areaStartX;
fAreaHeight = fSpecs.areaEndY - fSpecs.areaStartY;
fSpeed = SYN_WIDTH / fAreaWidth;
fSmallMovement = 3 / fSpeed;
}
void
MovementMaker::StartNewMovment()
{
if (fSettings.scroll_xstepsize <= 0)
fSettings.scroll_xstepsize = 1;
if (fSettings.scroll_ystepsize <= 0)
fSettings.scroll_ystepsize = 1;
fMovementMakerStarted = true;
scrolling_x = 0;
scrolling_y = 0;
}
void
MovementMaker::GetMovement(uint32 posX, uint32 posY)
{
_GetRawMovement(posX, posY);
}
void
MovementMaker::GetScrolling(uint32 posX, uint32 posY)
{
int32 stepsX = 0, stepsY = 0;
_GetRawMovement(posX, posY);
_ComputeAcceleration(fSettings.scroll_acceleration);
if (fSettings.scroll_xstepsize > 0) {
scrolling_x += xDelta;
stepsX = make_small(scrolling_x / fSettings.scroll_xstepsize);
scrolling_x -= stepsX * fSettings.scroll_xstepsize;
xDelta = stepsX;
} else {
scrolling_x = 0;
xDelta = 0;
}
if (fSettings.scroll_ystepsize > 0) {
scrolling_y += yDelta;
stepsY = make_small(scrolling_y / fSettings.scroll_ystepsize);
scrolling_y -= stepsY * fSettings.scroll_ystepsize;
yDelta = -1 * stepsY;
} else {
scrolling_y = 0;
yDelta = 0;
}
}
void
MovementMaker::_GetRawMovement(uint32 posX, uint32 posY)
{
posX = posX * SYN_WIDTH / fAreaWidth;
posY = posY * SYN_HEIGHT / fAreaHeight;
const float acceleration = 0.8;
const float translation = 12.0;
int diff;
if (fMovementMakerStarted) {
fMovementMakerStarted = false;
fPreviousX = posX;
fPreviousY = posY;
}
diff = posX - fPreviousX;
if ((diff > -fSmallMovement && diff < -1)
|| (diff > 1 && diff < fSmallMovement)) {
diff /= 2;
}
if (diff == 0)
fDeltaSumX = 0;
else
fDeltaSumX += diff;
diff = posY - fPreviousY;
if ((diff > -fSmallMovement && diff < -1)
|| (diff > 1 && diff < fSmallMovement)) {
diff /= 2;
}
if (diff == 0)
fDeltaSumY = 0;
else
fDeltaSumY += diff;
fPreviousX = posX;
fPreviousY = posY;
xDelta = fDeltaSumX / translation;
yDelta = fDeltaSumY / translation;
if (xDelta > 1.0) {
fDeltaSumX = 0.0;
xDelta = 1.0 + (xDelta - 1.0) * acceleration;
} else if (xDelta < -1.0) {
fDeltaSumX = 0.0;
xDelta = -1.0 + (xDelta + 1.0) * acceleration;
}
if (yDelta > 1.0) {
fDeltaSumY = 0.0;
yDelta = 1.0 + (yDelta - 1.0) * acceleration;
} else if (yDelta < -1.0) {
fDeltaSumY = 0.0;
yDelta = -1.0 + (yDelta + 1.0) * acceleration;
}
xDelta = make_small(xDelta);
yDelta = make_small(yDelta);
}
void
MovementMaker::_ComputeAcceleration(int8 accel_factor)
{
float acceleration = 1;
if (accel_factor != 0) {
acceleration = 1 + sqrtf(xDelta * xDelta
+ yDelta * yDelta) * accel_factor / 50.0;
}
xDelta = make_small(xDelta * acceleration);
yDelta = make_small(yDelta * acceleration);
}
#define fTapTimeOUT 200000
TouchpadMovement::TouchpadMovement()
{
fMovementStarted = false;
fScrollingStarted = false;
fTapStarted = false;
fValidEdgeMotion = false;
fDoubleClick = false;
}
status_t
TouchpadMovement::EventToMovement(const touchpad_movement* event, mouse_movement* movement,
bigtime_t& repeatTimeout)
{
if (!movement)
return B_ERROR;
movement->xdelta = 0;
movement->ydelta = 0;
movement->buttons = 0;
movement->wheel_ydelta = 0;
movement->wheel_xdelta = 0;
movement->modifiers = 0;
movement->clicks = 0;
movement->timestamp = system_time();
if ((movement->timestamp - fTapTime) > fTapTimeOUT) {
if (fTapStarted)
TRACE("TouchpadMovement: tap gesture timed out\n");
fTapStarted = false;
if (!fDoubleClick
|| (movement->timestamp - fTapTime) > 2 * fTapTimeOUT) {
fTapClicks = 0;
}
}
if (event->buttons & kLeftButton) {
fTapClicks = 0;
fTapdragStarted = false;
fTapStarted = false;
fValidEdgeMotion = false;
}
if (event->zPressure >= fSpecs.minPressure
&& event->zPressure < fSpecs.maxPressure
&& ((event->fingerWidth >= 4 && event->fingerWidth <= 7)
|| event->fingerWidth == 0 || event->fingerWidth == 1)
&& (event->xPosition != 0 || event->yPosition != 0)) {
if (!_CheckScrollingToMovement(event, movement))
_MoveToMovement(event, movement);
} else
_NoTouchToMovement(event, movement);
if (fTapdragStarted || fValidEdgeMotion) {
repeatTimeout = 1000 * 50;
} else
repeatTimeout = B_INFINITE_TIMEOUT;
return B_OK;
}
const int32 kEdgeMotionSpeed = 200;
bool
TouchpadMovement::_EdgeMotion(const touchpad_movement *event, mouse_movement *movement,
bool validStart)
{
float xdelta = 0;
float ydelta = 0;
bigtime_t time = system_time();
if (fLastEdgeMotion != 0) {
xdelta = fRestEdgeMotion + kEdgeMotionSpeed *
float(time - fLastEdgeMotion) / (1000 * 1000);
fRestEdgeMotion = xdelta - int32(xdelta);
ydelta = xdelta;
} else {
fRestEdgeMotion = 0;
}
bool inXEdge = false;
bool inYEdge = false;
if (int32(event->xPosition) < fSpecs.areaStartX + fSpecs.edgeMotionWidth) {
inXEdge = true;
xdelta *= -1;
} else if (event->xPosition > uint16(
fSpecs.areaEndX - fSpecs.edgeMotionWidth)) {
inXEdge = true;
}
if (int32(event->yPosition) < fSpecs.areaStartY + fSpecs.edgeMotionWidth) {
inYEdge = true;
ydelta *= -1;
} else if (event->yPosition > uint16(
fSpecs.areaEndY - fSpecs.edgeMotionWidth)) {
inYEdge = true;
}
if (inXEdge && validStart)
movement->xdelta = make_small(xdelta);
if (inYEdge && validStart)
movement->ydelta = make_small(ydelta);
if (!inXEdge && !inYEdge)
fLastEdgeMotion = 0;
else
fLastEdgeMotion = time;
if ((inXEdge || inYEdge) && !validStart)
return false;
return true;
}
this function updates the fClickCount, as well as the
\a movement's clicks field.
Also, it sets the button state from movement->buttons.
*/
void
TouchpadMovement::_UpdateButtons(mouse_movement *movement)
{
if (movement->buttons != 0 && fButtonsState == 0) {
if (fClickLastTime + click_speed > movement->timestamp)
fClickCount++;
else
fClickCount = 1;
fClickLastTime = movement->timestamp;
}
if (movement->buttons != 0)
movement->clicks = fClickCount;
fButtonsState = movement->buttons;
}
void
TouchpadMovement::_NoTouchToMovement(const touchpad_movement *event,
mouse_movement *movement)
{
uint32 buttons = event->buttons;
if (fMovementStarted)
TRACE("TouchpadMovement: no touch event\n");
fScrollingStarted = false;
fMovementStarted = false;
fLastEdgeMotion = 0;
if (fTapdragStarted
&& (movement->timestamp - fTapTime) < fTapTimeOUT) {
buttons = kLeftButton;
}
if ((movement->timestamp - fTapTime) > fTapTimeOUT) {
if (fTapdragStarted)
TRACE("TouchpadMovement: tap drag gesture timed out\n");
fTapdragStarted = false;
fValidEdgeMotion = false;
}
if (abs(fTapDeltaX) > 15 || abs(fTapDeltaY) > 15) {
fTapStarted = false;
fTapClicks = 0;
}
if (fTapStarted || fDoubleClick) {
TRACE("TouchpadMovement: tap gesture\n");
fTapClicks++;
if (fTapClicks > 1) {
TRACE("TouchpadMovement: empty click\n");
buttons = kNoButton;
fTapClicks = 0;
fDoubleClick = true;
} else {
buttons = kLeftButton;
fTapStarted = false;
fTapdragStarted = true;
fDoubleClick = false;
}
}
movement->buttons = buttons;
_UpdateButtons(movement);
}
void
TouchpadMovement::_MoveToMovement(const touchpad_movement *event, mouse_movement *movement)
{
bool isStartOfMovement = false;
float pressure = 0;
TRACE("TouchpadMovement: movement event\n");
if (!fMovementStarted) {
isStartOfMovement = true;
fMovementStarted = true;
StartNewMovment();
}
GetMovement(event->xPosition, event->yPosition);
movement->xdelta = make_small(xDelta);
movement->ydelta = make_small(yDelta);
fTapDeltaX += make_small(xDelta);
fTapDeltaY += make_small(yDelta);
if (fTapdragStarted) {
movement->buttons = kLeftButton;
movement->clicks = 0;
fValidEdgeMotion = _EdgeMotion(event, movement, fValidEdgeMotion);
TRACE("TouchpadMovement: tap drag\n");
} else {
TRACE("TouchpadMovement: movement set buttons\n");
movement->buttons = event->buttons;
}
pressure = 20 * (event->zPressure - fSpecs.minPressure)
/ (fSpecs.realMaxPressure - fSpecs.minPressure);
if (!fTapStarted
&& isStartOfMovement
&& fSettings.tapgesture_sensibility > 0.
&& fSettings.tapgesture_sensibility > (20 - pressure)) {
TRACE("TouchpadMovement: tap started\n");
fTapStarted = true;
fTapTime = system_time();
fTapDeltaX = 0;
fTapDeltaY = 0;
}
_UpdateButtons(movement);
}
scrolling work if it is.
\return \c true if this was a scrolling event, \c false if not.
*/
bool
TouchpadMovement::_CheckScrollingToMovement(const touchpad_movement *event,
mouse_movement *movement)
{
bool isSideScrollingV = false;
bool isSideScrollingH = false;
if (fButtonsState != 0)
return false;
if ((fSpecs.areaEndX - fAreaWidth * fSettings.scroll_rightrange
< event->xPosition && !fMovementStarted
&& fSettings.scroll_rightrange > 0.000001)
|| fSettings.scroll_rightrange > 0.999999) {
isSideScrollingV = true;
}
if ((fSpecs.areaStartY + fAreaHeight * fSettings.scroll_bottomrange
> event->yPosition && !fMovementStarted
&& fSettings.scroll_bottomrange > 0.000001)
|| fSettings.scroll_bottomrange > 0.999999) {
isSideScrollingH = true;
}
if ((event->fingerWidth == 0 || event->fingerWidth == 1)
&& fSettings.scroll_twofinger) {
isSideScrollingV = true;
isSideScrollingH = fSettings.scroll_twofinger_horizontal;
}
if (!isSideScrollingV && !isSideScrollingH) {
fScrollingStarted = false;
return false;
}
TRACE("TouchpadMovement: scroll event\n");
fTapStarted = false;
fTapClicks = 0;
fTapdragStarted = false;
fValidEdgeMotion = false;
if (!fScrollingStarted) {
fScrollingStarted = true;
StartNewMovment();
}
GetScrolling(event->xPosition, event->yPosition);
movement->wheel_ydelta = make_small(yDelta);
movement->wheel_xdelta = make_small(xDelta);
if (isSideScrollingV && !isSideScrollingH)
movement->wheel_xdelta = 0;
else if (isSideScrollingH && !isSideScrollingV)
movement->wheel_ydelta = 0;
fButtonsState = movement->buttons;
return true;
}