phoenix-firestorm/indra/newview/lljoystickbutton.cpp

799 lines
17 KiB
C++

/**
* @file lljoystickbutton.cpp
* @brief LLJoystick class implementation
*
* $LicenseInfo:firstyear=2001&license=viewergpl$
*
* Copyright (c) 2001-2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "lljoystickbutton.h"
// Library includes
#include "llcoord.h"
#include "indra_constants.h"
#include "llrender.h"
// Project includes
#include "llui.h"
#include "llagent.h"
#include "llviewertexture.h"
#include "llviewertexturelist.h"
#include "llviewerwindow.h"
#include "llmoveview.h"
#include "llglheaders.h"
static LLDefaultChildRegistry::Register<LLJoystickAgentSlide> r1("joystick_slide");
static LLDefaultChildRegistry::Register<LLJoystickAgentTurn> r2("joystick_turn");
static LLDefaultChildRegistry::Register<LLJoystickCameraRotate> r3("joystick_rotate");
static LLDefaultChildRegistry::Register<LLJoystickCameraZoom> r4("joystick_zoom");
static LLDefaultChildRegistry::Register<LLJoystickCameraTrack> r5("joystick_track");
const F32 NUDGE_TIME = 0.25f; // in seconds
const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed
//
// Public Methods
//
void QuadrantNames::declareValues()
{
declare("origin", JQ_ORIGIN);
declare("up", JQ_UP);
declare("down", JQ_DOWN);
declare("left", JQ_LEFT);
declare("right", JQ_RIGHT);
}
LLJoystick::LLJoystick(const LLJoystick::Params& p)
: LLButton(p),
mInitialOffset(0, 0),
mLastMouse(0, 0),
mFirstMouse(0, 0),
mVertSlopNear(0),
mVertSlopFar(0),
mHorizSlopNear(0),
mHorizSlopFar(0),
mHeldDown(FALSE),
mHeldDownTimer(),
mInitialQuadrant(p.quadrant)
{
setHeldDownCallback(&LLJoystick::onBtnHeldDown, this);
}
void LLJoystick::updateSlop()
{
mVertSlopNear = getRect().getHeight();
mVertSlopFar = getRect().getHeight() * 2;
mHorizSlopNear = getRect().getWidth();
mHorizSlopFar = getRect().getWidth() * 2;
// Compute initial mouse offset based on initial quadrant.
// Place the mouse evenly between the near and far zones.
switch (mInitialQuadrant)
{
case JQ_ORIGIN:
mInitialOffset.set(0, 0);
break;
case JQ_UP:
mInitialOffset.mX = 0;
mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
break;
case JQ_DOWN:
mInitialOffset.mX = 0;
mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2;
break;
case JQ_LEFT:
mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
break;
case JQ_RIGHT:
mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
break;
default:
llerrs << "LLJoystick::LLJoystick() - bad switch case" << llendl;
break;
}
return;
}
BOOL LLJoystick::handleMouseDown(S32 x, S32 y, MASK mask)
{
//llinfos << "joystick mouse down " << x << ", " << y << llendl;
mLastMouse.set(x, y);
mFirstMouse.set(x, y);
mMouseDownTimer.reset();
return LLButton::handleMouseDown(x, y, mask);
}
BOOL LLJoystick::handleMouseUp(S32 x, S32 y, MASK mask)
{
// llinfos << "joystick mouse up " << x << ", " << y << llendl;
if( hasMouseCapture() )
{
mLastMouse.set(x, y);
mHeldDown = FALSE;
onMouseUp();
}
return LLButton::handleMouseUp(x, y, mask);
}
BOOL LLJoystick::handleHover(S32 x, S32 y, MASK mask)
{
if( hasMouseCapture() )
{
mLastMouse.set(x, y);
}
return LLButton::handleHover(x, y, mask);
}
F32 LLJoystick::getElapsedHeldDownTime()
{
if( mHeldDown )
{
return getHeldDownTime();
}
else
{
return 0.f;
}
}
// static
void LLJoystick::onBtnHeldDown(void *userdata)
{
LLJoystick *self = (LLJoystick *)userdata;
if (self)
{
self->mHeldDown = TRUE;
self->onHeldDown();
}
}
EJoystickQuadrant LLJoystick::selectQuadrant(LLXMLNodePtr node)
{
EJoystickQuadrant quadrant = JQ_RIGHT;
if (node->hasAttribute("quadrant"))
{
std::string quadrant_name;
node->getAttributeString("quadrant", quadrant_name);
quadrant = quadrantFromName(quadrant_name);
}
return quadrant;
}
std::string LLJoystick::nameFromQuadrant(EJoystickQuadrant quadrant)
{
if (quadrant == JQ_ORIGIN) return std::string("origin");
else if (quadrant == JQ_UP) return std::string("up");
else if (quadrant == JQ_DOWN) return std::string("down");
else if (quadrant == JQ_LEFT) return std::string("left");
else if (quadrant == JQ_RIGHT) return std::string("right");
else return std::string();
}
EJoystickQuadrant LLJoystick::quadrantFromName(const std::string& sQuadrant)
{
EJoystickQuadrant quadrant = JQ_RIGHT;
if (sQuadrant == "origin")
{
quadrant = JQ_ORIGIN;
}
else if (sQuadrant == "up")
{
quadrant = JQ_UP;
}
else if (sQuadrant == "down")
{
quadrant = JQ_DOWN;
}
else if (sQuadrant == "left")
{
quadrant = JQ_LEFT;
}
else if (sQuadrant == "right")
{
quadrant = JQ_RIGHT;
}
return quadrant;
}
//-------------------------------------------------------------------------------
// LLJoystickAgentTurn
//-------------------------------------------------------------------------------
void LLJoystickAgentTurn::onHeldDown()
{
F32 time = getElapsedHeldDownTime();
updateSlop();
//llinfos << "move forward/backward (and/or turn)" << llendl;
S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;
float m = (float) (dx)/abs(dy);
if (m > 1) {
m = 1;
}
else if (m < -1) {
m = -1;
}
gAgent.moveYaw(-LLFloaterMove::getYawRate(time)*m);
// handle forward/back movement
if (dy > mVertSlopFar)
{
// ...if mouse is forward of run region run forward
gAgent.moveAt(1);
}
else if (dy > mVertSlopNear)
{
if( time < NUDGE_TIME )
{
gAgent.moveAtNudge(1);
}
else
{
// ...else if mouse is forward of walk region walk forward
// JC 9/5/2002 - Always run / move quickly.
gAgent.moveAt(1);
}
}
else if (dy < -mVertSlopFar)
{
// ...else if mouse is behind run region run backward
gAgent.moveAt(-1);
}
else if (dy < -mVertSlopNear)
{
if( time < NUDGE_TIME )
{
gAgent.moveAtNudge(-1);
}
else
{
// ...else if mouse is behind walk region walk backward
// JC 9/5/2002 - Always run / move quickly.
gAgent.moveAt(-1);
}
}
}
//-------------------------------------------------------------------------------
// LLJoystickAgentSlide
//-------------------------------------------------------------------------------
void LLJoystickAgentSlide::onMouseUp()
{
F32 time = getElapsedHeldDownTime();
if( time < NUDGE_TIME )
{
switch (mInitialQuadrant)
{
case JQ_LEFT:
gAgent.moveLeftNudge(1);
break;
case JQ_RIGHT:
gAgent.moveLeftNudge(-1);
break;
default:
break;
}
}
}
void LLJoystickAgentSlide::onHeldDown()
{
//llinfos << "slide left/right (and/or move forward/backward)" << llendl;
updateSlop();
S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;
// handle left-right sliding
if (dx > mHorizSlopNear)
{
gAgent.moveLeft(-1);
}
else if (dx < -mHorizSlopNear)
{
gAgent.moveLeft(1);
}
// handle forward/back movement
if (dy > mVertSlopFar)
{
// ...if mouse is forward of run region run forward
gAgent.moveAt(1);
}
else if (dy > mVertSlopNear)
{
// ...else if mouse is forward of walk region walk forward
gAgent.moveAtNudge(1);
}
else if (dy < -mVertSlopFar)
{
// ...else if mouse is behind run region run backward
gAgent.moveAt(-1);
}
else if (dy < -mVertSlopNear)
{
// ...else if mouse is behind walk region walk backward
gAgent.moveAtNudge(-1);
}
}
//-------------------------------------------------------------------------------
// LLJoystickCameraRotate
//-------------------------------------------------------------------------------
LLJoystickCameraRotate::LLJoystickCameraRotate(const LLJoystickCameraRotate::Params& p)
: LLJoystick(p),
mInLeft( FALSE ),
mInTop( FALSE ),
mInRight( FALSE ),
mInBottom( FALSE )
{ }
void LLJoystickCameraRotate::updateSlop()
{
// do the initial offset calculation based on mousedown location
// small fixed slop region
mVertSlopNear = 16;
mVertSlopFar = 32;
mHorizSlopNear = 16;
mHorizSlopFar = 32;
return;
}
BOOL LLJoystickCameraRotate::handleMouseDown(S32 x, S32 y, MASK mask)
{
updateSlop();
// Set initial offset based on initial click location
S32 horiz_center = getRect().getWidth() / 2;
S32 vert_center = getRect().getHeight() / 2;
S32 dx = x - horiz_center;
S32 dy = y - vert_center;
if (dy > dx && dy > -dx)
{
// top
mInitialOffset.mX = 0;
mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
mInitialQuadrant = JQ_UP;
}
else if (dy > dx && dy <= -dx)
{
// left
mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
mInitialQuadrant = JQ_LEFT;
}
else if (dy <= dx && dy <= -dx)
{
// bottom
mInitialOffset.mX = 0;
mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2;
mInitialQuadrant = JQ_DOWN;
}
else
{
// right
mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
mInitialQuadrant = JQ_RIGHT;
}
return LLJoystick::handleMouseDown(x, y, mask);
}
void LLJoystickCameraRotate::onHeldDown()
{
updateSlop();
S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;
// left-right rotation
if (dx > mHorizSlopNear)
{
gAgent.unlockView();
gAgent.setOrbitLeftKey(getOrbitRate());
}
else if (dx < -mHorizSlopNear)
{
gAgent.unlockView();
gAgent.setOrbitRightKey(getOrbitRate());
}
// over/under rotation
if (dy > mVertSlopNear)
{
gAgent.unlockView();
gAgent.setOrbitUpKey(getOrbitRate());
}
else if (dy < -mVertSlopNear)
{
gAgent.unlockView();
gAgent.setOrbitDownKey(getOrbitRate());
}
}
F32 LLJoystickCameraRotate::getOrbitRate()
{
F32 time = getElapsedHeldDownTime();
if( time < NUDGE_TIME )
{
F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME;
//llinfos << rate << llendl;
return rate;
}
else
{
return 1;
}
}
// Only used for drawing
void LLJoystickCameraRotate::setToggleState( BOOL left, BOOL top, BOOL right, BOOL bottom )
{
mInLeft = left;
mInTop = top;
mInRight = right;
mInBottom = bottom;
}
void LLJoystickCameraRotate::draw()
{
LLGLSUIDefault gls_ui;
getImageUnselected()->draw( 0, 0 );
if( mInTop )
{
drawRotatedImage( getImageSelected()->getImage(), 0 );
}
if( mInRight )
{
drawRotatedImage( getImageSelected()->getImage(), 1 );
}
if( mInBottom )
{
drawRotatedImage( getImageSelected()->getImage(), 2 );
}
if( mInLeft )
{
drawRotatedImage( getImageSelected()->getImage(), 3 );
}
if (sDebugRects)
{
drawDebugRect();
}
//// *HACK: also draw debug rectangles around currently-being-edited LLView, and any elements that are being highlighted by GUI preview code (see LLFloaterUIPreview)
//std::set<LLView*>::iterator iter = std::find(sPreviewHighlightedElements.begin(), sPreviewHighlightedElements.end(), this);
//if ((sEditingUI && this == sEditingUIView) || (iter != sPreviewHighlightedElements.end() && sDrawPreviewHighlights))
//{
// drawDebugRect();
//}
}
// Draws image rotated by multiples of 90 degrees
void LLJoystickCameraRotate::drawRotatedImage( LLTexture* image, S32 rotations )
{
S32 width = image->getWidth();
S32 height = image->getHeight();
F32 uv[][2] =
{
{ 1.f, 1.f },
{ 0.f, 1.f },
{ 0.f, 0.f },
{ 1.f, 0.f }
};
gGL.getTexUnit(0)->bind(image);
gGL.color4fv(UI_VERTEX_COLOR.mV);
gGL.begin(LLRender::QUADS);
{
gGL.texCoord2fv( uv[ (rotations + 0) % 4]);
gGL.vertex2i(width, height );
gGL.texCoord2fv( uv[ (rotations + 1) % 4]);
gGL.vertex2i(0, height );
gGL.texCoord2fv( uv[ (rotations + 2) % 4]);
gGL.vertex2i(0, 0);
gGL.texCoord2fv( uv[ (rotations + 3) % 4]);
gGL.vertex2i(width, 0);
}
gGL.end();
}
//-------------------------------------------------------------------------------
// LLJoystickCameraTrack
//-------------------------------------------------------------------------------
LLJoystickCameraTrack::Params::Params()
{
held_down_delay.seconds(0.0);
}
LLJoystickCameraTrack::LLJoystickCameraTrack(const LLJoystickCameraTrack::Params& p)
: LLJoystickCameraRotate(p)
{}
void LLJoystickCameraTrack::onHeldDown()
{
updateSlop();
S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX;
S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;
if (dx > mVertSlopNear)
{
gAgent.unlockView();
gAgent.setPanRightKey(getOrbitRate());
}
else if (dx < -mVertSlopNear)
{
gAgent.unlockView();
gAgent.setPanLeftKey(getOrbitRate());
}
// over/under rotation
if (dy > mVertSlopNear)
{
gAgent.unlockView();
gAgent.setPanUpKey(getOrbitRate());
}
else if (dy < -mVertSlopNear)
{
gAgent.unlockView();
gAgent.setPanDownKey(getOrbitRate());
}
}
//-------------------------------------------------------------------------------
// LLJoystickCameraZoom
//-------------------------------------------------------------------------------
LLJoystickCameraZoom::LLJoystickCameraZoom(const LLJoystickCameraZoom::Params& p)
: LLJoystick(p),
mInTop( FALSE ),
mInBottom( FALSE ),
mPlusInImage(p.plus_image),
mMinusInImage(p.minus_image)
{
}
BOOL LLJoystickCameraZoom::handleMouseDown(S32 x, S32 y, MASK mask)
{
BOOL handled = LLJoystick::handleMouseDown(x, y, mask);
if( handled )
{
if (mFirstMouse.mY > getRect().getHeight() / 2)
{
mInitialQuadrant = JQ_UP;
}
else
{
mInitialQuadrant = JQ_DOWN;
}
}
return handled;
}
void LLJoystickCameraZoom::onHeldDown()
{
updateSlop();
const F32 FAST_RATE = 2.5f; // two and a half times the normal rate
S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY;
if (dy > mVertSlopFar)
{
// Zoom in fast
gAgent.unlockView();
gAgent.setOrbitInKey(FAST_RATE);
}
else if (dy > mVertSlopNear)
{
// Zoom in slow
gAgent.unlockView();
gAgent.setOrbitInKey(getOrbitRate());
}
else if (dy < -mVertSlopFar)
{
// Zoom out fast
gAgent.unlockView();
gAgent.setOrbitOutKey(FAST_RATE);
}
else if (dy < -mVertSlopNear)
{
// Zoom out slow
gAgent.unlockView();
gAgent.setOrbitOutKey(getOrbitRate());
}
}
// Only used for drawing
void LLJoystickCameraZoom::setToggleState( BOOL top, BOOL bottom )
{
mInTop = top;
mInBottom = bottom;
}
void LLJoystickCameraZoom::draw()
{
if( mInTop )
{
mPlusInImage->draw(0,0);
}
else
if( mInBottom )
{
mMinusInImage->draw(0,0);
}
else
{
getImageUnselected()->draw( 0, 0 );
}
if (sDebugRects)
{
drawDebugRect();
}
//// *HACK: also draw debug rectangles around currently-being-edited LLView, and any elements that are being highlighted by GUI preview code (see LLFloaterUIPreview)
//std::set<LLView*>::iterator iter = std::find(sPreviewHighlightedElements.begin(), sPreviewHighlightedElements.end(), this);
//if ((sEditingUI && this == sEditingUIView) || (iter != sPreviewHighlightedElements.end() && sDrawPreviewHighlights))
//{
// drawDebugRect();
//}
}
void LLJoystickCameraZoom::updateSlop()
{
mVertSlopNear = getRect().getHeight() / 4;
mVertSlopFar = getRect().getHeight() / 2;
mHorizSlopNear = getRect().getWidth() / 4;
mHorizSlopFar = getRect().getWidth() / 2;
// Compute initial mouse offset based on initial quadrant.
// Place the mouse evenly between the near and far zones.
switch (mInitialQuadrant)
{
case JQ_ORIGIN:
mInitialOffset.set(0, 0);
break;
case JQ_UP:
mInitialOffset.mX = 0;
mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2;
break;
case JQ_DOWN:
mInitialOffset.mX = 0;
mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2;
break;
case JQ_LEFT:
mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
break;
case JQ_RIGHT:
mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2;
mInitialOffset.mY = 0;
break;
default:
llerrs << "LLJoystick::LLJoystick() - bad switch case" << llendl;
break;
}
return;
}
F32 LLJoystickCameraZoom::getOrbitRate()
{
F32 time = getElapsedHeldDownTime();
if( time < NUDGE_TIME )
{
F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME;
// llinfos << "rate " << rate << " time " << time << llendl;
return rate;
}
else
{
return 1;
}
}