Skip to content

Commit 44184aa

Browse files
committed
Add keepInFence method to Sprite
1 parent 2f7619f commit 44184aa

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

include/scratchcpp/sprite.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
6565
void setRotationStyle(const char *newRotationStyle);
6666

6767
Rect boundingRect() const;
68+
void keepInFence(double newX, double newY, double *fencedX, double *fencedY) const;
6869

6970
double graphicsEffectValue(IGraphicsEffect *effect) const;
7071
void setGraphicsEffectValue(IGraphicsEffect *effect, double value);

src/scratch/sprite.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,54 @@ Rect Sprite::boundingRect() const
353353
return ret;
354354
}
355355

356+
/*!
357+
* Keeps the desired position within the stage.
358+
* \param[in] New desired X position.
359+
* \param[in] New desired Y position.
360+
* \param[out] Fenced X position.
361+
* \param[out] Fenced Y position.
362+
*/
363+
void Sprite::keepInFence(double newX, double newY, double *fencedX, double *fencedY) const
364+
{
365+
// See https://github.com/scratchfoundation/scratch-vm/blob/05dcbc176f51da34aeb9165559fc6acba8087ff8/src/sprites/rendered-target.js#L915-L948
366+
IEngine *eng = engine();
367+
368+
if (!(fencedX && fencedY && eng))
369+
return;
370+
371+
double stageWidth = eng->stageWidth();
372+
double stageHeight = eng->stageHeight();
373+
Rect fence(-stageWidth / 2, stageHeight / 2, stageWidth / 2, -stageHeight / 2);
374+
Rect bounds;
375+
impl->getBoundingRect(&bounds);
376+
377+
// Adjust the known bounds to the target position
378+
bounds.setLeft(bounds.left() + newX - impl->x);
379+
bounds.setRight(bounds.right() + newX - impl->x);
380+
bounds.setTop(bounds.top() + newY - impl->y);
381+
bounds.setBottom(bounds.bottom() + newY - impl->y);
382+
383+
// Find how far we need to move the target position
384+
double dx = 0;
385+
double dy = 0;
386+
387+
if (bounds.left() < fence.left()) {
388+
dx += fence.left() - bounds.left();
389+
}
390+
if (bounds.right() > fence.right()) {
391+
dx += fence.right() - bounds.right();
392+
}
393+
if (bounds.top() > fence.top()) {
394+
dy += fence.top() - bounds.top();
395+
}
396+
if (bounds.bottom() < fence.bottom()) {
397+
dy += fence.bottom() - bounds.bottom();
398+
}
399+
400+
*fencedX = newX + dx;
401+
*fencedY = newY + dy;
402+
}
403+
356404
/*! Returns the value of the given graphics effect. */
357405
double Sprite::graphicsEffectValue(IGraphicsEffect *effect) const
358406
{

test/scratch_classes/sprite_test.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,95 @@ TEST(SpriteTest, BoundingRect)
686686
ScratchConfiguration::removeImageFormat("test");
687687
}
688688

689+
TEST(SpriteTest, KeepInFence)
690+
{
691+
auto imageFormatFactory = std::make_shared<ImageFormatFactoryMock>();
692+
auto imageFormat = std::make_shared<ImageFormatMock>();
693+
694+
ScratchConfiguration::registerImageFormat("test", imageFormatFactory);
695+
EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat));
696+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0));
697+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0));
698+
auto costume = std::make_shared<Costume>("costume1", "a", "test");
699+
700+
Sprite sprite;
701+
sprite.addCostume(costume);
702+
sprite.setCostumeIndex(0);
703+
704+
static char data[5] = "abcd";
705+
EXPECT_CALL(*imageFormat, setData(5, data));
706+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
707+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
708+
709+
EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
710+
EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
711+
EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
712+
EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
713+
714+
EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
715+
EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
716+
EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
717+
EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
718+
719+
EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
720+
EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
721+
EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
722+
EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
723+
costume->setData(5, data);
724+
725+
double fencedX = -1, fencedY = -1;
726+
EngineMock engine;
727+
sprite.setEngine(&engine);
728+
729+
EXPECT_CALL(engine, requestRedraw());
730+
sprite.setDirection(45);
731+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
732+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
733+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
734+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
735+
sprite.keepInFence(0, 0, &fencedX, &fencedY);
736+
ASSERT_EQ(fencedX, 0);
737+
ASSERT_EQ(fencedY, 0);
738+
739+
EXPECT_CALL(engine, requestRedraw()).Times(2);
740+
EXPECT_CALL(engine, spriteFencingEnabled()).Times(2).WillRepeatedly(Return(false));
741+
sprite.setX(100);
742+
sprite.setY(60);
743+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
744+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
745+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
746+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
747+
sprite.keepInFence(240, 180, &fencedX, &fencedY);
748+
ASSERT_EQ(std::round(fencedX * 100) / 100, 238.94);
749+
ASSERT_EQ(std::round(fencedY * 100) / 100, 179.65);
750+
751+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
752+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
753+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
754+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
755+
sprite.keepInFence(240, -180, &fencedX, &fencedY);
756+
ASSERT_EQ(std::round(fencedX * 100) / 100, 238.94);
757+
ASSERT_EQ(std::round(fencedY * 100) / 100, -178.94);
758+
759+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
760+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
761+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
762+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
763+
sprite.keepInFence(-240, -180, &fencedX, &fencedY);
764+
ASSERT_EQ(std::round(fencedX * 100) / 100, -238.23);
765+
ASSERT_EQ(std::round(fencedY * 100) / 100, -178.94);
766+
767+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
768+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
769+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
770+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
771+
sprite.keepInFence(-240, 180, &fencedX, &fencedY);
772+
ASSERT_EQ(std::round(fencedX * 100) / 100, -238.23);
773+
ASSERT_EQ(std::round(fencedY * 100) / 100, 179.65);
774+
775+
ScratchConfiguration::removeImageFormat("test");
776+
}
777+
689778
TEST(SpriteTest, GraphicsEffects)
690779
{
691780
auto c1 = std::make_shared<Costume>("", "", "");

0 commit comments

Comments
 (0)