/** * @file llmoveview.cpp * @brief Container for movement buttons like forward, left, fly * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ #include "llviewerprecompiledheaders.h" #include "llmoveview.h" // Library includes #include "indra_constants.h" #include "llparcel.h" // Viewer includes #include "llagent.h" #include "llagentcamera.h" #include "llvoavatarself.h" // to check gAgentAvatarp->isSitting() #include "llbottomtray.h" #include "llbutton.h" #include "llfirstuse.h" #include "llfloaterreg.h" #include "llhints.h" #include "lljoystickbutton.h" #include "lluictrlfactory.h" #include "llviewerwindow.h" #include "llviewercontrol.h" #include "llselectmgr.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" #include "lltooltip.h" // // Constants // const F32 MOVE_BUTTON_DELAY = 0.0f; const F32 YAW_NUDGE_RATE = 0.05f; // fraction of normal speed const F32 NUDGE_TIME = 0.25f; // in seconds const std::string BOTTOM_TRAY_BUTTON_NAME = "movement_btn"; // // Member functions // // protected LLFloaterMove::LLFloaterMove(const LLSD& key) : LLTransientDockableFloater(NULL, true, key), mForwardButton(NULL), mBackwardButton(NULL), mTurnLeftButton(NULL), mTurnRightButton(NULL), mMoveUpButton(NULL), mMoveDownButton(NULL), mModeActionsPanel(NULL), mCurrentMode(MM_WALK) { } LLFloaterMove::~LLFloaterMove() { // Ensure LLPanelStandStopFlying panel is not among floater's children. See EXT-8458. setVisible(FALSE); // Otherwise it can be destroyed and static pointer in LLPanelStandStopFlying::getInstance() will become invalid. // Such situation was possible when LLFloaterReg returns "dead" instance of floater. // Should not happen after LLFloater::destroy was modified to remove "dead" instances from LLFloaterReg. } // virtual BOOL LLFloaterMove::postBuild() { setIsChrome(TRUE); setTitleVisible(TRUE); // restore title visibility after chrome applying updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) LLDockableFloater::postBuild(); // Code that implements floater buttons toggling when user moves via keyboard is located in LLAgent::propagate() mForwardButton = getChild<LLJoystickAgentTurn>("forward btn"); mForwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mBackwardButton = getChild<LLJoystickAgentTurn>("backward btn"); mBackwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mSlideLeftButton = getChild<LLJoystickAgentSlide>("move left btn"); mSlideLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mSlideRightButton = getChild<LLJoystickAgentSlide>("move right btn"); mSlideRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mTurnLeftButton = getChild<LLButton>("turn left btn"); mTurnLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mTurnLeftButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnLeft, this)); mTurnRightButton = getChild<LLButton>("turn right btn"); mTurnRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mTurnRightButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnRight, this)); mMoveUpButton = getChild<LLButton>("move up btn"); mMoveUpButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mMoveUpButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveUp, this)); mMoveDownButton = getChild<LLButton>("move down btn"); mMoveDownButton->setHeldDownDelay(MOVE_BUTTON_DELAY); mMoveDownButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveDown, this)); mModeActionsPanel = getChild<LLPanel>("panel_modes"); LLButton* btn; btn = getChild<LLButton>("mode_walk_btn"); btn->setCommitCallback(boost::bind(&LLFloaterMove::onWalkButtonClick, this)); btn = getChild<LLButton>("mode_run_btn"); btn->setCommitCallback(boost::bind(&LLFloaterMove::onRunButtonClick, this)); btn = getChild<LLButton>("mode_fly_btn"); btn->setCommitCallback(boost::bind(&LLFloaterMove::onFlyButtonClick, this)); initModeTooltips(); initModeButtonMap(); initMovementMode(); LLViewerParcelMgr::getInstance()->addAgentParcelChangedCallback(LLFloaterMove::sUpdateFlyingStatus); return TRUE; } // *NOTE: we assume that setVisible() is called on floater close. // virtual void LLFloaterMove::setVisible(BOOL visible) { // Do nothing with Stand/Stop Flying panel in excessive calls of this method (from LLTransientFloaterMgr?). if (getVisible() == visible) { LLTransientDockableFloater::setVisible(visible); return; } if (visible) { LLFirstUse::notMoving(false); // Attach the Stand/Stop Flying panel. LLPanelStandStopFlying* ssf_panel = LLPanelStandStopFlying::getInstance(); ssf_panel->reparent(this); const LLRect& mode_actions_rect = mModeActionsPanel->getRect(); ssf_panel->setOrigin(mode_actions_rect.mLeft, mode_actions_rect.mBottom); } else { // Detach the Stand/Stop Flying panel. LLPanelStandStopFlying::getInstance()->reparent(NULL); } LLTransientDockableFloater::setVisible(visible); } // static F32 LLFloaterMove::getYawRate( F32 time ) { if( time < NUDGE_TIME ) { F32 rate = YAW_NUDGE_RATE + time * (1 - YAW_NUDGE_RATE)/ NUDGE_TIME; return rate; } else { return 1.f; } } // static void LLFloaterMove::setFlyingMode(BOOL fly) { LLFloaterMove* instance = LLFloaterReg::findTypedInstance<LLFloaterMove>("moveview"); if (instance) { instance->setFlyingModeImpl(fly); LLVOAvatarSelf* avatar_object = gAgentAvatarp; bool is_sitting = avatar_object && (avatar_object->getRegion() != NULL) && (!avatar_object->isDead()) && avatar_object->isSitting(); instance->showModeButtons(!fly && !is_sitting); } if (fly) { LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); } else { LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); } } //static void LLFloaterMove::setAlwaysRunMode(bool run) { LLFloaterMove* instance = LLFloaterReg::findTypedInstance<LLFloaterMove>("moveview"); if (instance) { instance->setAlwaysRunModeImpl(run); } } void LLFloaterMove::setFlyingModeImpl(BOOL fly) { updateButtonsWithMovementMode(fly ? MM_FLY : (gAgent.getAlwaysRun() ? MM_RUN : MM_WALK)); } void LLFloaterMove::setAlwaysRunModeImpl(bool run) { if (!gAgent.getFlying()) { updateButtonsWithMovementMode(run ? MM_RUN : MM_WALK); } } //static void LLFloaterMove::setSittingMode(BOOL bSitting) { if (bSitting) { LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); } else { LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); // show "Stop Flying" button if needed. EXT-871 if (gAgent.getFlying()) { LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); } } enableInstance(!bSitting); } // protected void LLFloaterMove::turnLeft() { F32 time = mTurnLeftButton->getHeldDownTime(); gAgent.moveYaw( getYawRate( time ) ); } // protected void LLFloaterMove::turnRight() { F32 time = mTurnRightButton->getHeldDownTime(); gAgent.moveYaw( -getYawRate( time ) ); } // protected void LLFloaterMove::moveUp() { // Jumps or flys up, depending on fly state gAgent.moveUp(1); } // protected void LLFloaterMove::moveDown() { // Crouches or flys down, depending on fly state gAgent.moveUp(-1); } ////////////////////////////////////////////////////////////////////////// // Private Section: ////////////////////////////////////////////////////////////////////////// void LLFloaterMove::onWalkButtonClick() { setMovementMode(MM_WALK); } void LLFloaterMove::onRunButtonClick() { setMovementMode(MM_RUN); } void LLFloaterMove::onFlyButtonClick() { setMovementMode(MM_FLY); } void LLFloaterMove::setMovementMode(const EMovementMode mode) { mCurrentMode = mode; gAgent.setFlying(MM_FLY == mode); // attempts to set avatar flying can not set it real flying in some cases. // For ex. when avatar fell down & is standing up. // So, no need to continue processing FLY mode. See EXT-1079 if (MM_FLY == mode && !gAgent.getFlying()) { return; } switch (mode) { case MM_RUN: gAgent.setAlwaysRun(); gAgent.setRunning(); break; case MM_WALK: gAgent.clearAlwaysRun(); gAgent.clearRunning(); break; default: //do nothing for other modes (MM_FLY) break; } // tell the simulator. gAgent.sendWalkRun(gAgent.getAlwaysRun()); updateButtonsWithMovementMode(mode); bool bHideModeButtons = MM_FLY == mode || (isAgentAvatarValid() && gAgentAvatarp->isSitting()); showModeButtons(!bHideModeButtons); } void LLFloaterMove::updateButtonsWithMovementMode(const EMovementMode newMode) { setModeTooltip(newMode); setModeButtonToggleState(newMode); setModeTitle(newMode); } void LLFloaterMove::initModeTooltips() { control_tooltip_map_t walkTipMap; walkTipMap.insert(std::make_pair(mForwardButton, getString("walk_forward_tooltip"))); walkTipMap.insert(std::make_pair(mBackwardButton, getString("walk_back_tooltip"))); walkTipMap.insert(std::make_pair(mSlideLeftButton, getString("walk_left_tooltip"))); walkTipMap.insert(std::make_pair(mSlideRightButton, getString("walk_right_tooltip"))); walkTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); walkTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); mModeControlTooltipsMap[MM_WALK] = walkTipMap; control_tooltip_map_t runTipMap; runTipMap.insert(std::make_pair(mForwardButton, getString("run_forward_tooltip"))); runTipMap.insert(std::make_pair(mBackwardButton, getString("run_back_tooltip"))); runTipMap.insert(std::make_pair(mSlideLeftButton, getString("run_left_tooltip"))); runTipMap.insert(std::make_pair(mSlideRightButton, getString("run_right_tooltip"))); runTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); runTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); mModeControlTooltipsMap[MM_RUN] = runTipMap; control_tooltip_map_t flyTipMap; flyTipMap.insert(std::make_pair(mForwardButton, getString("fly_forward_tooltip"))); flyTipMap.insert(std::make_pair(mBackwardButton, getString("fly_back_tooltip"))); flyTipMap.insert(std::make_pair(mSlideLeftButton, getString("fly_left_tooltip"))); flyTipMap.insert(std::make_pair(mSlideRightButton, getString("fly_right_tooltip"))); flyTipMap.insert(std::make_pair(mMoveUpButton, getString("fly_up_tooltip"))); flyTipMap.insert(std::make_pair(mMoveDownButton, getString("fly_down_tooltip"))); mModeControlTooltipsMap[MM_FLY] = flyTipMap; setModeTooltip(MM_WALK); } void LLFloaterMove::initModeButtonMap() { mModeControlButtonMap[MM_WALK] = getChild<LLButton>("mode_walk_btn"); mModeControlButtonMap[MM_RUN] = getChild<LLButton>("mode_run_btn"); mModeControlButtonMap[MM_FLY] = getChild<LLButton>("mode_fly_btn"); } void LLFloaterMove::initMovementMode() { EMovementMode initMovementMode = gAgent.getAlwaysRun() ? MM_RUN : MM_WALK; if (gAgent.getFlying()) { initMovementMode = MM_FLY; } setMovementMode(initMovementMode); if (isAgentAvatarValid()) { showModeButtons(!gAgentAvatarp->isSitting()); } } void LLFloaterMove::setModeTooltip(const EMovementMode mode) { llassert_always(mModeControlTooltipsMap.end() != mModeControlTooltipsMap.find(mode)); control_tooltip_map_t controlsTipMap = mModeControlTooltipsMap[mode]; control_tooltip_map_t::const_iterator it = controlsTipMap.begin(); for (; it != controlsTipMap.end(); ++it) { LLView* ctrl = it->first; std::string tooltip = it->second; ctrl->setToolTip(tooltip); } } void LLFloaterMove::setModeTitle(const EMovementMode mode) { std::string title; switch(mode) { case MM_WALK: title = getString("walk_title"); break; case MM_RUN: title = getString("run_title"); break; case MM_FLY: title = getString("fly_title"); break; default: // title should be provided for all modes llassert(false); break; } setTitle(title); } /** * Updates position of the floater to be center aligned with Move button. */ void LLFloaterMove::updatePosition() { LLBottomTray* tray = LLBottomTray::getInstance(); if (!tray) return; LLButton* movement_btn = tray->findChild<LLButton>(BOTTOM_TRAY_BUTTON_NAME); if (movement_btn) { //align centers of a button and a floater S32 x = movement_btn->calcScreenRect().getCenterX() - getRect().getWidth()/2; S32 y = 0; if (!mModeActionsPanel->getVisible()) { y = mModeActionsPanel->getRect().getHeight(); } setOrigin(x, y); } } //static void LLFloaterMove::sUpdateFlyingStatus() { LLFloaterMove *floater = LLFloaterReg::findTypedInstance<LLFloaterMove>("moveview"); if (floater) floater->mModeControlButtonMap[MM_FLY]->setEnabled(gAgent.canFly()); } void LLFloaterMove::showModeButtons(BOOL bShow) { if (mModeActionsPanel->getVisible() == bShow) return; mModeActionsPanel->setVisible(bShow); } //static void LLFloaterMove::enableInstance(BOOL bEnable) { LLFloaterMove* instance = LLFloaterReg::findTypedInstance<LLFloaterMove>("moveview"); if (instance) { if (gAgent.getFlying()) { instance->showModeButtons(FALSE); } else { instance->showModeButtons(bEnable); } } } void LLFloaterMove::onOpen(const LLSD& key) { LLButton *anchor_panel = LLBottomTray::getInstance()->getChild<LLButton>("movement_btn"); if (gAgent.getFlying()) { setFlyingMode(TRUE); showModeButtons(FALSE); } if (isAgentAvatarValid() && gAgentAvatarp->isSitting()) { setSittingMode(TRUE); showModeButtons(FALSE); } setDockControl(new LLDockControl( anchor_panel, this, getDockTongue(), LLDockControl::TOP)); sUpdateFlyingStatus(); } //virtual void LLFloaterMove::setDocked(bool docked, bool pop_on_undock/* = true*/) { LLTransientDockableFloater::setDocked(docked, pop_on_undock); } void LLFloaterMove::setModeButtonToggleState(const EMovementMode mode) { llassert_always(mModeControlButtonMap.end() != mModeControlButtonMap.find(mode)); mode_control_button_map_t::const_iterator it = mModeControlButtonMap.begin(); for (; it != mModeControlButtonMap.end(); ++it) { it->second->setToggleState(FALSE); } mModeControlButtonMap[mode]->setToggleState(TRUE); } /************************************************************************/ /* LLPanelStandStopFlying */ /************************************************************************/ LLPanelStandStopFlying::LLPanelStandStopFlying() : mStandButton(NULL), mStopFlyingButton(NULL), mAttached(false) { // make sure we have the only instance of this class static bool b = true; llassert_always(b); b=false; } // static LLPanelStandStopFlying* LLPanelStandStopFlying::getInstance() { static LLPanelStandStopFlying* panel = getStandStopFlyingPanel(); return panel; } //static void LLPanelStandStopFlying::setStandStopFlyingMode(EStandStopFlyingMode mode) { LLPanelStandStopFlying* panel = getInstance(); if (mode == SSFM_STAND) { LLFirstUse::sit(); LLFirstUse::notMoving(false); } panel->mStandButton->setVisible(SSFM_STAND == mode); panel->mStopFlyingButton->setVisible(SSFM_STOP_FLYING == mode); //visibility of it should be updated after updating visibility of the buttons panel->setVisible(TRUE); } //static void LLPanelStandStopFlying::clearStandStopFlyingMode(EStandStopFlyingMode mode) { LLPanelStandStopFlying* panel = getInstance(); switch(mode) { case SSFM_STAND: panel->mStandButton->setVisible(FALSE); break; case SSFM_STOP_FLYING: panel->mStopFlyingButton->setVisible(FALSE); break; default: llerrs << "Unexpected EStandStopFlyingMode is passed: " << mode << llendl; } } BOOL LLPanelStandStopFlying::postBuild() { mStandButton = getChild<LLButton>("stand_btn"); mStandButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStandButtonClick, this)); mStandButton->setCommitCallback(boost::bind(&LLFloaterMove::enableInstance, TRUE)); mStandButton->setVisible(FALSE); LLHints::registerHintTarget("stand_btn", mStandButton->getHandle()); mStopFlyingButton = getChild<LLButton>("stop_fly_btn"); //mStopFlyingButton->setCommitCallback(boost::bind(&LLFloaterMove::setFlyingMode, FALSE)); mStopFlyingButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStopFlyingButtonClick, this)); mStopFlyingButton->setVisible(FALSE); return TRUE; } //virtual void LLPanelStandStopFlying::setVisible(BOOL visible) { //we dont need to show the panel if these buttons are not activated if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) visible = false; if (visible) { updatePosition(); } // do not change parent visibility in case panel is attached into Move Floater: EXT-3632, EXT-4646 if (!mAttached) { //change visibility of parent layout_panel to animate in/out. EXT-2504 if (getParent()) getParent()->setVisible(visible); } // also change own visibility to avoid displaying the panel in mouselook (broken when EXT-2504 was implemented). // See EXT-4718. LLPanel::setVisible(visible); } BOOL LLPanelStandStopFlying::handleToolTip(S32 x, S32 y, MASK mask) { LLToolTipMgr::instance().unblockToolTips(); if (mStandButton->getVisible()) { LLToolTipMgr::instance().show(mStandButton->getToolTip()); } else if (mStopFlyingButton->getVisible()) { LLToolTipMgr::instance().show(mStopFlyingButton->getToolTip()); } return LLPanel::handleToolTip(x, y, mask); } void LLPanelStandStopFlying::reparent(LLFloaterMove* move_view) { LLPanel* parent = dynamic_cast<LLPanel*>(getParent()); if (!parent) { llwarns << "Stand/stop flying panel parent is unset, already attached?: " << mAttached << ", new parent: " << (move_view == NULL ? "NULL" : "Move Floater") << llendl; return; } if (move_view != NULL) { llassert(move_view != parent); // sanity check // Save our original container. if (!mOriginalParent.get()) mOriginalParent = parent->getHandle(); // Attach to movement controls. parent->removeChild(this); move_view->addChild(this); // Origin must be set by movement controls. mAttached = true; } else { if (!mOriginalParent.get()) { llwarns << "Original parent of the stand / stop flying panel not found" << llendl; return; } // Detach from movement controls. parent->removeChild(this); mOriginalParent.get()->addChild(this); // update parent with self visibility (it is changed in setVisible()). EXT-4743 mOriginalParent.get()->setVisible(getVisible()); mAttached = false; updatePosition(); // don't defer until next draw() to avoid flicker } } ////////////////////////////////////////////////////////////////////////// // Private Section ////////////////////////////////////////////////////////////////////////// //static LLPanelStandStopFlying* LLPanelStandStopFlying::getStandStopFlyingPanel() { LLPanelStandStopFlying* panel = new LLPanelStandStopFlying(); panel->buildFromFile("panel_stand_stop_flying.xml"); panel->setVisible(FALSE); //LLUI::getRootView()->addChild(panel); llinfos << "Build LLPanelStandStopFlying panel" << llendl; panel->updatePosition(); return panel; } void LLPanelStandStopFlying::onStandButtonClick() { LLFirstUse::sit(false); LLSelectMgr::getInstance()->deselectAllForStandingUp(); gAgent.setControlFlags(AGENT_CONTROL_STAND_UP); setFocus(FALSE); // EXT-482 mStandButton->setVisible(FALSE); // force visibility changing to avoid seeing Stand & Move buttons at once. } void LLPanelStandStopFlying::onStopFlyingButtonClick() { gAgent.setFlying(FALSE); setFocus(FALSE); // EXT-482 setVisible(FALSE); } /** * Updates position of the Stand & Stop Flying panel to be center aligned with Move button. */ void LLPanelStandStopFlying::updatePosition() { LLBottomTray* tray = LLBottomTray::getInstance(); if (!tray || mAttached) return; LLButton* movement_btn = tray->findChild<LLButton>(BOTTOM_TRAY_BUTTON_NAME); S32 x = 0; if (movement_btn) { // Align centers of the button and the panel. x = movement_btn->calcScreenRect().getCenterX() - getRect().getWidth()/2; } else { x = tray->calcScreenRect().getCenterX() - getRect().getWidth()/2; } setOrigin(x, 0); } // EOF