ROBLOX Studio 2013 is a tool designed for teens and pre-teens to design and build games. The users share and play games with each other through a social network website. Game logic is written in Lua so it’s a great introduction to programming and classifies the tool/website as educational entertainment.
The code was written in C++ and Qt using Ogre as the backend rendering engine.
#include "stdafx.h"
#include "ScriptSideWidget.h"
// Qt Headers
#include
#include
#include
// Roblox Studio Headers
#include "RobloxScriptDoc.h"
#include "ScriptTextEditor.h"
#include "AuthoringSettings.h"
#include "QtUtilities.h"
FASTFLAG(LuaDebugger)
ScriptSideWidget::ScriptSideWidget(ScriptTextEditor* editor)
: QWidget(editor),
m_pScriptEditor(editor),
m_foldArea(QtUtilities::BranchIndicatorSize)
{
}
ScriptSideWidget::~ScriptSideWidget()
{
}
void ScriptSideWidget::updateArea()
{
int digits = 1;
int max = qMax(1,m_pScriptEditor->blockCount());
while (max >= 10)
{
max /= 10;
++digits;
}
QFontMetrics fm(m_font);
int newWidth = fm.width(QLatin1Char('9')) * digits + 10 + (FFlag::LuaDebugger ? 2 * m_foldArea : m_foldArea);;
if (width() != newWidth)
{
setFixedWidth(newWidth);
m_pScriptEditor->setViewportMargins(newWidth,0,0,0);
}
update();
}
void ScriptSideWidget::toggleFold(int lineNumber)
{
if (isFolded(lineNumber))
unfold(lineNumber);
else
fold(lineNumber);
}
void ScriptSideWidget::fold(int lineNumber)
{
QTextBlock startBlock = m_pScriptEditor->document()->findBlockByNumber(lineNumber - 1);
if (!isFoldable(startBlock) || isFolded(lineNumber))
return;
QTextBlock endBlock = ScriptEditorUtils::findFoldBoundary(startBlock,false);
QTextBlock block = startBlock.next();
while (block.isValid() && (block.blockNumber() <= endBlock.blockNumber()))
{
block.setVisible(false);
block.setLineCount(0);
block = block.next();
}
m_FoldedBlocks << startBlock.blockNumber();
QTextBlock closingBlock = endBlock;
if (endBlock != m_pScriptEditor->document()->lastBlock())
closingBlock = endBlock.next();
QTextCursor cursor = m_pScriptEditor->textCursor();
int blockNumber = cursor.block().blockNumber();
if ( blockNumber > startBlock.blockNumber() && blockNumber <= endBlock.blockNumber() )
{
cursor.setPosition(endBlock.position() + endBlock.length(),QTextCursor::MoveAnchor);
m_pScriptEditor->setTextCursor(cursor);
}
m_pScriptEditor->document()->markContentsDirty(startBlock.position(),closingBlock.position() + closingBlock.length());
m_pScriptEditor->updateFolds();
m_pScriptEditor->viewport()->resize(m_pScriptEditor->viewport()->sizeHint());
update();
}
void ScriptSideWidget::unfold(int lineNumber)
{
QTextBlock startBlock = m_pScriptEditor->document()->findBlockByNumber(lineNumber - 1);
if (!isFoldable(startBlock))
return;
QTextBlock endBlock = ScriptEditorUtils::findFoldBoundary(startBlock,false);
QTextBlock block = startBlock.next();
while (block.isValid() && (block.blockNumber() <= endBlock.blockNumber()))
{
block.setVisible(true);
block.setLineCount(block.layout()->lineCount());
if (m_FoldedBlocks.contains(block.blockNumber()))
block = ScriptEditorUtils::findFoldBoundary(block,false);
block = block.next();
}
m_FoldedBlocks.removeOne(startBlock.blockNumber());
QTextBlock closingBlock = endBlock;
if (endBlock != m_pScriptEditor->document()->lastBlock())
closingBlock = endBlock.next();
m_pScriptEditor->document()->markContentsDirty(startBlock.position(),closingBlock.position() + closingBlock.length());
m_pScriptEditor->updateFolds();
m_pScriptEditor->viewport()->resize(m_pScriptEditor->viewport()->sizeHint());
update();
}
bool ScriptSideWidget::isFolded(int lineNumber)
{
QTextBlock block = m_pScriptEditor->document()->findBlockByNumber(lineNumber);
if (!block.isValid())
return false;
return !block.isVisible();
}
bool ScriptSideWidget::isFoldable(QTextBlock& block)
{
if (!block.isValid())
return false;
bool bFold = false;
RBXTextUserData* pUserData = dynamic_cast(block.userData());
if (pUserData)
bFold = (pUserData->getFoldState() > 0);
return bFold;
}
void ScriptSideWidget::toggleAllFolds(bool expand)
{
QTextBlock currentBlock = expand ? m_pScriptEditor->document()->begin() : m_pScriptEditor->document()->end().previous();
while (currentBlock.isValid())
{
if (isFoldable(currentBlock))
expand ? unfold(currentBlock.blockNumber()+1) : fold(currentBlock.blockNumber()+1);
currentBlock = expand ? currentBlock.next() : currentBlock.previous();
}
}
void ScriptSideWidget::paintEvent(QPaintEvent* evt)
{
QPainter painter(this);
painter.fillRect(rect(),QColor(Qt::lightGray).lighter(115));
painter.setFont(m_font);
const QFontMetrics fm(m_font);
const int pageBottom = m_pScriptEditor->viewport()->height();
const QTextBlock currentBlock = m_pScriptEditor->document()->findBlock(m_pScriptEditor->textCursor().position());
const QPointF viewportOffset = m_pScriptEditor->contentOffset();
const int xofs = width() - m_foldArea;
QTextBlock block = m_pScriptEditor->firstVisibleBlock();
int lineCount = block.blockNumber();
while ( block.isValid() )
{
lineCount += 1;
// The top left position of the block in the document
QPointF position = m_pScriptEditor->blockBoundingGeometry(block).topLeft() + viewportOffset;
// Check if the position of the block is outside of the visible area
if (position.y() > pageBottom)
break;
// We want the line number for the selected line to be bold.
bool isBold = false;
if (block == currentBlock)
{
isBold = true;
QFont font = painter.font();
font.setBold(true);
painter.setFont(font);
}
// Draw the line number right justified at the y position of the
// line. 3 is a magic padding number. drawText(x, y, text).
if (block.isVisible())
{
const QString strNumber = QString::number(lineCount);
const int x = width() - (FFlag::LuaDebugger ? 2 * m_foldArea : m_foldArea) - fm.width(strNumber) - 3;
const int y = position.y() + fm.ascent() + fm.descent() - 1;
painter.drawText(x,y,strNumber);
}
// Remove the bold style if it was set previously.
if (isBold)
{
QFont font = painter.font();
font.setBold(false);
painter.setFont(font);
}
block = block.next();
}
// Breakpoints region
int xofsB = width() - 2*m_foldArea;
if (FFlag::LuaDebugger)
painter.fillRect(xofsB, 0, m_foldArea, height(),QColor(Qt::lightGray).lighter(120));
// Code Folding
painter.fillRect(xofs,0,m_foldArea,height(),QColor(Qt::lightGray).lighter(130));
block = m_pScriptEditor->firstVisibleBlock();
while ( block.isValid() )
{
// Check if the position of the block is outside of the visible area
const QPointF position = m_pScriptEditor->blockBoundingGeometry(block).topLeft() + viewportOffset;
if ( position.y() > pageBottom )
break;
QString blockText = block.text();
if (block.isVisible())
{
if (FFlag::LuaDebugger)
{
RBXTextUserData* pUserData = dynamic_cast(block.userData());
if(pUserData)
{
if (pUserData->getBreakpointState())
painter.drawPixmap(xofsB, qRound(position.y())+2, QtUtilities::getBreakpointPixmap(pUserData->getBreakpointState()));
if (!pUserData->getMarker().isEmpty())
{
QPixmap marker(pUserData->getMarker());
painter.drawPixmap(xofsB,
qRound(position.y())+2,
marker.scaled(QtUtilities::BranchIndicatorSize,
QtUtilities::BranchIndicatorSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation));
}
}
}
if (isFoldable(block))
{
if (m_FoldedBlocks.contains(block.blockNumber()))
painter.drawPixmap(xofs,qRound(position.y()),QtUtilities::getBranchIndicator(QStyle::State_Children));
else
painter.drawPixmap(xofs,qRound(position.y()),QtUtilities::getBranchIndicator(QStyle::State_Children | QStyle::State_Open));
}
}
block = block.next();
}
painter.end();
QWidget::paintEvent(evt);
}
void ScriptSideWidget::mousePressEvent(QMouseEvent* evt)
{
if (m_foldArea > 0)
{
QFontMetrics fm(m_font);
const int xofs = FFlag::LuaDebugger ? 0 : (width() - m_foldArea);
const int fh = fm.lineSpacing();
const int ys = evt->posF().y();
int lineNumber = 0;
if (evt->pos().x() > xofs)
{
QTextBlock block = m_pScriptEditor->firstVisibleBlock();
const QPointF viewportOffset = m_pScriptEditor->contentOffset();
const int pageBottom = m_pScriptEditor->viewport()->height();
while (block.isValid())
{
const QPointF position = m_pScriptEditor->blockBoundingGeometry(block).topLeft() + viewportOffset;
if (position.y() > pageBottom)
break;
if ( (position.y() < ys) && ((position.y() + fh) > ys) )
{
int xofF = width() - m_foldArea;
if ( FFlag::LuaDebugger && (evt->pos().x() > xofs) && (evt->pos().x() < xofF) )
{
Q_EMIT toggleBreakpoint(block.blockNumber());
update();
}
else if (isFoldable(block))
{
lineNumber = block.blockNumber() + 1;
break;
}
}
block = block.next();
}
}
if (lineNumber > 0)
toggleFold(lineNumber);
}
}
void ScriptSideWidget::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor)
{
if ( !pDescriptor || pDescriptor->category.str == sScriptCategoryName )
{
m_font = AuthoringSettings::singleton().editorFont;
m_font.setFixedPitch(true);
m_font.setStrikeOut(false);
m_font.setUnderline(false);
m_font.setBold(false);
updateArea();
}
}