Files
2025-09-18 17:55:52 -04:00

1802 lines
53 KiB
C++

/**
* RobloxView.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxView.h"
// Qt Headers
#include <QApplication>
#include <QCursor>
#include <QPixmapCache>
#include <QStringList>
#include <QMouseEvent>
#include <QEvent>
// 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<RBX::DataModel> 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<RBX::DataModel> 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<RBX::Time::Fast>();
shared_ptr<RBX::DataModel> 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<RBX::Tasks::Coordinator> m_pSequence;
shared_ptr<RBX::DataModel> m_pDataModel;
QOgreWidget *m_pQOgreWidget;
volatile bool m_bIsAwake;
double m_timestamp;
public:
RenderRequestJob(RobloxView *pRbxView, shared_ptr<RBX::DataModel> 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<RBX::RunService>()->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<RBX::RunService>()->getPhysicsJob()->addCoordinator(m_pSequence);
}
}
~RenderRequestJob()
{
if (m_pSequence)
{
if (RBX::RunService* pRunService = m_pDataModel->find<RBX::RunService>())
{
#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<RBX::DataModel> m_pDataModel;
RBX::Time m_LastRenderTime;
volatile bool m_bIsAwake;
public:
RenderJob(RobloxView *, shared_ptr<RBX::DataModel> 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<RBX::Time::Fast>() - 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<RBX::Time::Fast>();
shared_ptr<RBX::DataModel> 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<RBX::DataModel> 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<RBX::ViewBase> RobloxView::createGameView(QOgreWidget *pQtWrapperWindow)
{
return boost::shared_ptr<RBX::ViewBase>(createGameWindow(pQtWrapperWindow));
}
RobloxView::RobloxView(QOgreWidget *pQtWrapperWidget, boost::shared_ptr<RBX::Game> game, boost::shared_ptr<RBX::ViewBase> 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<RenderJobCyclic>(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<RenderRequestJob>(new RenderRequestJob(this, m_pDataModel, m_pQtWrapperWidget));
RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tRenderJob");
m_pRenderJob = shared_ptr<RenderJob>(new RenderJob(this, m_pDataModel));
}
RBX::Log::current()->writeEntry(RBX::Log::Information,"\t\t\tWrapMouseJob");
m_pWrapMouseJob = shared_ptr<WrapMouseJob>(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<RBX::UserInputService>(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<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller);
RBX::TaskScheduler::singleton().removeBlocking(m_pRenderJob, callback);
m_pRenderJob.reset();
}
if (m_pRenderRequestJob && !RbxViewCyclicFlagsEnabled())
{
boost::function<void()> 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<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, m_pMarshaller);
RBX::TaskScheduler::singleton().removeBlocking(m_pRenderJobCyclic, callback);
m_pRenderJobCyclic.reset();
}
if (m_pWrapMouseJob)
{
boost::function<void()> 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<RBX::ControllerService>(m_pDataModel.get());
service->setHardwareDevice(NULL);
m_pViewBase->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
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<RBX::Instance> playerAdded)
{
if( RBX::Network::Player* localPlayer = RBX::Network::Players::findLocalPlayer(m_pDataModel.get()) )
if( RBX::Network::Player* thePlayerAdded = RBX::Instance::fastDynamicCast<RBX::Network::Player>(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<RBX::Network::Players>(m_pDataModel.get()) )
m_itemAddedToPlayersConnection = players->onDemandWrite()->childAddedSignal.connect(boost::bind(&RobloxView::onEvent_playerAdded, this, _1));
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(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<RBX::Stats::StatsService>(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<RBX::UserInputService>(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<const RBX::Instances> 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<RBX::Instances> instances(new RBX::Instances());
try
{
RBX::ContentProvider* cp = NULL;
{
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
cp = m_pDataModel.get()->create<RBX::ContentProvider>();
}
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<RBX::Decal>(instances->at(0)))
{
RBX::Selection* sel = RBX::ServiceProvider::create<RBX::Selection>(m_pDataModel->getWorkspace());
if ((sel->size() == 1) && RBX::Instance::fastSharedDynamicCast<RBX::PartInstance>(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<RBX::Instance> pInstanceToInsert, int x, int y, bool &mouseCommandInvoked)
{
if (!m_pDataModel || !pInstanceToInsert)
return;
try
{
shared_ptr<RBX::Instance> 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<RBX::Decal>(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<RBX::ICancelableTool*>(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<RBX::Decal>(instances[0].get()))
{
mouseCommandInvoked = m_pDataModel->getWorkspace()->startDecalDrag(RBX::Instance::fastDynamicCast<RBX::Decal>(instances[0].get()), RBX::INSERT_TO_3D_VIEW);
}
RBX::Selection* sel = RBX::ServiceProvider::create<RBX::Selection>(m_pDataModel->getWorkspace());
boost::shared_ptr<RBX::RunService> runService = shared_from(m_pDataModel->create<RBX::RunService>());
// 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<RBX::ContentProvider> cp, RBX::ContentId contentId, RBX::Instances& instances, bool& hasError)
{
try
{
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
std::auto_ptr<std::istream> 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<RBX::UserInputService>(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<const std::string> 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<RBX::ToolMouseCommand*>(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<const std::string> imageContent;
try
{
imageContent = RBX::ServiceProvider::create<RBX::ContentProvider>(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<RBX::CRenderSettings::GraphicsMode>::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<RBX::Selection>(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<QAction*>(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<QMouseEvent *>(evt);
if (mouseEvent->button() == Qt::RightButton)
{
QMenu *menu = static_cast<QMenu *>(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<QMouseEvent *>(evt);
if (mouseEvent->button() == Qt::RightButton)
{
QMenu *menu = static_cast<QMenu *>(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<RBX::CRenderSettings::GraphicsMode> 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<void*>(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;
}