Skip to content

Implement stage scaling #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions ScratchCPPGui/ProjectPlayer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import ScratchCPPGui

ProjectScene {
property string fileName
property int stageWidth: 480
property int stageHeight: 360
property alias fps: loader.fps
property alias turboMode: loader.turboMode
property alias cloneLimit: loader.cloneLimit
Expand All @@ -21,6 +23,7 @@ ProjectScene {
id: root
clip: true
engine: loader.engine
stageScale: (stageWidth == 0 || stageHeight == 0) ? 1 : Math.min(width / stageWidth, height / stageHeight)
onFileNameChanged: priv.loading = true;

QtObject {
Expand All @@ -31,8 +34,8 @@ ProjectScene {
ProjectLoader {
id: loader
fileName: root.fileName
stageWidth: parent.width
stageHeight: parent.height
stageWidth: root.stageWidth
stageHeight: root.stageHeight
onLoadingFinished: {
priv.loading = false;

Expand All @@ -48,6 +51,7 @@ ProjectScene {
engine: loader.engine
stageModel: loader.stage
mouseArea: sceneMouseArea
stageScale: root.stageScale
onStageModelChanged: stageModel.renderedTarget = this
}

Expand All @@ -68,6 +72,7 @@ ProjectScene {
engine: loader.engine
spriteModel: modelData
mouseArea: sceneMouseArea
stageScale: root.stageScale
transform: Scale { xScale: mirrorHorizontally ? -1 : 1 }
Component.onCompleted: modelData.renderedTarget = this
}
Expand Down
3 changes: 3 additions & 0 deletions ScratchCPPGui/irenderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class IRenderedTarget : public QNanoQuickItem
virtual SceneMouseArea *mouseArea() const = 0;
virtual void setMouseArea(SceneMouseArea *newMouseArea) = 0;

virtual double stageScale() const = 0;
virtual void setStageScale(double scale) = 0;

virtual qreal width() const = 0;
virtual void setWidth(qreal width) = 0;

Expand Down
19 changes: 17 additions & 2 deletions ScratchCPPGui/projectscene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,26 @@ void ProjectScene::setEngine(IEngine *newEngine)
emit engineChanged();
}

double ProjectScene::stageScale() const
{
return m_stageScale;
}

void ProjectScene::setStageScale(double newStageScale)
{
if (qFuzzyCompare(m_stageScale, newStageScale))
return;

m_stageScale = newStageScale;
Q_ASSERT(m_stageScale > 0);
emit stageScaleChanged();
}

void ProjectScene::handleMouseMove(qreal x, qreal y)
{
if (m_engine) {
m_engine->setMouseX(x - m_engine->stageWidth() / 2.0);
m_engine->setMouseY(-y + m_engine->stageHeight() / 2.0);
m_engine->setMouseX(x / m_stageScale - m_engine->stageWidth() / 2.0);
m_engine->setMouseY(-y / m_stageScale + m_engine->stageHeight() / 2.0);
}
}

Expand Down
6 changes: 6 additions & 0 deletions ScratchCPPGui/projectscene.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ class ProjectScene : public QQuickItem
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(libscratchcpp::IEngine *engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(double stageScale READ stageScale WRITE setStageScale NOTIFY stageScaleChanged)

public:
ProjectScene(QQuickItem *parent = nullptr);

libscratchcpp::IEngine *engine() const;
void setEngine(libscratchcpp::IEngine *newEngine);

double stageScale() const;
void setStageScale(double newStageScale);

Q_INVOKABLE void handleMouseMove(qreal x, qreal y);
Q_INVOKABLE void handleMousePress();
Q_INVOKABLE void handleMouseRelease();
Expand All @@ -35,11 +39,13 @@ class ProjectScene : public QQuickItem

signals:
void engineChanged();
void stageScaleChanged();

private:
void installKeyHandler(QQuickWindow *window);

libscratchcpp::IEngine *m_engine = nullptr;
double m_stageScale = 1;
KeyEventHandler *m_keyHandler = nullptr;
};

Expand Down
45 changes: 32 additions & 13 deletions ScratchCPPGui/renderedtarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ void RenderedTarget::loadProperties()

// Coordinates
double clampedSize = std::min(m_size, m_maxSize);
m_x = static_cast<double>(m_engine->stageWidth()) / 2 + sprite->x() - m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution() * (m_newMirrorHorizontally ? -1 : 1);
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - sprite->y() - m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution();
m_originX = m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution();
m_originY = m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution();
double stageWidth = m_engine->stageWidth();
double stageHeight = m_engine->stageHeight();
m_x = m_stageScale * (stageWidth / 2 + sprite->x() - m_costume->rotationCenterX() * clampedSize / m_costume->bitmapResolution() * (m_newMirrorHorizontally ? -1 : 1));
m_y = m_stageScale * (stageHeight / 2 - sprite->y() - m_costume->rotationCenterY() * clampedSize / m_costume->bitmapResolution());
m_originX = m_costume->rotationCenterX() * clampedSize * m_stageScale / m_costume->bitmapResolution();
m_originY = m_costume->rotationCenterY() * clampedSize * m_stageScale / m_costume->bitmapResolution();

// Layer
m_z = sprite->layerOrder();
Expand All @@ -92,8 +94,10 @@ void RenderedTarget::loadProperties()
mutex.unlock();
} else if (m_stageModel) {
updateCostumeData();
m_x = static_cast<double>(m_engine->stageWidth()) / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution();
m_y = static_cast<double>(m_engine->stageHeight()) / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution();
double stageWidth = m_engine->stageWidth();
double stageHeight = m_engine->stageHeight();
m_x = m_stageScale * (stageWidth / 2 - m_costume->rotationCenterX() / m_costume->bitmapResolution());
m_y = m_stageScale * (stageHeight / 2 - m_costume->rotationCenterY() / m_costume->bitmapResolution());
m_originX = m_costume->rotationCenterX() / m_costume->bitmapResolution();
m_originY = m_costume->rotationCenterY() / m_costume->bitmapResolution();
}
Expand Down Expand Up @@ -213,6 +217,21 @@ void RenderedTarget::setMouseArea(SceneMouseArea *newMouseArea)
emit mouseAreaChanged();
}

double RenderedTarget::stageScale() const
{
return m_stageScale;
}

void RenderedTarget::setStageScale(double newStageScale)
{
if (qFuzzyCompare(m_stageScale, newStageScale))
return;

m_stageScale = newStageScale;
Q_ASSERT(m_stageScale > 0);
emit stageScaleChanged();
}

qreal RenderedTarget::width() const
{
return QNanoQuickItem::width();
Expand Down Expand Up @@ -436,16 +455,16 @@ void RenderedTarget::calculateSize(Target *target, double costumeWidth, double c
{
if (m_costume) {
double bitmapRes = m_costume->bitmapResolution();
m_maxSize = std::min(m_maximumWidth / costumeWidth, m_maximumHeight / costumeHeight);
m_maxSize = std::min(m_maximumWidth / (costumeWidth * m_stageScale), m_maximumHeight / (costumeHeight * m_stageScale));
Sprite *sprite = dynamic_cast<Sprite *>(target);

if (sprite) {
double clampedSize = std::min(m_size, m_maxSize);
m_width = costumeWidth * clampedSize / bitmapRes;
m_height = costumeHeight * clampedSize / bitmapRes;
m_width = costumeWidth * clampedSize * m_stageScale / bitmapRes;
m_height = costumeHeight * clampedSize * m_stageScale / bitmapRes;
} else {
m_width = costumeWidth / bitmapRes;
m_height = costumeHeight / bitmapRes;
m_width = costumeWidth * m_stageScale / bitmapRes;
m_height = costumeHeight * m_stageScale / bitmapRes;
}
}
}
Expand All @@ -458,8 +477,8 @@ void RenderedTarget::handleSceneMouseMove(qreal x, qreal y)
Q_ASSERT(m_spriteModel && m_spriteModel->sprite());
Q_ASSERT(m_engine);
Sprite *sprite = m_spriteModel->sprite();
sprite->setX(x - m_engine->stageWidth() / 2.0 - m_dragDeltaX);
sprite->setY(-y + m_engine->stageHeight() / 2.0 - m_dragDeltaY);
sprite->setX(x / m_stageScale - m_engine->stageWidth() / 2.0 - m_dragDeltaX);
sprite->setY(-y / m_stageScale + m_engine->stageHeight() / 2.0 - m_dragDeltaY);
}
}

Expand Down
6 changes: 6 additions & 0 deletions ScratchCPPGui/renderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class RenderedTarget : public IRenderedTarget
Q_PROPERTY(SpriteModel *spriteModel READ spriteModel WRITE setSpriteModel NOTIFY spriteModelChanged)
Q_PROPERTY(bool mirrorHorizontally READ mirrorHorizontally NOTIFY mirrorHorizontallyChanged)
Q_PROPERTY(SceneMouseArea *mouseArea READ mouseArea WRITE setMouseArea NOTIFY mouseAreaChanged)
Q_PROPERTY(double stageScale READ stageScale WRITE setStageScale NOTIFY stageScaleChanged)

public:
RenderedTarget(QNanoQuickItem *parent = nullptr);
Expand All @@ -47,6 +48,9 @@ class RenderedTarget : public IRenderedTarget
SceneMouseArea *mouseArea() const override;
void setMouseArea(SceneMouseArea *newMouseArea) override;

double stageScale() const override;
void setStageScale(double newStageScale) override;

qreal width() const override;
void setWidth(qreal width) override;

Expand Down Expand Up @@ -77,6 +81,7 @@ class RenderedTarget : public IRenderedTarget
void spriteModelChanged();
void mouseAreaChanged();
void mirrorHorizontallyChanged();
void stageScaleChanged();

protected:
QNanoQuickItemPainter *createItemPainter() const override;
Expand Down Expand Up @@ -116,6 +121,7 @@ class RenderedTarget : public IRenderedTarget
bool m_newMirrorHorizontally = false;
double m_originX = 0;
double m_originY = 0;
double m_stageScale = 1;
qreal m_maximumWidth = std::numeric_limits<double>::infinity();
qreal m_maximumHeight = std::numeric_limits<double>::infinity();
std::vector<QPointF> m_hullPoints;
Expand Down
3 changes: 3 additions & 0 deletions test/mocks/renderedtargetmock.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class RenderedTargetMock : public IRenderedTarget
MOCK_METHOD(SceneMouseArea *, mouseArea, (), (const, override));
MOCK_METHOD(void, setMouseArea, (SceneMouseArea *), (override));

MOCK_METHOD(double, stageScale, (), (const, override));
MOCK_METHOD(void, setStageScale, (double), (override));

MOCK_METHOD(qreal, width, (), (const, override));
MOCK_METHOD(void, setWidth, (qreal), (override));

Expand Down
24 changes: 17 additions & 7 deletions test/projectscene/projectscene_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ using namespace scratchcppgui;

using ::testing::Return;

TEST(ProjectScene, Engine)
TEST(ProjectSceneTest, Engine)
{
ProjectScene scene;
ASSERT_EQ(scene.engine(), nullptr);
Expand All @@ -18,20 +18,30 @@ TEST(ProjectScene, Engine)
ASSERT_EQ(scene.engine(), &engine);
}

TEST(ProjectScene, HandleMouseMove)
TEST(ProjectSceneTest, StageScale)
{
ProjectScene scene;
ASSERT_EQ(scene.stageScale(), 1);

scene.setStageScale(5.79);
ASSERT_EQ(scene.stageScale(), 5.79);
}

TEST(ProjectSceneTest, HandleMouseMove)
{
ProjectScene scene;
EngineMock engine;
scene.setEngine(&engine);
scene.setStageScale(2.5);

EXPECT_CALL(engine, stageWidth()).WillOnce(Return(600));
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(400));
EXPECT_CALL(engine, setMouseX(-253.1));
EXPECT_CALL(engine, setMouseY(216.7));
EXPECT_CALL(engine, setMouseX(-281.24));
EXPECT_CALL(engine, setMouseY(206.68));
scene.handleMouseMove(46.9, -16.7);
}

TEST(ProjectScene, HandleMousePress)
TEST(ProjectSceneTest, HandleMousePress)
{
ProjectScene scene;
EngineMock engine;
Expand All @@ -44,7 +54,7 @@ TEST(ProjectScene, HandleMousePress)
scene.handleMousePress();
}

TEST(ProjectScene, HandleMouseRelease)
TEST(ProjectSceneTest, HandleMouseRelease)
{
ProjectScene scene;
EngineMock engine;
Expand All @@ -57,7 +67,7 @@ TEST(ProjectScene, HandleMouseRelease)
scene.handleMouseRelease();
}

TEST(ProjectScene, HandleKeyPressAndRelease)
TEST(ProjectSceneTest, HandleKeyPressAndRelease)
{
static const std::unordered_map<Qt::Key, KeyEvent::Type> SPECIAL_KEY_MAP = {
{ Qt::Key_Space, KeyEvent::Type::Space }, { Qt::Key_Left, KeyEvent::Type::Left }, { Qt::Key_Up, KeyEvent::Type::Up }, { Qt::Key_Right, KeyEvent::Type::Right },
Expand Down
38 changes: 25 additions & 13 deletions test/renderedtarget/renderedtarget_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties)
target.setZ(2.5);
target.setRotation(-78.05);
target.setTransformOriginPoint(QPointF(3.4, 9.7));
target.setStageScale(3.5);

EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544));
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249));
Expand All @@ -87,10 +88,10 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties)
ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7));

target.updateProperties();
ASSERT_EQ(target.width(), 4);
ASSERT_EQ(target.height(), 6);
ASSERT_EQ(target.x(), 295);
ASSERT_EQ(target.y(), 52.5);
ASSERT_EQ(target.width(), 14);
ASSERT_EQ(target.height(), 21);
ASSERT_EQ(target.x(), 1032.5);
ASSERT_EQ(target.y(), 183.75);
ASSERT_EQ(target.z(), 0);
ASSERT_EQ(target.rotation(), 0);
ASSERT_EQ(target.transformOriginPoint(), QPointF(-23, 72));
Expand Down Expand Up @@ -118,6 +119,7 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties)
target.setZ(2.5);
target.setRotation(-78.05);
target.setTransformOriginPoint(QPointF(3.4, 9.7));
target.setStageScale(5.23);

EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544));
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(249));
Expand All @@ -131,14 +133,14 @@ TEST_F(RenderedTargetTest, LoadAndUpdateProperties)
ASSERT_EQ(target.transformOriginPoint(), QPointF(3.4, 9.7));

target.updateProperties();
ASSERT_EQ(target.width(), 5.7592);
ASSERT_EQ(target.height(), 8.6388);
ASSERT_EQ(std::round(target.x() * 100) / 100, 237.18);
ASSERT_EQ(std::round(target.y() * 100) / 100, -100.93);
ASSERT_EQ(std::round(target.width() * 100) / 100, 30.12);
ASSERT_EQ(std::round(target.height() * 100) / 100, 45.18);
ASSERT_EQ(std::round(target.x() * 100) / 100, 1240.43);
ASSERT_EQ(std::round(target.y() * 100) / 100, -527.84);
ASSERT_EQ(target.z(), 3);
ASSERT_EQ(target.rotation(), -157.16);
ASSERT_EQ(target.transformOriginPoint().x(), -33.1154);
ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 103.67);
ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -173.19);
ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 542.17);
ASSERT_TRUE(mirrorHorizontallySpy.empty());

EXPECT_CALL(engine, stageWidth()).WillOnce(Return(544));
Expand Down Expand Up @@ -377,6 +379,7 @@ TEST_F(RenderedTargetTest, LoadSvgCostume)

// Test scale limit
sprite.setSize(maxSize * 250);
target.setStageScale(3.89);

target.loadCostume(costume.get());
ASSERT_TRUE(target.isSvg());
Expand Down Expand Up @@ -406,9 +409,9 @@ TEST_F(RenderedTargetTest, LoadSvgCostume)

ASSERT_EQ(std::round(target.width() * 100) / 100, maxWidth);
ASSERT_EQ(std::round(target.height() * 100) / 100, maxHeight);
ASSERT_EQ(std::round(target.scale() * 100) / 100, 2.5);
ASSERT_EQ(std::round(target.x() * 100) / 100, 11126.36);
ASSERT_EQ(std::round(target.y() * 100) / 100, -6593.27);
ASSERT_EQ(std::round(target.scale() * 100) / 100, 9.73);
ASSERT_EQ(std::round(target.x() * 100) / 100, 11963.59);
ASSERT_EQ(std::round(target.y() * 100) / 100, -5887.67);
ASSERT_EQ(std::round(target.transformOriginPoint().x() * 100) / 100, -10836.66);
ASSERT_EQ(std::round(target.transformOriginPoint().y() * 100) / 100, 6837.42);
}
Expand Down Expand Up @@ -603,3 +606,12 @@ TEST_F(RenderedTargetTest, MouseArea)
target.setMouseArea(&mouseArea);
ASSERT_EQ(target.mouseArea(), &mouseArea);
}

TEST_F(RenderedTargetTest, StageScale)
{
RenderedTarget target;
ASSERT_EQ(target.stageScale(), 1);

target.setStageScale(6.4);
ASSERT_EQ(target.stageScale(), 6.4);
}