From 6651e0c053e6e0811dd412a04190c6f062d1f4fd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 5 Jun 2026 16:51:28 +0000 Subject: [PATCH] Apply builder learnings to the demo + add four showcase scenes Bring the Kit Showcase demo up to the standard of the contraption builder, and expand it from 6 to 10 scenes. Improve (ported patterns): - Build-once: chromeBuilt() + uUIVersion stamp + rebuildCB, so the chrome persists with the saved stack and only the scene physics re-inits per open. - Theme constants: the scattered chrome RGB strings collapse into a kCol* block. - DRY + hover: setBtnIdle/setBtnActive helpers, plus real hover states on the scene/tool tabs (the demo had none); the highlighters route through them. - Wider 1180x760 window with a width-adaptive tab bar that fits all scenes. Expand (four new scenes, reusing proven kit primitives): - Dominoes: a long row of tiles to topple (chain reaction). - Balloons: negative-gravity-scale balls on rope joints to ground pegs. - Wrecking Ball: a heavy hinged arm, pre-lifted, that smashes a brick wall. - Magnets: per-frame b2kPush pulls every shape (and dropped bodies) to the cursor. Tuning constants (kMagPull, kWreckDensity) and a couple of scene geometries may want a tweak in the IDE; the structure follows the existing scene builders. https://claude.ai/code/session_01XpBcQg2DbncrBcFZLHqhMj --- examples/box2dxt-demo.livecodescript | 314 +++++++++++++++++++++++---- 1 file changed, 274 insertions(+), 40 deletions(-) diff --git a/examples/box2dxt-demo.livecodescript b/examples/box2dxt-demo.livecodescript index 21a6c48..f10de73 100644 --- a/examples/box2dxt-demo.livecodescript +++ b/examples/box2dxt-demo.livecodescript @@ -1154,12 +1154,30 @@ local gArenaL, gArenaR, gArenaT, gArenaB local gObjective, gTargets, gSpawnX, gStartX, gCar local gScore, gCoins, gCoinTotal, gWheelBC, gWheelFC -constant kScenes = "playground,pyramid,cradle,bridge,vehicle,lidar" -constant kLabels = "Playground,Pyramid,Cradle,Bridge,Vehicle,Lidar" +constant kScenes = "playground,pyramid,dominoes,cradle,bridge,wrecking,balloons,vehicle,magnets,lidar" +constant kLabels = "Playground,Pyramid,Dominoes,Cradle,Bridge,Wrecking,Balloons,Vehicle,Magnets,Lidar" constant kTools = "box,ball,capsule,poly,bomb" constant kToolLabels = "Box,Ball,Capsule,Poly,Bomb" constant kMaxDrops = 70 +-- Window + chrome version. Bump kUIVersion when the built chrome changes, so an older +-- saved stack rebuilds itself once on the next open. +constant kWinW = 1180, kWinH = 760 +constant kUIVersion = "1" + +-- Chrome theme: one source of truth for the UI colours (RGB strings) so the idle / +-- active / hover styling never drifts apart. +constant kColCardBg = "22,24,30", kColHeaderBg = "30,33,42" +constant kColAccent = "64,132,232", kColAccentTool = "72,190,130" +constant kColBtnIdle = "44,48,58", kColBtnIdleText = "196,200,212" +constant kColBtnHover = "58,64,78", kColBtnText = "255,255,255" +constant kColTitle = "236,238,245", kColSub = "150,154,166" +constant kColBarBg = "18,20,26", kColHudText = "224,226,234", kColHelpText = "168,172,184" + +-- New-scene tuning (forces / heft; tune live in the IDE if needed). +constant kMagPull = 8 -- magnets: per-frame velocity nudge toward the cursor (px/s) +constant kWreckDensity = 6 -- wrecking-ball heft + -- --------------------------------------------------------------------- on preOpenCard startBox2DDemo @@ -1174,15 +1192,31 @@ on startBox2DDemo set the title of this stack to "Box2Dxt — Kit Showcase" set the decorations of this stack to "default" set the resizable of this stack to false - set the width of this stack to 980 - set the height of this stack to 660 + set the width of this stack to kWinW + set the height of this stack to kWinH set the loc of this stack to the screenLoc - set the backgroundColor of this card to "22,24,30" + set the backgroundColor of this card to kColCardBg put "box" into gTool - buildUI + if not chromeBuilt() then -- build the chrome once; it persists in the saved stack + buildUI + set the uUIVersion of this stack to kUIVersion + end if switchScene "playground" end startBox2DDemo +-- The chrome (tab bar, HUD, help) is generated once and saved with the stack; only the +-- scene's physics is rebuilt per open (it is native state and cannot be serialised). +-- A version mismatch — e.g. after the demo is updated — rebuilds the chrome once. +function chromeBuilt + return (there is a graphic "ui_header") and (the uUIVersion of this stack is kUIVersion) +end chromeBuilt + +-- Force a one-time chrome rebuild (after editing the demo, or to repair the UI). +on rebuildCB + set the uUIVersion of this stack to empty + startBox2DDemo +end rebuildCB + on stopBox2DDemo b2kTeardown clearSceneControls @@ -1219,6 +1253,18 @@ on switchScene pScene case "lidar" buildLidar break + case "dominoes" + buildDominoes + break + case "balloons" + buildBalloons + break + case "wrecking" + buildWrecking + break + case "magnets" + buildMagnets + break end switch raiseUI -- keep the chrome above all scene controls b2kStart @@ -1563,6 +1609,142 @@ on buildLidar put "Move the mouse inside the arena — rays scan outward and stop at the nearest shape." into gObjective end buildLidar +-- ===================================================================== +-- More showcase scenes (dominoes · balloons · wrecking ball · magnets) +-- ===================================================================== + +-- A long line of tall, thin tiles. Topple the first (drag the red ball into it, or +-- drop a Ball on it) and the whole row goes down — a satisfying chain reaction. +on buildDominoes + prepArena + local tGroundY, tN, tI, tX, tStep, tTile + put (the height of this card) - 48 into tGroundY + put 22 into tN + put (gArenaR - gArenaL - 170) / tN into tStep + put gArenaL + 95 into tX + repeat with tI = 1 to tN + put newBox(round(tX), tGroundY - 48, 13, 92, accentShape(tI)) into tTile + b2kSetFriction tTile, 0.6 + add tStep to tX + end repeat + get newBall(gArenaL + 45, tGroundY - 130, 42, "214,92,92") -- a draggable nudger + put "Drag the red ball into the first tile (or drop a Ball on it) to start the cascade!" into gObjective +end buildDominoes + +-- Helium balloons: balls with a NEGATIVE gravity scale (so they rise) on rope joints +-- tied to little ground pegs, each with a visible string drawn every frame. +on buildBalloons + prepArena + local tGroundY, tN, tI, tX, tStep, tDiam, tLen, tPegY, tBalloon, tPeg + put (the height of this card) - 48 into tGroundY + put tGroundY - 6 into tPegY + put 7 into tN + put (gArenaR - gArenaL) / (tN + 1) into tStep + repeat with tI = 1 to tN + put round(gArenaL + tI * tStep) into tX + put rnd(44, 58) into tDiam + put rnd(300, 380) into tLen + put newStaticBar(tX, tPegY, 22, 10, "120,126,140") into tPeg -- the string's ground anchor + -- balloon built at the rope distance above the peg, so the joint is satisfied on + -- the first step (teleporting it after tying would jolt the whole thing) + put newBall(tX, tPegY - tLen, tDiam, accentShape(tI)) into tBalloon + b2kSetGravityScale tBalloon, -0.5 + b2kSetDamping tBalloon, 1.0, 1.0 + b2kRope tBalloon, tPeg, tLen + addCable tX, tPegY, tBalloon + end repeat + put "Balloons float up on their strings (negative gravity scale). Drag one and let it bob back." into gObjective +end buildBalloons + +-- A heavy ball hinged to the world on a long arm, pre-lifted to one side so it swings +-- down on its own and smashes a brick wall. Drag it back up for another go. +on buildWrecking + prepArena + local tGroundY, tCx, tPivotX, tPivotY, tLen, tBall, tBallX, tBallY + local tCols, tRows, tColW, tBrickH, tRow, tCol, tBx, tBy, tWallX0 + put (the height of this card) - 48 into tGroundY + put (the width of this card) div 2 into tCx + put tCx - 120 into tPivotX + put gArenaT + 24 into tPivotY + put tGroundY - 60 - tPivotY into tLen -- arm length: ball swings near the floor + -- brick wall to the right, in the path of the swing + put 5 into tCols + put 6 into tRows + put 44 into tColW + put 30 into tBrickH + put tCx + 40 into tWallX0 + repeat with tRow = 0 to tRows - 1 + repeat with tCol = 0 to tCols - 1 + put tWallX0 + tCol * (tColW + 2) + tColW / 2 into tBx + put tGroundY - tBrickH / 2 - tRow * (tBrickH + 1) into tBy + get newBox(round(tBx), round(tBy), tColW, tBrickH, pyramidColor(tRow, tRows)) + end repeat + end repeat + -- decorative crane hub at the pivot + create graphic "demo_cranehub" + set the style of graphic "demo_cranehub" to "rectangle" + set the filled of graphic "demo_cranehub" to true + set the backgroundColor of graphic "demo_cranehub" to "92,98,112" + set the rect of graphic "demo_cranehub" to tPivotX - 10, tPivotY - 10, tPivotX + 10, tPivotY + 10 + -- the ball, built pre-lifted ~40 deg left so its distance to the pivot equals the arm + put round(tPivotX - tLen * 0.643) into tBallX + put round(tPivotY + tLen * 0.766) into tBallY + put newBall(tBallX, tBallY, 76, "58,62,72") into tBall + b2kSetDensity tBall, kWreckDensity + b2kHinge tBall, empty, tPivotX, tPivotY + addCable tPivotX, tPivotY, tBall + put "The wrecking ball swings in on its own — drag it back up and release for another smash!" into gObjective +end buildWrecking + +-- A field of mixed shapes all pulled toward the cursor every frame (b2kPush) — shows +-- how to apply your own per-frame forces. Mouse outside the arena = no pull. +on buildMagnets + prepArena + local tN, tI, tX, tY, tShape + put empty into gTargets + put 16 into tN + repeat with tI = 1 to tN + put rnd(gArenaL + 70, gArenaR - 70) into tX + put rnd(gArenaT + 50, gArenaB - 70) into tY + switch tI mod 3 + case 0 + put newBall(tX, tY, rnd(30, 50), accentShape(tI)) into tShape + break + case 1 + put newBox(tX, tY, rnd(34, 52), rnd(34, 52), accentShape(tI)) into tShape + break + default + put newPoly(tX, tY, rnd(22, 36), accentShape(tI)) into tShape + end switch + b2kSetDamping tShape, 1.5, 1.5 + put tShape & cr after gTargets + end repeat + put "Move the mouse inside the arena — every shape is pulled toward the cursor!" into gObjective +end buildMagnets + +-- Pull every magnet-scene body (and anything you've dropped) toward the cursor. +on frameMagnets + local tMx, tMy, t, tx, ty, dx, dy, tDist + put the mouseH into tMx + put the mouseV into tMy + if tMx < gArenaL or tMx > gArenaR or tMy < gArenaT or tMy > gArenaB then exit frameMagnets + repeat for each line t in (gTargets & gDrops) + if t is empty then next repeat + put empty into tx + try + put item 1 of the loc of t into tx + put item 2 of the loc of t into ty + catch tErr + end try + if tx is empty then next repeat + put tMx - tx into dx + put tMy - ty into dy + put sqrt(dx * dx + dy * dy) into tDist + if tDist < 26 then next repeat + b2kPush t, dx / tDist * kMagPull, dy / tDist * kMagPull + end repeat +end frameMagnets + -- ===================================================================== -- Per-frame hook (the Kit sends this once per simulated frame) -- ===================================================================== @@ -1577,6 +1759,9 @@ on b2kFrame case "pyramid" framePyramid break + case "magnets" + frameMagnets + break end switch renderCables updateFlashes @@ -1875,6 +2060,32 @@ on mouseRelease b2kRelease end mouseRelease +-- Lighten a tab on hover unless it is the active one (active state is derived from +-- gScene/gTool, never the pixel colour); mouseLeave repaints the correct idle/active. +on mouseEnter + local tName + put the short name of the target into tName + if "ui_scene_" is in tName or "ui_tool_" is in tName then hoverBtn the long id of the target +end mouseEnter + +on mouseLeave + local tName + put the short name of the target into tName + if "ui_scene_" is in tName then highlightScenes + else if "ui_tool_" is in tName then highlightTools +end mouseLeave + +on hoverBtn pBtn + if there is not a button pBtn then exit hoverBtn + local tActive + if "ui_scene_" is in the short name of pBtn then + put (the uSceneId of pBtn is gScene) into tActive + else + put (the uToolId of pBtn is gTool) into tActive + end if + if not tActive then set the backgroundColor of pBtn to kColBtnHover +end hoverBtn + on arrowKey pKey if gScene is "vehicle" then if pKey is "right" then @@ -1984,23 +2195,30 @@ end newStaticBar -- ===================================================================== on buildUI clearUI - local tWide, tI, tScene, tTool, tX + local tWide, tHigh, tI, tScene, tTool, tX, tToolsX, tStep, tBtnW, tNScenes put the width of this card into tWide - - makeBar "ui_header", 0, 0, tWide, 54, "30,33,42" - makeBar "ui_accent", 0, 54, tWide, 57, "64,132,232" - makeLabel "ui_title", 18, 11, 320, 44, "Box2Dxt", 22, "236,238,245", true - makeLabel "ui_sub", 20, 33, 460, 53, "Box2D v3 physics · the OpenXTalk Kit, in action", 11, "150,154,166", false - - put 16 into tX - repeat with tI = 1 to the number of items of kScenes + put the height of this card into tHigh + + makeBar "ui_header", 0, 0, tWide, 54, kColHeaderBg + makeBar "ui_accent", 0, 54, tWide, 57, kColAccent + makeLabel "ui_title", 18, 11, 320, 44, "Box2Dxt", 22, kColTitle, true + makeLabel "ui_sub", 20, 33, 560, 53, "Box2D v3 physics · the OpenXTalk Kit, in action", 11, kColSub, false + + -- Tools pinned to the right; scene tabs fill the space to their left, so the bar + -- adapts to the window width and the number of scenes (no fixed widths to outgrow). + put tWide - (the number of items of kTools) * 62 - 8 into tToolsX + put 12 into tX + put the number of items of kScenes into tNScenes + put (tToolsX - 24 - tX) / tNScenes into tStep + put tStep - 4 into tBtnW + repeat with tI = 1 to tNScenes put item tI of kScenes into tScene - makeButton ("ui_scene_" & tScene), (item tI of kLabels), tX, 66, 96, 28 + makeButton ("ui_scene_" & tScene), (item tI of kLabels), round(tX), 66, round(tBtnW), 28 set the uSceneId of button ("ui_scene_" & tScene) to tScene - add 100 to tX + add tStep to tX end repeat - put tWide - (5 * 62) - 8 into tX + put tToolsX into tX repeat with tI = 1 to the number of items of kTools put item tI of kTools into tTool makeButton ("ui_tool_" & tTool), (item tI of kToolLabels), tX, 66, 58, 28 @@ -2008,10 +2226,10 @@ on buildUI add 62 to tX end repeat - makeBar "ui_hudbar", 0, 100, tWide, 126, "18,20,26" - makeField "ui_hud", 12, 103, tWide - 12, 124, 12, "224,226,234" - makeBar "ui_helpbar", 0, (the height of this card) - 28, tWide, the height of this card, "18,20,26" - makeField "ui_help", 12, (the height of this card) - 26, tWide - 12, (the height of this card) - 5, 11, "168,172,184" + makeBar "ui_hudbar", 0, 100, tWide, 126, kColBarBg + makeField "ui_hud", 12, 103, tWide - 12, 124, 12, kColHudText + makeBar "ui_helpbar", 0, tHigh - 28, tWide, tHigh, kColBarBg + makeField "ui_help", 12, tHigh - 26, tWide - 12, tHigh - 5, 11, kColHelpText end buildUI on clearUI @@ -2077,33 +2295,33 @@ on makeButton pName, pLabel, pX, pY, pW, pH set the rect of button pName to pX, pY, pX + pW, pY + pH end makeButton +-- Idle / active button styling in one place, so the colours never drift apart and the +-- hover helper can reason about selection state instead of reading pixels. +on setBtnIdle pBtn + if there is not a button pBtn then exit setBtnIdle + set the backgroundColor of button pBtn to kColBtnIdle + set the textColor of button pBtn to kColBtnIdleText +end setBtnIdle + +on setBtnActive pBtn, pColor + if there is not a button pBtn then exit setBtnActive + set the backgroundColor of button pBtn to pColor + set the textColor of button pBtn to kColBtnText +end setBtnActive + on highlightScenes local tScene repeat for each item tScene in kScenes - if there is a button ("ui_scene_" & tScene) then - if tScene is gScene then - set the backgroundColor of button ("ui_scene_" & tScene) to "64,132,232" - set the textColor of button ("ui_scene_" & tScene) to "255,255,255" - else - set the backgroundColor of button ("ui_scene_" & tScene) to "44,48,58" - set the textColor of button ("ui_scene_" & tScene) to "196,200,212" - end if - end if + if tScene is gScene then setBtnActive ("ui_scene_" & tScene), kColAccent + else setBtnIdle ("ui_scene_" & tScene) end repeat end highlightScenes on highlightTools local tTool repeat for each item tTool in kTools - if there is a button ("ui_tool_" & tTool) then - if tTool is gTool then - set the backgroundColor of button ("ui_tool_" & tTool) to "72,190,130" - set the textColor of button ("ui_tool_" & tTool) to "255,255,255" - else - set the backgroundColor of button ("ui_tool_" & tTool) to "44,48,58" - set the textColor of button ("ui_tool_" & tTool) to "196,200,212" - end if - end if + if tTool is gTool then setBtnActive ("ui_tool_" & tTool), kColAccentTool + else setBtnIdle ("ui_tool_" & tTool) end repeat end highlightTools @@ -2154,6 +2372,14 @@ function sceneTitle pScene return "VEHICLE — wheel joints, a motor & spring suspension" case "lidar" return "LIDAR — live ray casting that follows your mouse" + case "dominoes" + return "DOMINOES — a chain reaction of toppling tiles" + case "balloons" + return "BALLOONS — buoyancy from a negative gravity scale, on rope joints" + case "wrecking" + return "WRECKING BALL — a heavy hinged arm vs a brick wall" + case "magnets" + return "MAGNETS — per-frame forces pull every shape to your cursor" end switch return pScene end sceneTitle @@ -2170,6 +2396,14 @@ function sceneHelp pScene return "Choose the Bomb tool and click beside the stack to scatter it with a blast." & tTail case "cradle" return "Momentum carries through the line of balls. Drag one aside and let go." & tTail + case "dominoes" + return "Drag the red ball into the first tile (or drop a Ball on it) to start the cascade." & tTail + case "balloons" + return "Each balloon rises on its string via a negative gravity scale. Drag them around." & tTail + case "wrecking" + return "The ball swings in by itself. Drag it back up and release to smash the wall again." & tTail + case "magnets" + return "Move the mouse inside the arena — every shape is pulled toward the cursor (b2kPush)." end switch return "Bodies flash white the instant they touch (contact events)." & tTail end sceneHelp