From b192bcde1eb84dc17256b146744aff57403307f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 4 Jul 2026 20:41:40 -0300 Subject: [PATCH] Fix stale width being reused during the deferred CSS relayout. This happened when a display was being switched and during the layout change we didn't reset to the initial state. --- .../assets/html/inline_block_wrap_defer.html | 244 ++---------------- .../html/inline_block_wrap_files/88x31.png | Bin 1469 -> 0 bytes src/eepp/ui/uihtmlwidget.cpp | 20 ++ src/eepp/ui/uirichtext.cpp | 4 + src/tests/unit_tests/uihtml_tests.cpp | 77 ++++++ src/tests/unit_tests/uiwebview_tests.cpp | 6 +- 6 files changed, 130 insertions(+), 221 deletions(-) delete mode 100644 bin/unit_tests/assets/html/inline_block_wrap_files/88x31.png diff --git a/bin/unit_tests/assets/html/inline_block_wrap_defer.html b/bin/unit_tests/assets/html/inline_block_wrap_defer.html index ec77f64d8..2262c7dc4 100644 --- a/bin/unit_tests/assets/html/inline_block_wrap_defer.html +++ b/bin/unit_tests/assets/html/inline_block_wrap_defer.html @@ -1,220 +1,28 @@ - - - - - - - Space Cadet Pinball on Linux • Stephen Brennan - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - -
- -
- -
- -

Space Cadet Pinball on Linux

-Stephen Brennan • 09 May 2026 - -

To my fellow Linux users who grew up using Windows XP: did you know, you can -have Space Cadet Pinball on your Linux machine? This is not breaking news, but -it’s exciting to me, and I’m the one who decides what I write about. So here’s -your PSA!

- -

Space Cadet Pinball was bundled with Windows XP, and growing up I played it a -lot. As a result it holds a special place in my heart. I found that it was the -most engaging game that was bundled with Windows. Solitaire was too mindless, -and Freecell, Hearts, and Minesweeper were too complex and boring to me at that -age. But pinball held my attention, and so I played it a lot1.

- -

Anyway, somebody has gone to the effort of using a decompiler and reverse -engineering tools to create source code, and then put in what I’d imagine is a -lot of effort to make it playable on a lot of platforms! All that results in -this project on Github. The -easiest way to play on Linux is actually to use the Flatpak, which comes bundled -with the original game resources from the Windows version. You can either -install it with a GUI (e.g. KDE Discover) or install on the CLI:

- -
flatpak install com.github.k4zmu2a.spacecadetpinball
-
- -

That’s all you need to play & get a hit of nostalgia! I know there are -browser-based versions available too, but I would -rather have it installed on my computer directly.

- -

High(er) Resolution With Full Tilt Data

- -

Graphics technology has come a long way since then, and the graphics are a bit -rough at 480p. But fear not: another version of the game existed, called Full -Tilt! Pinball. Its game data is capable of displaying at the massive screen -resolution 1024x768! You can find this game data on -archive.org in a zip file.

- -

Getting the flatpak version to use these data files is a bit tricky. The easiest -way is:

- -
    -
  1. Ensure that you’ve run the game at least once prior to this, so that the data -directory is created.
  2. -
  3. Extract the downloaded zip file directly into your data directory: -
    cd ~/.var/app/com.github.k4zmu2a.spacecadetpinball/data/SpaceCadetPinball
    -unzip ~/Downloads/CADET.ZIP
    -
    -
  4. -
  5. Delete (or, if you’re cautious, rename) the old data directory which is -bundled with the app. Unfortunately this is necessary because the game -searches multiple locations for data, but once it finds data files in one -directory, it won’t continue looking for files in other locations. -
    sudo rm -r $(flatpak info --show-location com.github.k4zmu2a.spacecadetpinball)/files/extra/Pinball
    -
    -

    You may not need the sudo call if your installation was per-user. Mine got -installed to /var/lib/flatpak so I needed root.

    -
  6. -
- -

It’s possible you’d need to repeat step 3 if the game gets updated. However, the -flatpak -hasn’t seen an update in over two years. I doubt one will happen, and if it -does, it won’t be frequent.

- -

Random Notes

- -
    -
  1. -

    If you want, you can keep the original files and merge them together so that -you have the full set of both the original (referred to as “3DPB” for 3D -Pinball in the game) and the new (Full Tilt) data. Then, the game will let -you toggle between them if you’d like.

    -
  2. -
  3. -

    The data files seem to have some impact on the game rules. For instance, in -the original 3DPB version, the reentry lanes (and launch lanes) have lights -which toggle as the ball passes over. In the Full Tilt version, the lights -stay on (rather than toggling), making it easier to complete the set of -lights and upgrade the associated set of bumpers. (Yes yes, I know I’m a nerd -for noticing this.)

    -
  4. -
  5. -

    Apparently, there was a game called “Marble Blast” which came pre-installed -on some Macs in a roughly similar time period, which I know people developed -similar relationships to growing up. Unlike this pinball game, the Marble -Blast series grew, and there are newer versions available to play today.

    -
  6. -
- -

Bonus Thoughts

- -

I think it’s great that this old game was beloved to enough people (and -especially at least one very competent & motivated person) to do this work. -Having (any) source code available makes this game portable to all sorts of -platforms, which is really great. You can play this on Mac, Windows, Linux, and -even Android & Nintendo Switch, apparently.

- -

Personally, I would be happy to pay the original developers for their work on -this game, and I understand that there’s some concern about the legality of -downloading game data files, especially for the Full Tilt version of the game. -After all, they are copyrighted art & data which was part of a commercial -product. It seems like an unpopular opinion in today’s world, but I don’t -advocate for piracy. Paying people for their work is important, even when it -feels like you’re paying a faceless corporation. While I’d prefer things be -created with a FOSS license, the world doesn’t always work that way. At the end -of the day, I want people to be paid to create good things, because that’s how -we get more good things!

- -

On the other hand, I feel software preservation is an important goal too. -Ideally, I’d like to see a world where proprietary software like this could be -placed into some sort of source code -escrow. As long as the -original copyright holders are in the business of selling their product, their -rights should be respected. But if they elect to stop selling it, I think that -code should revert to a FOSS license that allows users to improve & maintain the -software they use. This would help balance the rights of creators, users, and -the goals of preservation.

- -
- -
-
    -
  1. -

    I also played another pinball game called 3D Ultra Pinball, but that came - on a CD presumably for purchase. I have no idea whether my parents went - out and bought it, or if it came in a cereal box. (Yes, game CDs did show - up in cereal boxes sometimes.) ↩

    -
  2. -
-
- - - -
- - - -
-
- Legal • - RSS -

- Creative Commons License -

- Stephen Brennan's Blog is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License -
- -
- - - + + diff --git a/bin/unit_tests/assets/html/inline_block_wrap_files/88x31.png b/bin/unit_tests/assets/html/inline_block_wrap_files/88x31.png deleted file mode 100644 index 41edcfd503fb8cd17f2bf3a1f8c9bd3006244836..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1469 zcmXxkdpOg390%~9Fj|>)JITa$OemX`B6Uu#xs-dlTc(uike%GaafwRxBrS@{jz>~q zS2MG*4YRvp)f2fk&7}*<;h4(#`QyBw*Z2E=K7V|_|9qe4TM!cDYXUWg0svs*@8=x` z_Azi-8S8_4_%FI1u=Enbd_DDgr4Cu(U=ZUG=m7xqI~j|iBydKa@e2zCfGaitkbM;Z z7M8%C0DvSM08A19V9!GU*cg8=Qse{P!w(4v-w&Pvv-E#vW@e_QrpCv{G#btD(D2aE z(3{t<)o*%wUW2;3UxB*1Kx&mrt?KOQ?Ce%5mnl>#m9k5rSSDAf@vMAJg3XMsn)Ynm%byNnmj#*pFsG(A+HT0LY zH8qtL739kD^1q*u$>j1UPsk-@C8Z?~OCFFOl8WvZ6_JXH3YSxGyWr0CTY1;=^78X? z^KvNF;#(vlMriI?MvMB=5`*fWt)1Ofqn3Lg?00>;bR%iZ05pW8k+ z7uQ|RyLLEj$Ki0>F<1-+V{dP7XKx2)tF0{>jkdNzf)N|3ahjLpil7k_6SeyW0fo(UF)%49KS1UOmP0ybW~%A}Xvp{kL5CyshO-zIbVp zJWDXUD0Q4ip0lz0WA&VwaNLPlN$vON9C+)wqs%vv(Sg#LpV*sDkA?<$u1^8Roza`z z|4|(df2_`c%pF`&*q>KC8tqR|@=ktrIU^13z}-r^f$tKaA)Mb1XC3iLiphjfS}vHP zTux%G9Z$`qemu$5UGuSh_AFT-ORl3;{vP}Ty0FMrF}`CpPP9>NvWGp&C_0qzfQwzQ zN}PrDI3R-t?4IY`-F;qF899C8r@Lg^!qJWx0zK6b8IV|Jbgbc4%QI(1Zm#t!gY|f8 zS3qaDH0ox?bRZwjgkZTmLnvg;4UuL<@zv7m2;r7v)$v)=!p+bvg;_@7(`lQJ4fnwJ zBSS>hIa?1z?#=ARA77*~<5Oq5?EL0G#d)5KK5ms?V+Pv^5AN$}mKJ^?E_xsbTZw4B8nG?bj z#jS4ALlnX)cmmp%X@nm(DxzkObABN)AoEXrpW8Sl%5d0dEaK{!k0edcwJ+*+e7e3- z@r0@F`UGUVE%#r}4=Xi%>Qw^js*Ml!-Ohtvv;@@WZ+uCwp+Z=TV~(-btazqd=*MT2 zZe#S`-2GknH5Re@w1=5@ec+)uN0u?RT5VJBu)Q`-asType()->isFlex() || parentNode->asType()->isGrid() ); if ( isInlineBlockTextSpan && mode == IntrinsicMode::None && + container->getLayoutWidthPolicy() != SizePolicy::WrapContent && container->getPixelsSize().getWidth() > 0 ) { maxWidth = container->getPixelsSize().getWidth() - container->getPixelsContentOffset().Left - container->getPixelsContentOffset().Right; + } else if ( isInlineBlockTextSpan && mode == IntrinsicMode::None && + container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { + maxWidth = 0; } else if ( parentIsFlexOrGrid && container->getLayoutWidthPolicy() == SizePolicy::WrapContent && mode == IntrinsicMode::None ) { diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 8f1adc934..e5c43b27b 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -2832,6 +2832,83 @@ UTEST( UIHTML, InlineBlockWrapIssue ) { Engine::destroySingleton(); } +UTEST( UIHTML, DeferredInlineBlockListDoesNotWrapItems ) { + Engine::instance()->createWindow( WindowSettings( 1024, 653, "Deferred Inline Block List Test", + WindowStyle::Default, WindowBackend::Default, + 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + auto* win = Engine::instance()->getCurrentWindow(); + UI::UISceneNode* sceneNode = init_test_inline_block(); + + UIWebView* webView = UIWebView::New(); + webView->setParent( sceneNode->getRoot() ); + webView->setPixelsSize( 1280, 600 ); + webView->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + webView->loadURI( URI( "./assets/html/inline_block_wrap_defer.html" ) ); + + UISceneNode* documentScene = webView->getDocumentSceneNode(); + ASSERT_TRUE( documentScene != nullptr ); + + UIWidget* ul = nullptr; + + for ( int i = 0; i < 100; i++ ) { + win->getInput()->update(); + SceneManager::instance()->update( Milliseconds( 16 ) ); + ul = documentScene->getRoot()->findByTag( "ul" ); + if ( ul ) { + auto lis = ul->findAllByTag( "li" ); + if ( !lis.empty() && + lis.front()->asType()->getDisplay() == CSSDisplay::InlineBlock ) + break; + } + Sys::sleep( Milliseconds( 1 ) ); + } + + ASSERT_TRUE( ul != nullptr ); + auto lis = ul->findAllByTag( "li" ); + ASSERT_EQ( lis.size(), (size_t)4 ); + + const Float rowY = lis.front()->getPixelsPosition().y; + Float previousRight = lis.front()->getPixelsPosition().x; + + for ( auto li : lis ) { + auto* htmlLi = li->asType(); + auto* richLi = li->asType(); + ASSERT_TRUE( htmlLi != nullptr ); + ASSERT_TRUE( richLi != nullptr ); + auto* anchor = li->findByTag( "a" ); + ASSERT_TRUE( anchor != nullptr ); + auto* htmlAnchor = anchor->asType(); + auto* richAnchor = anchor->asType(); + ASSERT_TRUE( htmlAnchor != nullptr ); + ASSERT_TRUE( richAnchor != nullptr ); + + EXPECT_EQ( htmlLi->getDisplay(), CSSDisplay::InlineBlock ); + EXPECT_EQ( htmlAnchor->getDisplay(), CSSDisplay::InlineBlock ); + EXPECT_EQ( richLi->getRichTextPtr()->getLines().size(), (size_t)1 ); + EXPECT_EQ( richAnchor->getRichTextPtr()->getLines().size(), (size_t)1 ); + const auto& anchorLine = richAnchor->getRichTextPtr()->getLines().front(); + const Rectf liContentOffset = li->asType()->getPixelsContentOffset(); + const Rectf anchorContentOffset = anchor->asType()->getPixelsContentOffset(); + EXPECT_NEAR( li->getPixelsSize().getHeight(), + anchorLine.height + liContentOffset.Top + liContentOffset.Bottom, 1.f ); + EXPECT_NEAR( anchor->getPixelsSize().getHeight(), + anchorLine.height + anchorContentOffset.Top + anchorContentOffset.Bottom, + 1.f ); + EXPECT_EQ( li->getPixelsPosition().y, rowY ); + EXPECT_GE( li->getPixelsPosition().x, previousRight ); + EXPECT_GT( li->getPixelsSize().getWidth(), 0 ); + EXPECT_GE( li->getPixelsSize().getWidth() + 1.f, li->getMaxIntrinsicWidth() ); + EXPECT_GE( anchor->getPixelsSize().getWidth() + 1.f, anchor->getMaxIntrinsicWidth() ); + + previousRight = li->getPixelsPosition().x + li->getPixelsSize().getWidth(); + } + + Engine::destroySingleton(); +} + UTEST( UIHTML, InlineBlockBrowserTest ) { Engine::instance()->createWindow( WindowSettings( 1024, 653, "Inline Block Browser Test", WindowStyle::Default, WindowBackend::Default, diff --git a/src/tests/unit_tests/uiwebview_tests.cpp b/src/tests/unit_tests/uiwebview_tests.cpp index 1457b5b29..7cff90a30 100644 --- a/src/tests/unit_tests/uiwebview_tests.cpp +++ b/src/tests/unit_tests/uiwebview_tests.cpp @@ -1504,7 +1504,7 @@ UTEST( UIWebView, RemoteImageIgnoredAfterNavigation ) { std::string imageData; const std::string imagePath = - Sys::getProcessPath() + "assets/html/inline_block_wrap_files/88x31.png"; + Sys::getProcessPath() + "assets/html/reddit_old_thread_files/pixel.png"; ASSERT_TRUE( FileSystem::fileGet( imagePath, imageData ) ); ASSERT_FALSE( imageData.empty() ); @@ -1614,7 +1614,7 @@ UTEST( UIWebView, RemoteBackgroundImageIgnoredAfterNavigation ) { std::string imageData; const std::string imagePath = - Sys::getProcessPath() + "assets/html/inline_block_wrap_files/88x31.png"; + Sys::getProcessPath() + "assets/html/reddit_old_thread_files/pixel.png"; ASSERT_TRUE( FileSystem::fileGet( imagePath, imageData ) ); ASSERT_FALSE( imageData.empty() ); @@ -2667,7 +2667,7 @@ UTEST( UIWebView, DestroyWithPendingSubresourcesIsSafe ) { std::string imageData; const std::string imagePath = - Sys::getProcessPath() + "assets/html/inline_block_wrap_files/88x31.png"; + Sys::getProcessPath() + "assets/html/reddit_old_thread_files/pixel.png"; ASSERT_TRUE( FileSystem::fileGet( imagePath, imageData ) ); ASSERT_FALSE( imageData.empty() );