phoenix-firestorm/indra/newview/fsvirtualtrackpad.cpp

632 lines
17 KiB
C++

/**
* @file irtualTrackpad.cpp
* @author Angeldark Raymaker; derived from llVirtualTrackball by Andrey Lihatskiy
* @brief Header file for FSVirtualTrackpad
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2018, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
// A two dimensional slider control with optional pinch-mode.
#include "fsvirtualtrackpad.h"
#include "llrect.h"
#include "lluictrlfactory.h"
#include "llkeyboard.h"
// Globals
static LLDefaultChildRegistry::Register<FSVirtualTrackpad> register_virtual_trackball("fs_virtual_trackpad");
FSVirtualTrackpad::Params::Params()
: border("border"),
image_moon_back("image_moon_back"),
image_moon_front("image_moon_front"),
image_sphere("image_sphere"),
image_sun_back("image_sun_back"),
image_sun_front("image_sun_front"),
pinch_mode("pinch_mode"),
infinite_scroll_mode("infinite_scroll_mode")
{
}
FSVirtualTrackpad::FSVirtualTrackpad(const FSVirtualTrackpad::Params &p)
: LLUICtrl(p),
mImgMoonBack(p.image_moon_back),
mImgMoonFront(p.image_moon_front),
mImgSunBack(p.image_sun_back),
mImgSunFront(p.image_sun_front),
mImgSphere(p.image_sphere),
mAllowPinchMode(p.pinch_mode),
mInfiniteScrollMode(p.infinite_scroll_mode)
{
LLRect border_rect = getLocalRect();
mCursorValueX = mPinchCursorValueX = border_rect.getCenterX();
mCursorValueY = mPinchCursorValueY = border_rect.getCenterY();
mCursorValueZ = mPinchCursorValueZ = 0;
mThumbClickOffsetX = mThumbClickOffsetY = mPinchThumbClickOffsetX = mPinchThumbClickOffsetY = 0;
mPosXwhenCtrlDown = mPosYwhenCtrlDown = 0;
mPinchValueDeltaX = mPinchValueDeltaY = mPinchValueDeltaZ = 0;
mValueDeltaX = mValueDeltaY = mValueDeltaZ = 0;
LLViewBorder::Params border = p.border;
border.rect(border_rect);
mBorder = LLUICtrlFactory::create<LLViewBorder>(border);
addChild(mBorder);
LLPanel::Params touch_area;
touch_area.rect = LLRect(border_rect);
mTouchArea = LLUICtrlFactory::create<LLPanel>(touch_area);
addChild(mTouchArea);
}
FSVirtualTrackpad::~FSVirtualTrackpad() {}
bool FSVirtualTrackpad::postBuild()
{
return true;
}
void FSVirtualTrackpad::drawThumb(bool isPinchThumb)
{
LLUIImage *thumb;
if (mTouchArea->isInEnabledChain())
thumb = isPinchThumb ? mImgSunFront : mImgMoonFront;
else
thumb = isPinchThumb ? mImgSunBack : mImgMoonBack;
S32 x = isPinchThumb ? mPinchCursorValueX : mCursorValueX;
S32 y = isPinchThumb ? mPinchCursorValueY : mCursorValueY;
wrapOrClipCursorPosition(&x, &y);
S32 halfThumbWidth = thumb->getWidth() / 2;
S32 halfThumbHeight = thumb->getHeight() / 2;
thumb->draw(LLRect(x - halfThumbWidth,
y + halfThumbHeight,
x + halfThumbWidth,
y - halfThumbHeight));
}
bool FSVirtualTrackpad::isPointInTouchArea(S32 x, S32 y) const
{
if (!mTouchArea)
return false;
return mTouchArea->getRect().localPointInRect(x, y);
}
void FSVirtualTrackpad::determineThumbClickError(S32 x, S32 y)
{
if (!mTouchArea)
return;
if (!mImgSunFront)
return;
mThumbClickOffsetX = 0;
mThumbClickOffsetY = 0;
S32 errorX = mCursorValueX;
S32 errorY = mCursorValueY;
wrapOrClipCursorPosition(&errorX, &errorY);
errorX -= x;
errorY -= y;
if (fabs(errorX) > mImgSunFront->getWidth() / 2.0)
return;
if (fabs(errorY) > mImgSunFront->getHeight() / 2.0)
return;
mThumbClickOffsetX = errorX;
mThumbClickOffsetY = errorY;
}
void FSVirtualTrackpad::updateClickErrorIfInfiniteScrolling()
{
if (!mInfiniteScrollMode)
return;
S32 errorX = mCursorValueX;
S32 errorY = mCursorValueY;
LLRect rect = mTouchArea->getRect();
while (errorX > rect.mRight)
{
errorX -= rect.getWidth();
mThumbClickOffsetX += rect.getWidth();
}
while (errorX < rect.mLeft)
{
errorX += rect.getWidth();
mThumbClickOffsetX -= rect.getWidth();
}
while (errorY > rect.mTop)
{
errorY -= rect.getHeight();
mThumbClickOffsetY += rect.getHeight();
}
while (errorY < rect.mBottom)
{
errorY += rect.getHeight();
mThumbClickOffsetY -= rect.getHeight();
}
}
void FSVirtualTrackpad::determineThumbClickErrorForPinch(S32 x, S32 y)
{
if (!mTouchArea)
return;
if (!mImgMoonFront)
return;
mPinchThumbClickOffsetX = 0;
mPinchThumbClickOffsetY = 0;
S32 errorX = mPinchCursorValueX;
S32 errorY = mPinchCursorValueY;
wrapOrClipCursorPosition(&errorX, &errorY);
errorX -= x;
errorY -= y;
if (fabs(errorX) > mImgMoonFront->getWidth() / 2.0)
return;
if (fabs(errorY) > mImgMoonFront->getHeight() / 2.0)
return;
mPinchThumbClickOffsetX = errorX;
mPinchThumbClickOffsetY = errorY;
}
void FSVirtualTrackpad::updateClickErrorIfInfiniteScrollingForPinch()
{
if (!mInfiniteScrollMode)
return;
S32 errorX = mCursorValueX;
S32 errorY = mCursorValueY;
LLRect rect = mTouchArea->getRect();
while (errorX > rect.mRight)
{
errorX -= rect.getWidth();
mPinchThumbClickOffsetX += rect.getWidth();
}
while (errorX < rect.mLeft)
{
errorX += rect.getWidth();
mPinchThumbClickOffsetX -= rect.getWidth();
}
while (errorY > rect.mTop)
{
errorY -= rect.getHeight();
mPinchThumbClickOffsetY += rect.getHeight();
}
while (errorY < rect.mBottom)
{
errorY += rect.getHeight();
mPinchThumbClickOffsetY -= rect.getHeight();
}
}
void FSVirtualTrackpad::draw()
{
mImgSphere->draw(mTouchArea->getRect(), mTouchArea->isInEnabledChain() ? UI_VERTEX_COLOR : UI_VERTEX_COLOR % 0.5f);
if (mAllowPinchMode)
drawThumb(true);
drawThumb(false);
LLView::draw();
}
void FSVirtualTrackpad::setValue(const LLSD& value)
{
if (!value.isArray())
return;
if (value.size() == 2)
{
LLVector2 vec2;
vec2.setValue(value);
setValue(vec2.mV[VX], vec2.mV[VY], 0);
}
else if (value.size() == 3)
{
LLVector3 vec3;
vec3.setValue(value);
setValue(vec3.mV[VX], vec3.mV[VY], vec3.mV[VZ]);
}
}
void FSVirtualTrackpad::setValue(F32 x, F32 y, F32 z)
{
convertNormalizedToPixelPos(x, y, z, &mCursorValueX, &mCursorValueY, &mCursorValueZ);
mValueX = mCursorValueX;
mValueY = mCursorValueY;
mValueZ = mCursorValueZ;
}
void FSVirtualTrackpad::setPinchValue(F32 x, F32 y, F32 z)
{
convertNormalizedToPixelPos(x, y, z, &mPinchCursorValueX, &mPinchCursorValueY, &mPinchCursorValueZ);
mPinchValueX = mPinchCursorValueX;
mPinchValueY = mPinchCursorValueY;
mPinchValueZ = mPinchCursorValueZ;
}
LLSD FSVirtualTrackpad::getValue() const { return normalizePixelPos(mValueX, mValueY, mValueZ).getValue(); }
LLSD FSVirtualTrackpad::getValueDelta() { return normalizeDelta(mValueDeltaX, mValueDeltaY, mValueDeltaZ).getValue(); }
LLSD FSVirtualTrackpad::getPinchValue() { return normalizePixelPos(mPinchValueX, mPinchValueY, mPinchValueZ).getValue(); }
LLSD FSVirtualTrackpad::getPinchValueDelta() { return normalizeDelta(mPinchValueDeltaX, mPinchValueDeltaY, mPinchValueDeltaZ).getValue(); }
void FSVirtualTrackpad::wrapOrClipCursorPosition(S32* x, S32* y) const
{
if (!x || !y)
return;
LLRect rect = mTouchArea->getRect();
if (mInfiniteScrollMode)
{
while (*x > rect.mRight)
*x -= rect.getWidth();
while (*x < rect.mLeft)
*x += rect.getWidth();
while (*y > rect.mTop)
*y -= rect.getHeight();
while (*y < rect.mBottom)
*y += rect.getHeight();
}
else
{
rect.clampPointToRect(*x, *y);
}
}
bool FSVirtualTrackpad::handleHover(S32 x, S32 y, MASK mask)
{
if (!hasMouseCapture())
return true;
S32 deltaX, deltaY;
getHoverMovementDeltas(x, y, mask, &deltaX, &deltaY);
applyHoverMovementDeltas(deltaX, deltaY, mask);
applyDeltasToValues(deltaX, deltaY, mask);
applyDeltasToDeltaValues(deltaX, deltaY, mask);
onCommit();
return true;
}
void FSVirtualTrackpad::getHoverMovementDeltas(S32 x, S32 y, MASK mask, S32* deltaX, S32* deltaY)
{
if (!deltaX || !deltaY)
return;
S32 fromX, fromY;
fromX = mDoingPinchMode ? mPinchCursorValueX : mCursorValueX;
fromY = mDoingPinchMode ? mPinchCursorValueY : mCursorValueY;
if (mask & MASK_CONTROL)
{
if (!mHeldDownControlBefore)
{
mPosXwhenCtrlDown = x;
mPosYwhenCtrlDown = y;
mHeldDownControlBefore = true;
}
if (mDoingPinchMode)
{
*deltaX = mPosXwhenCtrlDown - (mPosXwhenCtrlDown - x) / 8 + mPinchThumbClickOffsetX - fromX;
*deltaY = mPosYwhenCtrlDown - (mPosYwhenCtrlDown - y) / 8 + mPinchThumbClickOffsetY - fromY;
}
else
{
*deltaX = mPosXwhenCtrlDown - (mPosXwhenCtrlDown - x) / 8 + mThumbClickOffsetX - fromX;
*deltaY = mPosYwhenCtrlDown - (mPosYwhenCtrlDown - y) / 8 + mThumbClickOffsetY - fromY;
}
}
else
{
if (mHeldDownControlBefore)
{
mThumbClickOffsetX = fromX - x;
mThumbClickOffsetY = fromY - y;
mHeldDownControlBefore = false;
}
if (mDoingPinchMode)
{
*deltaX = x + mPinchThumbClickOffsetX - fromX;
*deltaY = y + mPinchThumbClickOffsetY - fromY;
}
else
{
*deltaX = x + mThumbClickOffsetX - fromX;
*deltaY = y + mThumbClickOffsetY - fromY;
}
}
}
void FSVirtualTrackpad::applyHoverMovementDeltas(S32 deltaX, S32 deltaY, MASK mask)
{
if (mDoingPinchMode)
{
mPinchCursorValueX += deltaX;
mPinchCursorValueY += deltaY;
if (!mInfiniteScrollMode) // then constrain the cursor within control area
wrapOrClipCursorPosition(&mPinchCursorValueX, &mPinchCursorValueY);
}
else
{
mCursorValueX += deltaX;
mCursorValueY += deltaY;
if (!mInfiniteScrollMode) // then constrain the cursor within control area
wrapOrClipCursorPosition(&mCursorValueX, &mCursorValueY);
}
}
void FSVirtualTrackpad::applyDeltasToValues(S32 deltaX, S32 deltaY, MASK mask)
{
if (mDoingPinchMode)
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
{
mPinchValueY += deltaY;
mPinchValueZ += deltaX;
}
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
{
mPinchValueX += deltaX;
mPinchValueZ += deltaY;
}
else
{
mPinchValueX += deltaX;
mPinchValueY += deltaY;
}
}
else
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
{
mValueY += deltaY;
mValueZ += deltaX;
}
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
{
mValueX += deltaX;
mValueZ += deltaY;
}
else
{
mValueX += deltaX;
mValueY += deltaY;
}
}
}
void FSVirtualTrackpad::applyDeltasToDeltaValues(S32 deltaX, S32 deltaY, MASK mask)
{
if (mDoingPinchMode)
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
{
mPinchValueDeltaX = 0;
mPinchValueDeltaY = deltaY;
mPinchValueDeltaZ = deltaX;
}
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
{
mPinchValueDeltaX = deltaX;
mPinchValueDeltaY = 0;
mPinchValueDeltaZ = deltaY;
}
else
{
mPinchValueDeltaX = deltaX;
mPinchValueDeltaY = deltaY;
mPinchValueDeltaZ = 0;
}
}
else
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
{
mValueDeltaX = 0;
mValueDeltaY = deltaY;
mValueDeltaZ = deltaX;
}
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
{
mValueDeltaX = deltaX;
mValueDeltaY = 0;
mValueDeltaZ = deltaY;
}
else
{
mValueDeltaX = deltaX;
mValueDeltaY = deltaY;
mValueDeltaZ = 0;
}
}
}
LLVector3 FSVirtualTrackpad::normalizePixelPos(S32 x, S32 y, S32 z) const
{
LLVector3 result;
if (!mTouchArea)
return result;
LLRect rect = mTouchArea->getRect();
S32 centerX = rect.getCenterX();
S32 centerY = rect.getCenterY();
S32 width = rect.getWidth();
S32 height = rect.getHeight();
result.mV[VX] = (F32) (x - centerX) / width * 2;
result.mV[VY] = (F32) (y - centerY) / height * 2;
result.mV[VZ] = (F32) z * ThirdAxisQuantization;
return result;
}
LLVector3 FSVirtualTrackpad::normalizeDelta(S32 x, S32 y, S32 z) const
{
LLVector3 result;
if (!mTouchArea)
return result;
LLRect rect = mTouchArea->getRect();
S32 width = rect.getWidth();
S32 height = rect.getHeight();
result.mV[VX] = (F32) x / width * 2;
result.mV[VY] = (F32) y / height * 2;
result.mV[VZ] = (F32) z * ThirdAxisQuantization;
return result;
}
void FSVirtualTrackpad::convertNormalizedToPixelPos(F32 x, F32 y, F32 z, S32 *valX, S32 *valY, S32 *valZ)
{
if (!mTouchArea)
return;
LLRect rect = mTouchArea->getRect();
S32 centerX = rect.getCenterX();
S32 centerY = rect.getCenterY();
S32 width = rect.getWidth();
S32 height = rect.getHeight();
if (mInfiniteScrollMode)
{
*valX = centerX + ll_round(x * width / 2);
*valY = centerY + ll_round(y * height / 2);
}
else
{
*valX = centerX + ll_round(llclamp(x, -1, 1) * width / 2);
*valY = centerY + ll_round(llclamp(y, -1, 1) * height / 2);
}
*valZ = ll_round(z / ThirdAxisQuantization);
}
bool FSVirtualTrackpad::handleMouseUp(S32 x, S32 y, MASK mask)
{
if (hasMouseCapture())
{
gFocusMgr.setMouseCapture(NULL);
mHeldDownControlBefore = false;
make_ui_sound("UISndClickRelease");
}
return LLView::handleMouseUp(x, y, mask);
}
bool FSVirtualTrackpad::handleMouseDown(S32 x, S32 y, MASK mask)
{
if (isPointInTouchArea(x, y))
{
determineThumbClickError(x, y);
updateClickErrorIfInfiniteScrolling();
gFocusMgr.setMouseCapture(this);
make_ui_sound("UISndClick");
}
return LLView::handleMouseDown(x, y, mask);
}
bool FSVirtualTrackpad::handleRightMouseUp(S32 x, S32 y, MASK mask)
{
if (hasMouseCapture())
{
mDoingPinchMode = false;
gFocusMgr.setMouseCapture(NULL);
make_ui_sound("UISndClickRelease");
}
return LLView::handleRightMouseUp(x, y, mask);
}
// move pinch cursor
bool FSVirtualTrackpad::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
if (!mAllowPinchMode)
return LLView::handleRightMouseDown(x, y, mask);
if (isPointInTouchArea(x, y))
{
determineThumbClickErrorForPinch(x, y);
updateClickErrorIfInfiniteScrollingForPinch();
mDoingPinchMode = true;
gFocusMgr.setMouseCapture(this);
make_ui_sound("UISndClick");
}
return LLView::handleRightMouseDown(x, y, mask);
}
// pass wheel-clicks to third axis
bool FSVirtualTrackpad::handleScrollWheel(S32 x, S32 y, S32 clicks)
{
if (hasMouseCapture() || isPointInTouchArea(x, y))
{
MASK mask = gKeyboard->currentMask(true);
S32 changeAmount = WheelClickQuanta;
if (mask & MASK_CONTROL)
changeAmount /= 5;
if (mDoingPinchMode)
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
mPinchValueX -= clicks * changeAmount;
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
mPinchValueY -= clicks * changeAmount;
else
mPinchValueZ -= clicks * changeAmount;
}
else
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
mValueX -= clicks * changeAmount;
else if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_SHIFT)
mValueY -= clicks * changeAmount;
else
mValueZ -= clicks * changeAmount;
}
if (!hasMouseCapture())
onCommit();
return true;
}
else
return LLUICtrl::handleScrollWheel(x, y, clicks);
}