/** * RobloxView.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "RobloxView.h" // Qt Headers #include #include #include #include #include #include // Roblox Headers #include "rbx/Tasks/Coordinator.h" #include "rbx/CEvent.h" #include "rbx/SystemUtil.h" #include "Network/Players.h" #include "Network/api.h" #include "script/script.h" #include "v8datamodel/BaseRenderJob.h" #include "v8datamodel/Workspace.h" #include "v8datamodel/Game.h" #include "v8datamodel/UserController.h" #include "v8datamodel/ContentProvider.h" #include "v8datamodel/ToolMouseCommand.h" #include "v8datamodel/UserInputService.h" #include "v8datamodel/Decal.h" #include "v8datamodel/GameBasicSettings.h" #include "util/FileSystem.h" #include "util/standardout.h" #include "util/Profiling.h" #include "util/ContentId.h" #include "v8xml/XmlSerializer.h" #include "v8xml/Serializer.h" #include "GfxBase/ViewBase.h" #include "GfxBase/RenderSettings.h" #include "GfxBase/FrameRateManager.h" #include "RenderSettingsItem.h" #include "tool/ICancelableTool.h" #include "AppDraw/Draw.h" #ifdef _WIN32 #include "LogManager.h" #endif #ifdef Q_WS_MAC #include "BreakpadCrashReporter.h" #endif // Roblox Studio Headers #include "RobloxMainWindow.h" #include "UserInput.h" #include "UserInputUtil.h" #include "ogrewidget.h" #include "FunctionMarshaller.h" #include "RobloxCustomWidgets.h" #include "RenderStatsItem.h" #include "StudioUtilities.h" #include "RobloxSettings.h" #include "UpdateUIManager.h" #include "AuthoringSettings.h" #include "QtUtilities.h" #include "RobloxMouseConfig.h" #include "RobloxTreeWidget.h" #include "CommonInsertWidget.h" #include "Util/RobloxGoogleAnalytics.h" #include "RobloxDocManager.h" #include "RobloxIDEDoc.h" #include "rbx/Profiler.h" LOGGROUP(HardwareMouse) LOGGROUP(TaskSchedulerTiming) LOGVARIABLE(RenderRequest, 0) FASTFLAG(GoogleAnalyticsTrackingEnabled) FASTFLAG(StudioSeparateActionByActivationMethod) FASTFLAG(StudioRecordToolboxInsert) FASTFLAGVARIABLE(DirectX11Enable, false) FASTFLAG(UserBetterInertialScrolling) FASTINTVARIABLE(StudioRightClickBuffer, 500) FASTINTVARIABLE(StudioRightClickDelay, 150) FASTINTVARIABLE(StudioRightClickMax, 700) // should point to local file that is the default cursor for 3D window static std::string defaultMouseCursorPath = "Textures/advCursor-default.png"; class RobloxView::RenderJobCyclic : public RBX::BaseRenderJob { shared_ptr m_pDataModel; RobloxView *m_pRbxView; QOgreWidget *m_pQOgreWidget; bool viewUpdateRequested; double m_timestamp; bool isBusySkipRender; public: RBX::CEvent updateView; RBX::CEvent viewUpdated; RenderJobCyclic(RobloxView *pRbxView, shared_ptr pDataModel, QOgreWidget* pQOgreWidget) : RBX::BaseRenderJob( CRenderSettingsItem::singleton().getMinFrameRate(), CRenderSettingsItem::singleton().getMaxFrameRate(), pDataModel) , m_pRbxView(pRbxView) , m_pDataModel(pDataModel) , m_pQOgreWidget(pQOgreWidget) , viewUpdateRequested(false) , updateView(false) , viewUpdated(false) , isBusySkipRender(false) { cyclicExecutive = true; isAwake = false; m_timestamp = RBX::Time::nowFastSec(); } ~RenderJobCyclic() { } double getTimestamp() { return m_timestamp; } void setIsBusySkipRender(bool value) { isBusySkipRender = value; } bool ogreWidgetHasFocus() { return m_pQOgreWidget->hasApplicationFocus(); } bool tryJobAgain() { if (!isAwake) { if (!viewUpdateRequested) { m_timestamp = RBX::Time::nowFastSec(); //do view update // Prevent Double Events QCoreApplication::removePostedEvents(m_pQOgreWidget, OGRE_VIEW_UPDATE); QApplication::postEvent(m_pQOgreWidget, new RobloxCustomEvent(OGRE_VIEW_UPDATE)); viewUpdateRequested = true; } return true; } return false; } // Have to define to compile virtual RBX::Time::Interval sleepTime(const Stats&) { return RBX::Time::Interval(0); } virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats&) { lastRenderTime = RBX::Time::now(); shared_ptr pDataModel(m_pDataModel); if (!pDataModel) return RBX::TaskScheduler::Done; RBX::DataModel::scoped_write_request request(pDataModel.get()); isAwake = false; // This logic has to be set by RobloxView::updateView // This is to prevent Deadlocks, and to not follow-through the // locking nature of RenderJob if the UIThread is busy. if (!isBusySkipRender) { //start view update updateView.Set(); //wait till view is updated viewUpdated.Wait(); } viewUpdateRequested = false; isBusySkipRender = false; return RBX::TaskScheduler::Stepped; } }; //Job code has been taken from Client/Roblox/RobloxWnd.cpp class RobloxView::RenderRequestJob : public RBX::DataModelJob { RobloxView *m_pRbxView; boost::shared_ptr m_pSequence; shared_ptr m_pDataModel; QOgreWidget *m_pQOgreWidget; volatile bool m_bIsAwake; double m_timestamp; public: RenderRequestJob(RobloxView *pRbxView, shared_ptr pDataModel, QOgreWidget* pQOgreWidget) : RBX::DataModelJob("RenderRequest", RBX::DataModelJob::Render, false, pDataModel, RBX::Time::Interval(0)) , m_pRbxView(pRbxView) , m_pDataModel(pDataModel) , m_pQOgreWidget(pQOgreWidget) , m_bIsAwake(false) { cyclicExecutive = true; m_timestamp = RBX::Time::nowFastSec(); // Fixed Rally: DE2449 for Mouse Performance // Created Rally: DE2557 for Rendering performance improvements // KLUDGE - For Mac Mouse issue, especially with Trackpad // Qt mouse events starts throttling when using trackpad // tieing the render job with heartbeat somehow breaks the chain & gives qt chance to process mouse trackpad events // The disadvantage of this approach is that the Render job is now maxed out at 30 fps. // Need to find the root cause of why the problem gets resolved with tieing to heartbeat job // Without this fix Mouse performance is 0.5 to 2 fps on CTF Model with Trackpad, unusable // With this fix Mouse performance is 30 to 50 fps on CTF Model with Trackpad, amazing fast #ifndef _WIN32 m_pSequence.reset(new RBX::Tasks::Sequence()); addCoordinator(m_pSequence); m_pDataModel->create()->getHeartbeat()->addCoordinator(m_pSequence); #endif // Force rendering and physics to happen in lock-step if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics) { #ifdef _WIN32 m_pSequence.reset(new RBX::Tasks::Sequence()); addCoordinator(m_pSequence); #endif m_pDataModel->create()->getPhysicsJob()->addCoordinator(m_pSequence); } } ~RenderRequestJob() { if (m_pSequence) { if (RBX::RunService* pRunService = m_pDataModel->find()) { #ifndef _WIN32 pRunService->getHeartbeat()->removeCoordinator(m_pSequence); #endif if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics) pRunService->getPhysicsJob()->removeCoordinator(m_pSequence); } m_pSequence.reset(); } } void wake() { m_bIsAwake = true; RBX::TaskScheduler::singleton().reschedule(shared_from_this()); } virtual RBX::Time::Interval sleepTime(const Stats& stats) { if (m_bIsAwake) { float frameRate = RBX::DataModel::throttleAt30Fps ? CRenderSettingsItem::singleton().getMaxFrameRate() : 1000; // check for application focus if (UpdateUIManager::Instance().isBusy() || !m_pQOgreWidget->hasApplicationFocus()) frameRate *= (100 - qBound(0, AuthoringSettings::singleton().renderThrottlePercentage, 100)) / 100.0f; return computeStandardSleepTime(stats,frameRate); } return RBX::Time::Interval::max(); } virtual Job::Error error(const Stats& stats) { if (m_bIsAwake) return computeStandardError(stats, RBX::DataModel::throttleAt30Fps ? CRenderSettingsItem::singleton().getMinFrameRate() : 1000); return Job::Error(); } double getTimestamp() { return m_timestamp; } virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats&) { m_timestamp = RBX::Time::nowFastSec(); m_bIsAwake = false; //do view update QApplication::postEvent(m_pQOgreWidget, new RobloxCustomEvent(OGRE_VIEW_UPDATE)); return RBX::TaskScheduler::Stepped; } }; class RobloxView::RenderJob : public RBX::DataModelJob { boost::weak_ptr m_pDataModel; RBX::Time m_LastRenderTime; volatile bool m_bIsAwake; public: RenderJob(RobloxView *, shared_ptr pDataModel) : RBX::DataModelJob("Render", RBX::DataModelJob::Render, false, pDataModel, RBX::Time::Interval(.02)) , m_pDataModel(pDataModel) , m_bIsAwake(false) , updateView(false) , viewUpdated(false) { cyclicExecutive = true; } RBX::CEvent updateView; RBX::CEvent viewUpdated; void wake() { m_bIsAwake = true; RBX::TaskScheduler::singleton().reschedule(shared_from_this()); } RBX::Time::Interval timeSinceLastRender() const { return RBX::Time::now() - m_LastRenderTime; } virtual RBX::Time::Interval sleepTime(const Stats&) { return m_bIsAwake ? RBX::Time::Interval(0) : RBX::Time::Interval::max(); } virtual Job::Error error(const Stats& stats) { if (!m_bIsAwake) return Job::Error(); Job::Error result; if (RBX::TaskScheduler::singleton().isCyclicExecutive() && cyclicExecutive ) { result = computeStandardErrorCyclicExecutiveSleeping(stats, CRenderSettingsItem::singleton().getMaxFrameRate()); } else { result = computeStandardError(stats, CRenderSettingsItem::singleton().getMinFrameRate()); } return result; } virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats&) { m_LastRenderTime = RBX::Time::now(); shared_ptr pDataModel(m_pDataModel.lock()); if (!pDataModel) return RBX::TaskScheduler::Done; RBX::DataModel::scoped_write_request request(pDataModel.get()); m_bIsAwake = false; //start view update updateView.Set(); //wait till view is updated viewUpdated.Wait(); return RBX::TaskScheduler::Stepped; } }; class RobloxView::WrapMouseJob : public RBX::DataModelJob { RobloxView *m_pRbxView; RBX::DataModel *m_pDataModel; public: WrapMouseJob(RobloxView* pRbxView, shared_ptr pDataModel) : RBX::DataModelJob("WrapMouse", RBX::DataModelJob::Write, true, pDataModel, RBX::Time::Interval(0)) , m_pRbxView(pRbxView) , m_pDataModel(pDataModel.get()) { cyclicExecutive = true; } virtual RBX::Time::Interval sleepTime(const Stats& stats) { return computeStandardSleepTime(stats, 60); } virtual Job::Error error(const Stats& stats) { Job::Error result = computeStandardError(stats, 60); return result; } virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats&) { RBX::DataModel::scoped_write_request request(m_pDataModel); if(m_pRbxView) m_pRbxView->processWrapMouse(); return RBX::TaskScheduler::Stepped; } }; static RBX::ViewBase* createGameWindow(QOgreWidget *pQtWrapperWindow); boost::shared_ptr RobloxView::createGameView(QOgreWidget *pQtWrapperWindow) { return boost::shared_ptr(createGameWindow(pQtWrapperWindow)); } RobloxView::RobloxView(QOgreWidget *pQtWrapperWidget, boost::shared_ptr game, boost::shared_ptr view, bool vr) : m_pViewBase(view) , m_pDataModel(game->getDataModel()) , m_pQtWrapperWidget(pQtWrapperWidget) , m_bIgnoreCursorChange(false) , m_hoverOverAccum(0) , m_hoverOverLastTime(RBX::Time::nowFast()) , m_rightClickContextAllowed(true) , m_rightClickMenuPending(false) , m_DataModelHashNeeded(true) , m_mouseCursorHidden(false) , m_updateViewStepId(0) { RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\tRobloxView::RobloxView - start"); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tUserInput"); m_pUserInput.reset(new UserInput(this,game->getDataModel())); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tMarshaller"); m_pMarshaller = RBX::FunctionMarshaller::GetWindow(); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tProfilingSection"); m_profilingSection.reset(new RBX::Profiling::CodeProfiler("Render")); //set bounds appropriately setBounds(m_pQtWrapperWidget->width(), m_pQtWrapperWidget->height()); // set this to Qt widget. Internally this may depend on the roblox view being // mostly set up. In particular, this may depend on the window bounds being set // before this call is made. m_pQtWrapperWidget->setRobloxView(this); //CRenderSettingsItem::singleton().runProfiler(false); //Bind view to the Workspace RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tBindToWorkspace"); bindToWorkspace(); // Enable or disable VR m_pViewBase->enableVR(vr); if (RbxViewCyclicFlagsEnabled()) { RBX::Log::current()->writeEntry(RBX::Log::Information, "\t\t\tRenderJobCyclic"); m_pRenderJobCyclic = shared_ptr(new RenderJobCyclic(this, m_pDataModel, m_pQtWrapperWidget)); } else { //Create the rendering jobs RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tRenderRequestJob"); m_pRenderRequestJob = shared_ptr(new RenderRequestJob(this, m_pDataModel, m_pQtWrapperWidget)); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tRenderJob"); m_pRenderJob = shared_ptr(new RenderJob(this, m_pDataModel)); } RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tWrapMouseJob"); m_pWrapMouseJob = shared_ptr(new WrapMouseJob(this, m_pDataModel)); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tConfigureStats"); configureStats(); if (RbxViewCyclicFlagsEnabled()) { RBX::TaskScheduler::singleton().add(m_pRenderJobCyclic); RBX::TaskScheduler::singleton().add(m_pWrapMouseJob); } else { RBX::TaskScheduler::singleton().add(m_pRenderJob); RBX::TaskScheduler::singleton().add(m_pRenderRequestJob); RBX::TaskScheduler::singleton().add(m_pWrapMouseJob); } mouseCursorChangeConnection = m_pUserInput->cursorIdChangedSignal.connect( boost::bind(&RobloxView::handleMouseCursorChange, this) ); if (m_pDataModel && m_pDataModel.get()) { if( RBX::UserInputService* userInputService = RBX::ServiceProvider::create(m_pDataModel.get()) ) { handleMouseIconEnabledEvent(userInputService->getMouseIconEnabled()); mouseCursorEnabledConnection = userInputService->mouseIconEnabledEvent.connect(boost::bind(&RobloxView::handleMouseIconEnabledEvent, this, _1)); m_textBoxGainFocus = userInputService->textBoxGainFocus.connect(boost::bind(&RobloxView::handleTextBoxGainFocus, this)); m_textBoxReleaseFocus = userInputService->textBoxReleaseFocus.connect(boost::bind(&RobloxView::handleTextBoxReleaseFocus, this)); } } m_lastRightClickBlockingEventDate = QDateTime::currentDateTime(); RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\tRobloxView::RobloxView - end"); } RobloxView::~RobloxView(void) { { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); if(m_renderStatsItem) { m_renderStatsItem->setParent(NULL); m_renderStatsItem.reset(); } } if (m_pRenderJob && !RbxViewCyclicFlagsEnabled()) { m_pRenderJob->viewUpdated.Set(); // viewUpdateJob can deadlock if we don't process the marshaller queue boost::function callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller); RBX::TaskScheduler::singleton().removeBlocking(m_pRenderJob, callback); m_pRenderJob.reset(); } if (m_pRenderRequestJob && !RbxViewCyclicFlagsEnabled()) { boost::function callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller); RBX::TaskScheduler::singleton().removeBlocking(m_pRenderRequestJob, callback); m_pRenderRequestJob.reset(); } if (m_pRenderJobCyclic && RbxViewCyclicFlagsEnabled()) { m_pRenderJobCyclic->viewUpdated.Set(); boost::function callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller); RBX::TaskScheduler::singleton().removeBlocking(m_pRenderJobCyclic, callback); m_pRenderJobCyclic.reset(); } if (m_pWrapMouseJob) { boost::function callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller); RBX::TaskScheduler::singleton().removeBlocking(m_pWrapMouseJob, callback); m_pWrapMouseJob.reset(); } // RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled // renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view // in the marshaller queue. // This makes sure that all pending marshalled events are processed to avoid a use after free. m_pMarshaller->ProcessMessages(); { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); RBX::ControllerService* service = RBX::ServiceProvider::create(m_pDataModel.get()); service->setHardwareDevice(NULL); m_pViewBase->bindWorkspace(boost::shared_ptr()); mouseCursorChangeConnection.disconnect(); mouseCursorEnabledConnection.disconnect(); m_pUserInput.reset(); } RBX::FunctionMarshaller::ReleaseWindow(m_pMarshaller); // First destroy the view before closing the DataModel m_pViewBase.reset(); //TODO - ROBLOXSTUDIO //Roblox::relinquishGame(game); } void RobloxView::onEvent_playerAdded(shared_ptr playerAdded) { if( RBX::Network::Player* localPlayer = RBX::Network::Players::findLocalPlayer(m_pDataModel.get()) ) if( RBX::Network::Player* thePlayerAdded = RBX::Instance::fastDynamicCast(playerAdded.get()) ) if(localPlayer == thePlayerAdded) buildGui(true); } void RobloxView::bindToWorkspace() { // we need write locks on all datamodels at this point (about to edit them outside of normal datamodel tasks) RBXASSERT(m_pDataModel->currentThreadHasWriteLock()); // Note that this code needs to be thread-sensitive CRenderSettingsItem::singleton().runProfiler(false); m_pViewBase->bindWorkspace(m_pDataModel); // we check when a player is added to players, in case we need to load in the gui (someone might add a player via cmd line) if( !StudioUtilities::isAvatarMode() ) if ( RBX::Network::Players* players = RBX::ServiceProvider::create(m_pDataModel.get()) ) m_itemAddedToPlayersConnection = players->onDemandWrite()->childAddedSignal.connect(boost::bind(&RobloxView::onEvent_playerAdded, this, _1)); RBX::ControllerService* service = RBX::ServiceProvider::create(m_pDataModel.get()); service->setHardwareDevice(m_pUserInput.get()); } void RobloxView::buildGui(bool buildInGameGui) { if(m_pViewBase) m_pViewBase->buildGui(buildInGameGui); } void RobloxView::configureStats() { // For diagnostic view RBX::Stats::StatsService* pStats = RBX::ServiceProvider::create(m_pDataModel.get()); m_renderStatsItem = RenderStatsItem::create(m_pViewBase->getRenderStats()); m_renderStatsItem->setName("Render"); m_renderStatsItem->setParent(pStats); m_renderStatsItem->createBoundChildItem(*m_profilingSection); } void RobloxView::setBounds(unsigned int width, unsigned int height) { m_Width = width; m_Height = height; if (m_pViewBase) m_pViewBase->onResize(width, height); } void RobloxView::handleMouse(RBX::InputObject::UserInputType event, RBX::InputObject::UserInputState state, int x, int y, RBX::ModCode modifiers) { if (!m_pUserInput) return; switch (event) { case RBX::InputObject::TYPE_MOUSEMOVEMENT: { G3D::Vector2 newPos(x, y); // determine delta x = x - m_previousCursorPos.x; y = y - m_previousCursorPos.y; // if we didn't move, no need to go any further if (x == 0 && y == 0) { return; } if (StudioUtilities::isAvatarMode()) { float scale = RBX::GameBasicSettings::singleton().getMouseSensitivity(); m_previousCursorPosFraction.x += x * scale; m_previousCursorPosFraction.y += y * scale; newPos = m_previousCursorPos + m_previousCursorPosFraction; x = (int) (newPos.x) - (int) (m_previousCursorPos.x); y = (int) (newPos.y) - (int) (m_previousCursorPos.y); m_previousCursorPosFraction.x -= (float) x; m_previousCursorPosFraction.y -= (float) y; setCursorPos(&newPos, m_pUserInput->isLeftMouseDown(), true, false); if (hasApplicationFocus() && !m_mouseCursorHidden) handleMouseIconEnabledEvent(false); else if (!hasApplicationFocus() && m_mouseCursorHidden) handleMouseIconEnabledEvent(true); } // remember new location m_previousCursorPos = newPos; m_rightClickContextAllowed = false; break; } case RBX::InputObject::TYPE_MOUSEBUTTON2: { if (state ==RBX::InputObject::INPUT_STATE_BEGIN) { getCursorPos(&m_mousePressedPos); m_rightClickContextAllowed = true; if (m_rightClickMenuPending) { m_rightClickContextAllowed = false; m_lastRightClickBlockingEventDate = QDateTime::currentDateTime(); } else { m_rightClickMouseDownDate = QDateTime::currentDateTime(); } } else if (state == RBX::InputObject::INPUT_STATE_END) { #ifdef _WIN32 // we want to show the cursor inside of the 3d view when right mouse rotating camera stops m_bIgnoreCursorChange = false; setCursorImage(); #endif if (RobloxMouseConfig::singleton().canOpenContextMenu(event, state) && m_rightClickContextAllowed && !m_pUserInput->areEditModeMovementKeysDown() && !RBX::Profiler::isCapturingMouseInput() && !StudioUtilities::isAvatarMode()) { m_rightClickPosition = QPoint(x, y); handleContextMenu(x,y); return; } } break; } case RBX::InputObject::TYPE_MOUSEBUTTON1: { if ((state == RBX::InputObject::INPUT_STATE_BEGIN) && ((modifiers == RBX::KMOD_LCTRL) || (modifiers == RBX::KMOD_LSHIFT))) { if(RBX::UserInputService* userInputService = RBX::ServiceProvider::find(m_pDataModel.get())) { // check is Ctrl or Shift is not pressed already RBX::KeyCode rbxKey = RBX::SDLK_UNKNOWN; if (!userInputService->isCtrlDown() && (modifiers == RBX::KMOD_LCTRL)) rbxKey = RBX::SDLK_LCTRL; else if (!userInputService->isShiftDown() && (modifiers == RBX::KMOD_LSHIFT)) rbxKey = RBX::SDLK_LSHIFT; // DE7421 - if Ctrl or Shift is not pressed already then make sure we post key press event first so multiple selections can be done if (rbxKey != RBX::SDLK_UNKNOWN) m_pUserInput->PostUserInputMessage(RBX::InputObject::TYPE_KEYBOARD, RBX::InputObject::INPUT_STATE_BEGIN, rbxKey, modifiers, NULL); } } if(state == RBX::InputObject::INPUT_STATE_END) RobloxMainWindow::get(m_pQtWrapperWidget)->m_mouseActions.increment(); break; } default: break; } m_pUserInput->PostUserInputMessage(event, state, modifiers, MAKEXYLPARAM((uint) x, (uint) y)); } void RobloxView::handleKey(RBX::InputObject::UserInputType event, RBX::InputObject::UserInputState state, RBX::KeyCode keyCode, RBX::ModCode modifiers, const char keyWithModifier, bool processed) { m_pUserInput->PostUserInputMessage(event, state, keyCode, modifiers, keyWithModifier, processed); } void RobloxView::handleFocus(bool focus) { if (m_pUserInput) m_pUserInput->PostUserInputMessage(RBX::InputObject::TYPE_FOCUS, focus ? RBX::InputObject::INPUT_STATE_BEGIN : RBX::InputObject::INPUT_STATE_END, 0, 0); } void RobloxView::resetKeyState() { if (m_pUserInput) m_pUserInput->resetKeyState(); } void RobloxView::handleMouseInside(bool inside) { if (!m_pUserInput) return; if (inside) { m_pUserInput->onMouseInside(); getCursorPos(&m_previousCursorPos); // need to immediately set cursor to what the game has m_bIgnoreCursorChange = false; setCursorImage(); } else { // should unload any cursors we added (Qt keeps a stack of cursor changes otherwise, also some platforms need this to properly set mouse icons) m_pQtWrapperWidget->unsetCursor(); m_bIgnoreCursorChange = false; m_pUserInput->onMouseLeave(); } } void RobloxView::handleTextBoxGainFocus() { m_pQtWrapperWidget->setLuaTextBoxHasFocus(true); } void RobloxView::handleTextBoxReleaseFocus() { m_pQtWrapperWidget->setLuaTextBoxHasFocus(false); } void RobloxView::handleMouseIconEnabledEvent(bool iconEnabled) { m_mouseCursorHidden = !iconEnabled; if (m_mouseCursorHidden) { m_pQtWrapperWidget->unsetCursor(); m_pQtWrapperWidget->setCursor(Qt::BlankCursor); } else { handleMouseCursorChange(); } } void RobloxView::handleMouseCursorChange() { if (!m_bIgnoreCursorChange && m_pUserInput) setCursorImage(); } void RobloxView::handleScrollWheel(float delta, int x, int y) { if (m_pUserInput) { // normalize the wheel input if (!FFlag::UserBetterInertialScrolling) { delta = G3D::clamp(delta,-1.0f, 1.0f); } m_pUserInput->PostUserInputMessage(RBX::InputObject::TYPE_MOUSEWHEEL, RBX::InputObject::INPUT_STATE_CHANGE, delta, MAKEXYLPARAM((uint) x, (uint) y)); } } shared_ptr RobloxView::handleDropOperation(const QString& url, int x, int y, bool &mouseCommandInvoked) { if (!m_pDataModel) throw std::runtime_error("Can't insert at this time"); shared_ptr instances(new RBX::Instances()); try { RBX::ContentProvider* cp = NULL; { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); cp = m_pDataModel.get()->create(); } RBXASSERT(cp); // load instances in a different thread bool hasError = false; RBX::ContentId contentId(url.toStdString()); UpdateUIManager::Instance().waitForLongProcess("Inserting...", boost::bind(&RobloxView::loadContent, this, shared_from(cp), contentId, boost::ref(*instances), boost::ref(hasError))); if (FFlag::StudioRecordToolboxInsert && !hasError) UpdateUIManager::Instance().getMainWindow().trackToolboxInserts(contentId, *instances); if (hasError || instances->size() < 1) throw std::runtime_error("Dragged object cannot be inserted."); RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); RBX::LuaSourceContainer::blockingLoadLinkedScriptsForInstances(cp, *instances); if (FFlag::GoogleAnalyticsTrackingEnabled) { // Get the assetID from the url and use it as the event label. QStringList urlSplit = url.split("?id="); if (!urlSplit.empty()) { QString assetID = urlSplit.last(); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ACTION, "Insert", assetID.toStdString().c_str()); RobloxMainWindow::get(m_pQtWrapperWidget)->m_inserts.increment(); } } RBX::InsertMode insertMode = RBX::INSERT_TO_3D_VIEW; if ((instances->size() == 1) && RBX::Instance::fastSharedDynamicCast(instances->at(0))) { RBX::Selection* sel = RBX::ServiceProvider::create(m_pDataModel->getWorkspace()); if ((sel->size() == 1) && RBX::Instance::fastSharedDynamicCast(sel->front())) insertMode = RBX::INSERT_TO_TREE; } insertInstances(x, y, *instances, mouseCommandInvoked, insertMode); } // Catch any invalid asset url being dragged into Roblox Ogre Widget, this can come from different application catch (std::exception const& e) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what()); } return instances; } void RobloxView::handleDropOperation(boost::shared_ptr pInstanceToInsert, int x, int y, bool &mouseCommandInvoked) { if (!m_pDataModel || !pInstanceToInsert) return; try { shared_ptr pClonedObjectToInsert = pInstanceToInsert->clone(RBX::EngineCreator); if (!pClonedObjectToInsert) return; RBX::Instances instances; instances.push_back(pClonedObjectToInsert); RBX::InsertMode insertMode = RBX::INSERT_TO_3D_VIEW; if (RBX::Instance::fastDynamicCast(pClonedObjectToInsert.get()) != NULL) insertMode = RBX::INSERT_TO_TREE; insertInstances(x, y, instances, mouseCommandInvoked, insertMode); } catch (std::exception const& e) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what()); } } void RobloxView::cancelDropOperation(bool resetMouseCommand) { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); std::for_each(m_DraggedItems.begin(), m_DraggedItems.end(), boost::bind(&RBX::Instance::setParent, _1, (RBX::Instance*) NULL)); m_DraggedItems.clear(); if (resetMouseCommand) { RBX::MouseCommand* pCurrentMouseCommand = m_pDataModel->getWorkspace()->getCurrentMouseCommand(); RBX::ICancelableTool* pCancelableTool = NULL; if((pCurrentMouseCommand != NULL) && (pCancelableTool = dynamic_cast(pCurrentMouseCommand))) m_pDataModel->getWorkspace()->setMouseCommand(pCancelableTool->onCancelOperation()); } } void RobloxView::insertInstances(int x, int y, const RBX::Instances &instances, bool &mouseCommandInvoked, RBX::InsertMode insertMode) { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); try { m_DraggedItems.clear(); mouseCommandInvoked = false; std::for_each(instances.begin(), instances.end(), boost::bind(&InsertObjectWidget::SetObjectDefaultValues, _1)); RBX::Instances remaining; { m_pDataModel->getWorkspace()->insertInstances(instances, m_pDataModel->getWorkspace(), insertMode, RBX::SUPPRESS_PROMPTS, NULL, &remaining); if (!remaining.empty()) mouseCommandInvoked = m_pDataModel->getWorkspace()->startPartDropDrag(remaining, true); if ((instances.size() == 1) && RBX::Instance::fastDynamicCast(instances[0].get())) { mouseCommandInvoked = m_pDataModel->getWorkspace()->startDecalDrag(RBX::Instance::fastDynamicCast(instances[0].get()), RBX::INSERT_TO_3D_VIEW); } RBX::Selection* sel = RBX::ServiceProvider::create(m_pDataModel->getWorkspace()); boost::shared_ptr runService = shared_from(m_pDataModel->create()); // If we're not playing a game then select the content if (runService->getRunState() == RBX::RS_STOPPED) { sel->setSelection(instances.begin(), instances.end()); } } for (size_t ii=0; ii < instances.size(); ++ii) m_DraggedItems.push_back(instances[ii]); } catch (std::exception&) { std::for_each(instances.begin(), instances.end(), boost::bind(&RBX::Instance::setParent, _1, (RBX::Instance*) NULL)); throw; } } void RobloxView::loadContent(boost::shared_ptr cp, RBX::ContentId contentId, RBX::Instances& instances, bool& hasError) { try { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); std::auto_ptr stream(cp->getContent(contentId)); Serializer().loadInstances(*stream, instances); hasError = false; } catch (...) { hasError = true; } } void RobloxView::processWrapMouse() { if (m_pDataModel && m_pDataModel.get()) if( RBX::UserInputService* userInputService = RBX::ServiceProvider::create(m_pDataModel.get()) ) if(userInputService->getMouseWrapMode() == RBX::UserInputService::WRAP_HYBRID) m_pUserInput->ProcessUserInputMessage(RBX::InputObject::TYPE_NONE,RBX::InputObject::INPUT_STATE_NONE,0,0); } void RobloxView::getCursorPos(G3D::Vector2 *pPos) { if (!m_pQtWrapperWidget || !pPos) return; //map global position to local widget QPoint viewPos = QCursor::pos(); QPoint localPos = m_pQtWrapperWidget->mapFromGlobal(viewPos); //now get the position pPos->x = localPos.x(); pPos->y = localPos.y(); } void RobloxView::setDefaultCursorImage() { if ( !RBX::MouseCommand::isAdvArrowToolEnabled() ) { m_pQtWrapperWidget->setCursor(Qt::BlankCursor); return; } m_pQtWrapperWidget->setCursor(Qt::ArrowCursor); } bool RobloxView::setCursorImageInternal_deprecated(const std::string fileName) { if ( !RBX::MouseCommand::isAdvArrowToolEnabled() ) { m_pQtWrapperWidget->setCursor(Qt::BlankCursor); return true; } QString qs = fileName.c_str(); QPixmap pix; if ( !QPixmapCache::find(qs,&pix) ) { pix.load(qs); QPixmapCache::insert(qs,pix); } if(!pix.isNull()) // we have a valid image file, lets set the cursor m_pQtWrapperWidget->setCursor(QCursor(pix)); return !pix.isNull(); } bool RobloxView::setCursorImageInternal(const RBX::ContentId& contentId, shared_ptr imageContent) { if ( !RBX::MouseCommand::isAdvArrowToolEnabled() ) { m_pQtWrapperWidget->setCursor(Qt::BlankCursor); return true; } QString qs = contentId.toString().c_str(); QPixmap pix; if ( !QPixmapCache::find(qs,&pix) ) { QImage image; image.loadFromData((uchar*)imageContent->c_str(), imageContent->size()); pix = QPixmap::fromImage(image); QPixmapCache::insert(qs, pix); } if(!pix.isNull()) // we have a valid image file, lets set the cursor m_pQtWrapperWidget->setCursor(QCursor(pix)); return !pix.isNull(); } void RobloxView::setCursorImage(bool forceShowCursor) { if (!m_pQtWrapperWidget) return; if (m_mouseCursorHidden && !forceShowCursor) { m_pQtWrapperWidget->setCursor(Qt::BlankCursor); return; } RBX::ContentId cursorContent = m_pUserInput->getCurrentCursorId(); if (cursorContent.toString().empty()) { if (RBX::Network::Players::findLocalPlayer(m_pDataModel.get()) != NULL) { RBX::ToolMouseCommand* pToolMouseCommand = dynamic_cast(m_pDataModel->getWorkspace()->getCurrentMouseCommand()); if (pToolMouseCommand != NULL) cursorContent = RBX::ContentId::fromAssets("Textures/ArrowCursor.png"); } else { setDefaultCursorImage(); return; } } bool cursorIsSet = false; // first, unload our current cursor (we are guaranteed to set this later) m_pQtWrapperWidget->unsetCursor(); // If we want the default cursor just set with Qt arrow cursor... if (!cursorContent.toString().compare("rbxasset://" + defaultMouseCursorPath)) { setDefaultCursorImage(); cursorIsSet = true; } else { if (m_pDataModel) { if (RBX::DataModel* dm = m_pDataModel.get()) { shared_ptr imageContent; try { imageContent = RBX::ServiceProvider::create(dm)->requestContentString( cursorContent, RBX::ContentProvider::PRIORITY_MFC); } catch (const std::exception& e) { FASTLOG2(FLog::HardwareMouse, "RobloxView::setCursorImage failed to load cursorContent %s because %s", cursorContent.c_str(), e.what()); // cursor load failed (either from web or local asset) // make sure we still set cursor to something setDefaultCursorImage(); return; } if (!imageContent || imageContent->empty() || !setCursorImageInternal(cursorContent, imageContent)) { setDefaultCursorImage(); } cursorIsSet = true; } } } if (!cursorIsSet) setDefaultCursorImage(); } void RobloxView::setCursorPos(G3D::Vector2 *pPos, bool isLMB, bool force, bool updatePos) { if (!m_pQtWrapperWidget || !pPos || !isValidCursorPos(pPos, isLMB, force)) return; // need to set previous position before the call to setPos. // otherwise we'll send an extra delta that we don't want. if (updatePos) m_previousCursorPos = *pPos; //map local to global QPoint localPos(pPos->x, pPos->y); QPoint globalPos = m_pQtWrapperWidget->mapToGlobal(localPos); //now set the position QCursor::setPos(globalPos); } bool RobloxView::hasApplicationFocus() { if (m_pQtWrapperWidget) return m_pQtWrapperWidget->hasApplicationFocus(); return false; } bool RobloxView::isValidCursorPos(G3D::Vector2 *pPos, bool isLMB, bool force) { if (force) return true; if (isLMB) { QPoint cursorPos = m_pQtWrapperWidget->mapFromGlobal(QCursor::pos()); return !m_pQtWrapperWidget->rect().contains(QPoint(cursorPos.x(), cursorPos.y()), true); } return (m_mousePressedPos == *pPos); } void RobloxView::requestUpdateView() { if (m_pRenderRequestJob && !(RbxViewCyclicFlagsEnabled())) { m_pRenderRequestJob->wake(); } } void RobloxView::doRender(const double timeStamp) { m_pViewBase->renderPrepare(this); } void RobloxView::updateView() { if ((!m_pRenderJob || !m_pRenderRequestJob) && (!(m_pRenderJobCyclic && RbxViewCyclicFlagsEnabled()))) return; if ( !RbxViewCyclicFlagsEnabled() && (UpdateUIManager::Instance().getMainWindow().isMinimized() || UpdateUIManager::Instance().isBusy() || !m_pQtWrapperWidget->isVisible()) ) { return; } RBX::Profiling::Mark mark(*m_profilingSection, true, true); FASTLOG(FLog::RenderRequest, "Got render request, waking render job"); bool cyclicExecutiveSkipsPrep = false; if (RbxViewCyclicFlagsEnabled()) { // Keeps track of when to Skip frames for // Unfocused UI m_updateViewStepId++; cyclicExecutiveSkipsPrep = (UpdateUIManager::Instance().getMainWindow().isMinimized() || UpdateUIManager::Instance().isBusy() || !m_pQtWrapperWidget->isVisible()); if (!m_pRenderJobCyclic->ogreWidgetHasFocus()) { if (!(m_updateViewStepId / 4)) { cyclicExecutiveSkipsPrep = true; } } if (m_updateViewStepId >= 4) { m_updateViewStepId = 0; } // Set the RenderJob to skip all the fancy shmancy Locking and Waiting if (cyclicExecutiveSkipsPrep) { m_pRenderJobCyclic->setIsBusySkipRender(true); } // Wake the Rendering Job m_pRenderJobCyclic->wake(); // We ignore the locking nature of the RenderJob if Busy if (!cyclicExecutiveSkipsPrep) { m_pRenderJobCyclic->updateView.Wait(); } } else { m_pRenderJob->wake(); //block the job, as we are in between of an update m_pRenderJob->updateView.Wait(); } if (!m_pViewBase.get() || !m_pDataModel) { if (RbxViewCyclicFlagsEnabled()) { if (!cyclicExecutiveSkipsPrep) { m_pRenderJobCyclic->viewUpdated.Set(); } } else { m_pRenderJob->viewUpdated.Set(); } return; } FASTLOG(FLog::RenderRequest, "Job has started, proceeding"); // update hover over animation, hover color, and selection color updateHoverOver(); RBX::Profiling::CodeProfiler* renderCodeProfiler = m_pViewBase->getRenderStats().cpuRenderTotal.get(); { RBX::Profiling::Mark mark(*renderCodeProfiler, true, true); { bool isViewUpdated = false; if (!cyclicExecutiveSkipsPrep) { try { RBX::FrameRateManager* pFRMgr = m_pViewBase->getFrameRateManager(); if(pFRMgr) { pFRMgr->configureFrameRateManager(CRenderSettingsItem::singleton().getFrameRateManagerMode(), true); // hasCharacter); } //RenderUpdate phase { RBX::DataModel::scoped_write_transfer request(m_pDataModel.get()); m_pViewBase->updateVR(); float secondsElapsed = m_pViewBase->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f; m_pDataModel->renderStep(secondsElapsed); } double timeStamp; if (RbxViewCyclicFlagsEnabled()) { timeStamp = m_pRenderJobCyclic->getTimestamp(); } else { timeStamp = m_pRenderRequestJob->getTimestamp(); } doRender(timeStamp); //done with the update, release job! if (RbxViewCyclicFlagsEnabled()) { m_pRenderJobCyclic->viewUpdated.Set(); } else { m_pRenderJob->viewUpdated.Set(); } isViewUpdated = true; m_pViewBase->renderPerform(timeStamp); //this is required to convey view that we are done with screenshot m_pViewBase->getAndClearDoScreenshot(); } catch (std::exception& e) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what()); } catch (...) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "Unknown exception occurred during view update"); } } if (!isViewUpdated) { if (RbxViewCyclicFlagsEnabled()) { if (!cyclicExecutiveSkipsPrep) { m_pRenderJobCyclic->viewUpdated.Set(); } } else { m_pRenderJob->viewUpdated.Set(); } } //TODO: add support for "isPaintMessageAware" requestUpdateView(); } } //init dataModelHash if (m_DataModelHashNeeded) { RobloxDocManager::Instance().getPlayDoc()->initializeDataModeHash(); m_DataModelHashNeeded = false; } FASTLOG(FLog::RenderRequest, "Render request finished"); } /** * Updates the hover over color and updates the selection color. * If hover over animation is enabled, the hover over color will be changed. */ void RobloxView::updateHoverOver() { // adjust the hover over and select colors G3D::Color4 hoverOverColor = AuthoringSettings::singleton().hoverOverColor; G3D::Color4 selectColor = AuthoringSettings::singleton().selectColor; // animation time const RBX::Time currentTime = RBX::Time::nowFast(); const double deltaTime = (currentTime - m_hoverOverLastTime).msec(); m_hoverOverAccum += deltaTime; // determine length of cycle int animateTime; switch ( AuthoringSettings::singleton().hoverAnimateSpeed ) { case AuthoringSettings::VerySlow: animateTime = 2000; // 2 seconds break; case AuthoringSettings::Slow: animateTime = 1000; // 1 seconds break; default: case AuthoringSettings::Medium: animateTime = 500; // 1/2 second break; case AuthoringSettings::Fast: animateTime = 250; // 1/4 second break; case AuthoringSettings::VeryFast: animateTime = 100; // 1/10 second break; } // adjust time accumulator to a reasonable value while ( m_hoverOverAccum > animateTime * 2 ) m_hoverOverAccum -= animateTime * 2; m_hoverOverLastTime = currentTime; // adjust colors based on animation time if ( AuthoringSettings::singleton().animateHoverOver ) { if ( m_hoverOverAccum < animateTime ) { hoverOverColor = selectColor.lerp( hoverOverColor, m_hoverOverAccum / animateTime ); } else { hoverOverColor = hoverOverColor.lerp( selectColor, (m_hoverOverAccum - animateTime) / animateTime ); } } // set new colors RBX::Draw::setSelectColor(selectColor); RBX::Draw::setHoverOverColor(hoverOverColor); } // IMetric double RobloxView::getMetricValue(const std::string& metric) const { RBX::FrameRateManager* pFrameRateManager = m_pViewBase ? m_pViewBase->getFrameRateManager() : NULL; if (metric == "Video Memory") return RBX::SystemUtil::getVideoMemory(); if (RbxViewCyclicFlagsEnabled()) { if (metric == "Render FPS") return m_pRenderJobCyclic.get() ? m_pRenderJobCyclic->averageStepsPerSecond() : 0.0; if (metric == "Render Duty") return m_pRenderJobCyclic.get() ? m_pRenderJobCyclic->averageDutyCycle() : 0.0; } else { if (metric == "Render FPS") return m_pRenderJob.get() ? m_pRenderJob->averageStepsPerSecond() : 0.0; if (metric == "Render Duty") return m_pRenderJob.get() ? m_pRenderJob->averageDutyCycle() : 0.0; } if (metric == "Render Nominal FPS") return pFrameRateManager ? 1000.0 / pFrameRateManager->GetRenderTimeAverage() : 0.0; if (!m_pViewBase) return 0.0; if (metric == "Delta Between Renders") return m_pViewBase->getMetricValue(metric); if (metric == "Total Render") return m_pViewBase->getMetricValue(metric); if (metric == "Present Time") return m_pViewBase->getMetricValue(metric); if (metric == "GPU Delay") return m_pViewBase->getMetricValue(metric); return 0.0; } std::string RobloxView::getMetric(const std::string& metric) const { if (! m_pViewBase ) return "No View"; if (metric == "Graphics Mode") return RBX::Reflection::EnumDesc::singleton().convertToString(CRenderSettingsItem::singleton().getLatchedGraphicsMode()); RBX::FrameRateManager* pFrameRateManager = m_pViewBase ? m_pViewBase->getFrameRateManager() : 0; if (metric == "FRM") return (pFrameRateManager && pFrameRateManager->IsBlockCullingEnabled()) ? "On" : "Off"; if (metric == "Anti-Aliasing") return (pFrameRateManager && pFrameRateManager->getAntialiasingMode() == RBX::CRenderSettings::AntialiasingOn) ? "On" : "Off"; RBXASSERT(0); return ""; } void RobloxView::handleContextMenu() { m_rightClickMenuPending = false; if(!m_rightClickContextAllowed) return; int x = m_rightClickPosition.x(); int y = m_rightClickPosition.y(); handleContextMenu(x, y); } /** * Displays a context menu at a given screen location. * If nothing is selected, a left click will be simulated in order to attempt to select * an object under the cursor. */ void RobloxView::handleContextMenu(int x, int y) { m_pQtWrapperWidget->setFocus(Qt::MouseFocusReason); // send the right click to the engine first m_pUserInput->ProcessUserInputMessage(RBX::InputObject::TYPE_MOUSEBUTTON2, RBX::InputObject::INPUT_STATE_END, 0, MAKEXYLPARAM((uint)x,(uint)y)); RBX::Instance* parent; { RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write); // fire a left click to make sure whatever is under the cursor is selected m_pUserInput->ProcessUserInputMessage(RBX::InputObject::TYPE_MOUSEBUTTON1, RBX::InputObject::INPUT_STATE_BEGIN, 0, MAKEXYLPARAM((uint)x,(uint)y)); m_pUserInput->ProcessUserInputMessage(RBX::InputObject::TYPE_MOUSEBUTTON1, RBX::InputObject::INPUT_STATE_END, 0, MAKEXYLPARAM((uint)x,(uint)y)); // get parent object now that we might have selected something RBX::Selection* selection = RBX::ServiceProvider::create(m_pDataModel.get()); parent = selection->front().get(); } if (!parent) parent = m_pDataModel->getWorkspace(); RobloxMainWindow& mainWindow = UpdateUIManager::Instance().getMainWindow(); QMenu menu(m_pQtWrapperWidget); menu.addAction(mainWindow.cutAction); menu.addAction(mainWindow.copyAction); menu.addAction(mainWindow.pasteAction); menu.addAction(mainWindow.pasteIntoAction); menu.addAction(mainWindow.duplicateSelectionAction); menu.addAction(mainWindow.deleteSelectedAction); menu.addSeparator(); menu.addAction(mainWindow.groupSelectionAction); menu.addAction(mainWindow.ungroupSelectionAction); menu.addAction(mainWindow.unionSelectionAction); menu.addAction(mainWindow.negateSelectionAction); menu.addAction(mainWindow.separateSelectionAction); menu.addAction(mainWindow.selectChildrenAction); menu.addAction(mainWindow.zoomExtentsAction); menu.addSeparator(); menu.addAction( QIcon(QtUtilities::getPixmap(":/images/ClassImages.PNG",1)), // part icon "Insert Part", this, SLOT(onInsertPart()) ); QMenu* insertObjectMenu = InsertObjectWidget::createMenu(parent,this,SLOT(onInsertObject())); menu.addMenu(insertObjectMenu); menu.addSeparator(); menu.addAction(mainWindow.launchHelpForSelectionAction); menu.connect(&menu,SIGNAL(aboutToShow()),&UpdateUIManager::Instance(),SLOT(onMenuShow())); menu.connect(&menu,SIGNAL(aboutToHide()),&UpdateUIManager::Instance(),SLOT(onMenuHide())); QPoint p = m_pQtWrapperWidget->mapToGlobal(QPoint(x,y)); menu.exec(p); } bool RobloxView::rightClickContextMenuAllowed() { if (m_rightClickContextAllowed && !m_pUserInput->areEditModeMovementKeysDown() && !RBX::Profiler::isCapturingMouseInput() && !StudioUtilities::isAvatarMode() && //if there is not a right click pending !m_rightClickMenuPending && //checking if maximum hold time has been hit m_rightClickMouseDownDate.time().msecsTo(QDateTime::currentDateTime().time()) < FInt::StudioRightClickMax) { //return if buffer time is less than time between last right click blocking event and now return (FInt::StudioRightClickBuffer < m_lastRightClickBlockingEventDate.time().msecsTo(QDateTime::currentDateTime().time())); } return false; } /** * Callback for user clicking on insert part in context menu menu. */ void RobloxView::onInsertPart() { InsertObjectWidget::InsertObject("Part",m_pDataModel, InsertObjectWidget::InsertMode_ContextMenu, &m_rightClickPosition); if (FFlag::StudioSeparateActionByActivationMethod) UpdateUIManager::Instance().getMainWindow().sendActionCounterEvent("insertPartMainContext"); } /** * Callback for user clicking on insert basic object in context menu sub menu. */ void RobloxView::onInsertObject() { QAction* action = static_cast(sender()); QString className = action->text(); InsertObjectWidget::InsertObject(className,m_pDataModel, InsertObjectWidget::InsertMode_ContextMenu, &m_rightClickPosition); if (FFlag::StudioSeparateActionByActivationMethod) UpdateUIManager::Instance().getMainWindow().sendActionCounterEvent(QString("insert%1Context").arg(className)); } bool RobloxView::eventFilter(QObject * watched, QEvent * evt) { if (evt->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast(evt); if (mouseEvent->button() == Qt::RightButton) { QMenu *menu = static_cast(watched); m_pQtWrapperWidget->setFocus(Qt::MouseFocusReason); menu->resize(0,0); menu->move(0,0); m_pUserInput->PostUserInputMessage( RBX::InputObject::TYPE_MOUSEBUTTON1, RBX::InputObject::INPUT_STATE_BEGIN, mouseEvent->modifiers(), MAKEXYLPARAM((uint) mouseEvent->x(), (uint) mouseEvent->y())); return true; } } else if (evt->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(evt); if (mouseEvent->button() == Qt::RightButton) { QMenu *menu = static_cast(watched); menu->hide(); m_pUserInput->PostUserInputMessage( RBX::InputObject::TYPE_MOUSEBUTTON1, RBX::InputObject::INPUT_STATE_BEGIN, mouseEvent->modifiers(), MAKEXYLPARAM((uint) mouseEvent->x(), (uint) mouseEvent->y())); return true; } } return watched->event(evt); } bool RobloxView::RbxViewCyclicFlagsEnabled() { return (RBX::TaskScheduler::singleton().isCyclicExecutive()); } static RBX::ViewBase* createGameWindow(QOgreWidget *pQtWrapperWindow) { if (!pQtWrapperWindow) return NULL; //Ogre Plugin Initialization static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(&RBX::ViewBase::InitPluginModules, flag); RBX::ViewBase* pRbxView = NULL; std::vector modes; const char* graphicsModeName; RBX::CRenderSettings::GraphicsMode graphicsMode = CRenderSettingsItem::singleton().getLatchedGraphicsMode(); switch(graphicsMode) { case RBX::CRenderSettings::NoGraphics: break; case RBX::CRenderSettings::OpenGL: graphicsModeName = "gfx_gl"; modes.push_back(graphicsMode); break; #ifdef _WIN32 case RBX::CRenderSettings::Direct3D9: graphicsModeName = "gfx_d3d9"; modes.push_back(graphicsMode); break; case RBX::CRenderSettings::Direct3D11: graphicsModeName = "gfx_d3d11"; modes.push_back(graphicsMode); break; #endif default: graphicsModeName = "gfx_all"; #ifdef _WIN32 if (FFlag::DirectX11Enable) modes.push_back(RBX::CRenderSettings::Direct3D11); modes.push_back(RBX::CRenderSettings::Direct3D9); #endif modes.push_back(RBX::CRenderSettings::OpenGL); break; } //View creation parameters RBX::OSContext context; context.hWnd = reinterpret_cast(pQtWrapperWindow->winId()); context.width = 800; context.height = 600; bool success = false; size_t modei = 0; while(!success && modei < modes.size()) { graphicsMode = modes[modei]; try { RBX::CRenderSettings& renderSettings = CRenderSettingsItem::singleton(); //Create Ogre view pRbxView = RBX::ViewBase::CreateView(graphicsMode, &context, &renderSettings); success = true; } catch (std::exception& e) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Mode %d failed: \"%s\"", graphicsMode, e.what()); modei++; if(modei < modes.size()) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "Trying mode %d...", modes[modei]); } } } if(modes.size() != 0 && !success) { RobloxSettings settings; settings.setValue("lastGFXMode", -1); throw std::runtime_error("Your graphics drivers seem to be too old for Roblox to use.\n\nVisit http://www.roblox.com/drivers for info on how to perform a driver upgrade."); } if(success && pRbxView) { RobloxSettings settings; settings.setValue("lastGFXMode", graphicsMode); //Initialize resources pRbxView->initResources(); } return pRbxView; }