diff --git a/CHANGELOG.md b/CHANGELOG.md index c20156a..370cd5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,14 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `2`). float**, **spin-lock**, **fixed ↔ free movement**, plus per-object specials (fan direction/strength/zone size, magnet pull/push/strength/reach, bomb blast radius/power and fuse). Everything round-trips through the save file. + - **Duplicate tool.** Click any part to drop an identical copy — same size, + colour, material and special settings — then drag it into place (it auto-selects + and switches to the Drag tool). + - **Reset.** A one-click "Reset" returns every part to exactly where it was the + moment you last pressed Run, so you can replay a contraption again and again. + - **Clearer Build vs Run.** The top accent turns amber and the subtitle reads + "RUNNING" while the simulation is live (green/neutral while building), and + picking any build tool while running drops you straight back into Build mode. - **New objects, all built from existing Kit primitives:** **helium balloons** (rise via negative gravity scale; lift parts by a rope; pop in a blast), **bombs** (detonate on a hard hit, a pressure-plate signal, or an optional diff --git a/examples/box2dxt-contraption-builder.livecodescript b/examples/box2dxt-contraption-builder.livecodescript index e13fc97..2cc70dc 100644 --- a/examples/box2dxt-contraption-builder.livecodescript +++ b/examples/box2dxt-contraption-builder.livecodescript @@ -1243,9 +1243,10 @@ local gRingN, gRingX, gRingY, gRingF local gDynParts, gDynDirty -- selection / inspector / onboarding / stress-test bookkeeping local gSelPart, gHoverId, gOnbStep, gStressCount +local gPreRun -- snapshot of the build, taken when Run starts -constant kShapeTools = "drag,box,ball,capsule,poly,image,anchor,delete" -constant kShapeLabels = "Drag,Box,Ball,Capsule,Poly,Image,Anchor,Delete" +constant kShapeTools = "drag,box,ball,capsule,poly,image,anchor,delete,duplicate" +constant kShapeLabels = "Drag,Box,Ball,Capsule,Poly,Image,Anchor,Delete,Duplicate" constant kSpecialTools = "balloon,bomb,plate,fan,magnet" constant kSpecialLabels = "Balloon,Bomb,Pressure Plate,Fan,Magnet" constant kJointTools = "hinge,weld,rope,slider,wheel" @@ -1259,7 +1260,7 @@ constant kTopBarH = 54, kAccentH = 3 constant kPaletteW = 196, kInspectorW = 248, kStatusH = 26 constant kCanvasInset = 2, kGroundBarH = 22 constant kPalPadX = 12, kPalBtnH = 26, kPalBtnGap = 4 -constant kPalSectionGap = 12, kPalHeaderH = 18 +constant kPalSectionGap = 8, kPalHeaderH = 18 -- Materials, motors, feedback timing, and default part sizes (were magic numbers). constant kBounceOn = 0.7, kFlashMs = 150, kSelectLine = 3 @@ -1405,9 +1406,9 @@ on makeTopBar makeBar "ui_accent", 0, kTopBarH, tWide, kTopBarH + kAccentH, "72,190,130" makeLabel "ui_title", 18, 7, 280, 33, "Box2Dxt", 22, "236,238,245", true makeLabel "ui_sub", 20, 32, 560, 50, "Contraption Builder — build a machine, then press Run to bring it to life", 11, "150,154,166", false - put "mode,clear,save,load,recipes,info" into tIds - put "Run,Clear,Save,Load,Recipes,?" into tLbls - put "84,64,64,64,84,30" into tWidths + put "mode,reset,clear,save,load,recipes,info" into tIds + put "Run,Reset,Clear,Save,Load,Recipes,?" into tLbls + put "84,60,64,64,64,84,30" into tWidths put 6 into tGap put 0 into tW repeat for each item tId in tWidths @@ -1631,6 +1632,7 @@ on highlightGroup pPrefix, pItems, pActiveId, pEnabled, pActiveColor end highlightGroup on highlightActions + local tAccent, tSub if there is a button "ui_act_mode" then if gMode is "run" then set the label of button "ui_act_mode" to "Build" @@ -1641,6 +1643,17 @@ on highlightActions end if set the textColor of button "ui_act_mode" to "255,255,255" end if + -- A clear, full-width cue for which mode you're in: green accent + neutral + -- subtitle while building; amber accent + a "running" subtitle while simulating. + if gMode is "run" then + put "232,150,60" into tAccent + put "RUNNING — drag parts to play; press Build or Reset to edit" into tSub + else + put "72,190,130" into tAccent + put "Contraption Builder — build a machine, then press Run to bring it to life" into tSub + end if + if there is a graphic "ui_accent" then set the backgroundColor of graphic "ui_accent" to tAccent + if there is a field "ui_sub" then set the text of field "ui_sub" to tSub setToggle "ui_act_motor", gMotorOn setToggle "ui_act_bouncy", gBouncy end highlightActions @@ -1687,6 +1700,10 @@ on mouseDown end if exit mouseDown end if + if gTool is "duplicate" then + if tHit is not empty then duplicatePart tHit + exit mouseDown + end if if tHit is not empty then put tHit into gDragCtrl put "build" into gDragMode @@ -1748,6 +1765,8 @@ on handleUiClick hideRecipesMenu exit handleUiClick end if + -- Picking a build tool while the simulation runs drops you back into Build. + if ("ui_tool_" is in tName or "ui_joint_" is in tName) and gMode is "run" then toggleMode if "ui_tool_" is in tName then put the uToolId of the target into gTool put empty into gJointTool @@ -1778,6 +1797,9 @@ on handleAction pId case "mode" toggleMode break + case "reset" + resetMachine + break case "clear" clearAll break @@ -1855,6 +1877,7 @@ end handleAction on toggleMode if gMode is "build" then + put serializeText() into gPreRun -- remember the layout so Reset can restore it put "run" into gMode put empty into gJointTool resetPend @@ -2462,6 +2485,35 @@ on deletePart pCtrl put "Deleted a part and any joints on it." into gStatus end deletePart +-- Drop an identical copy of a part — same size, colour, material and special +-- settings — nudged off the original, then select it and switch to Drag so it +-- can be moved straight away. (Joints aren't copied; they connect two parts.) +on duplicatePart pCtrl + if pCtrl is empty then exit duplicatePart + if pCtrl is not among the lines of gParts then exit duplicatePart + local tKind, tLoc, tNx, tNy, tNew + put the uKind of pCtrl into tKind + put the loc of pCtrl into tLoc + put (item 1 of tLoc) + 26 into tNx + put (item 2 of tLoc) + 26 into tNy + if tNx > gArenaR then put (item 1 of tLoc) - 26 into tNx + if tNy > gArenaB then put (item 2 of tLoc) - 26 into tNy + put rebuildPart(tKind, tNx, tNy, (the width of pCtrl), (the height of pCtrl), \ + (the uColor of pCtrl), (the uFile of pCtrl)) into tNew + if tNew is empty then exit duplicatePart + if the uBounce of pCtrl is not empty then + set the uBounce of tNew to (the uBounce of pCtrl) + if (the uBounce of pCtrl) > 0 then b2kSetBounce tNew, (the uBounce of pCtrl) + end if + applyPartSpecial tNew, partSpecial(pCtrl) + renderBuild + put "drag" into gTool + selectPart tNew + highlightAll + put "Duplicated a " & friendlyKind(tKind) & ". Drag the copy where you want it." into gStatus + updateHud +end duplicatePart + on removeJointAt pIndex try b2kRemoveJoint gJHandle[pIndex] @@ -2576,7 +2628,8 @@ end buildSampleSwing -- ===================================================================== -- Save / load (whole contraption as tab-delimited text) -- ===================================================================== -on saveContraption +-- Serialise the whole scene to CB2 text (used by Save and by Reset's snapshot). +function serializeText local tOut, tP, tI put "CB2" & cr into tOut repeat for each line tP in gParts @@ -2590,6 +2643,12 @@ on saveContraption if gJKind[tI] is not empty then put jointLine(tI) & cr after tOut end repeat end if + return tOut +end serializeText + +on saveContraption + local tOut + put serializeText() into tOut ask file "Save contraption as:" with "contraption.cbx" if it is empty or the result is "cancel" then put "Save cancelled." into gStatus @@ -2684,9 +2743,7 @@ function jointLine pIndex end jointLine on loadContraption - local tFile, tData, tLine, tRec, tMap, tNew - local tId, tKind, tX, tY, tW, tH, tBounce, tColor, tFileRef, tSpecial - local tIdA, tIdB, tPx, tPy, tMotor, tA, tB + local tFile, tData answer file "Open a contraption:" with type "Contraption|cbx,txt" put it into tFile if tFile is empty or the result is "cancel" then @@ -2706,10 +2763,21 @@ on loadContraption exit loadContraption end if if gMode is "run" then toggleMode + rebuildFromText tData + put "Loaded contraption from " & tFile & ". Press Run to simulate." into gStatus + updateHud +end loadContraption + +-- Rebuild the whole scene from CB2/CB1 text, clearing the stage first. Shared by +-- Load (from a file) and Reset (from the pre-run snapshot). +on rebuildFromText pData + local tLine, tRec, tMap, tNew + local tId, tKind, tX, tY, tW, tH, tBounce, tColor, tFileRef, tSpecial + local tIdA, tIdB, tPx, tPy, tMotor, tA, tB clearAll put empty into tMap lock screen - repeat for each line tLine in tData + repeat for each line tLine in pData set the itemDelimiter to tab put item 1 of tLine into tRec if tRec is "part" then @@ -2754,9 +2822,22 @@ on loadContraption set the itemDelimiter to comma renderBuild unlock screen - put "Loaded contraption from " & tFile & ". Press Run to simulate." into gStatus +end rebuildFromText + +-- Restore the layout captured when Run last started, and return to Build mode. +on resetMachine + if gPreRun is empty then + put "Nothing to reset yet — press Run first; then Reset brings this build back." into gStatus + updateHud + exit resetMachine + end if + lock screen + if gMode is "run" then toggleMode + rebuildFromText gPreRun + unlock screen + put "Reset to your pre-run build." into gStatus updateHud -end loadContraption +end resetMachine function rebuildPart pKind, pX, pY, pW, pH, pColor, pFile if pW is empty or pW < 1 then put kBoxMin into pW @@ -3476,9 +3557,9 @@ on adjustPartProp pCtrl, pKey, pDir else resizePart pCtrl, 0.87 break case "color" - put colorPalette() into tPal + put partSwatches() into tPal put the number of lines of tPal into tCnt - put colorIndex(pCtrl, tPal) + pDir into tIdx + put swatchIndexOf(pCtrl, tPal) + pDir into tIdx if tIdx < 1 then put tCnt into tIdx if tIdx > tCnt then put 1 into tIdx set the backgroundColor of pCtrl to (line tIdx of tPal) @@ -3645,13 +3726,13 @@ on reapplyMaterial pCtrl end reapplyMaterial -- A generous palette the Colour setting cycles through. -function colorPalette +function partSwatches return "92,150,222" & cr & "84,206,142" & cr & "232,182,92" & cr & "214,92,92" & cr \ & "206,124,206" & cr & "120,200,232" & cr & "236,120,150" & cr & "150,160,180" & cr \ & "120,90,210" & cr & "240,140,60" & cr & "90,200,170" & cr & "232,232,236" -end colorPalette +end partSwatches -function colorIndex pCtrl, pPal +function swatchIndexOf pCtrl, pPal local tLine, tI put 0 into tI repeat for each line tLine in pPal @@ -3659,7 +3740,7 @@ function colorIndex pCtrl, pPal if tLine is (the uColor of pCtrl) then return tI end repeat return 0 -end colorIndex +end swatchIndexOf function frictionLabel pV if pV is empty then put 0.4 into pV @@ -3708,6 +3789,7 @@ function niceName pId local tNames put "drag=Move / Drag" & cr & "box=Box" & cr & "ball=Ball" & cr & "capsule=Capsule" & cr \ & "poly=Polygon" & cr & "image=Picture" & cr & "anchor=Anchor" & cr & "delete=Delete" & cr \ + & "duplicate=Duplicate" & cr \ & "balloon=Helium Balloon" & cr & "bomb=Bomb" & cr & "plate=Pressure Plate" & cr \ & "fan=Fan / Wind" & cr & "magnet=Magnet" & cr & "hinge=Hinge Joint" & cr \ & "weld=Weld Joint" & cr & "rope=Rope Joint" & cr & "slider=Slider Joint" & cr \ @@ -3729,7 +3811,7 @@ end niceName function toolGlyph pId local tGlyphs put "drag=✥" & cr & "box=■" & cr & "ball=●" & cr & "capsule=▬" & cr & "poly=◆" & cr \ - & "image=▦" & cr & "anchor=▼" & cr & "delete=✕" & cr & "balloon=◯" & cr & "bomb=◉" & cr \ + & "image=▦" & cr & "anchor=▼" & cr & "delete=✕" & cr & "duplicate=▣" & cr & "balloon=◯" & cr & "bomb=◉" & cr \ & "plate=▭" & cr & "fan=▷" & cr & "magnet=◐" & cr & "hinge=○" & cr & "weld=▰" & cr \ & "rope=∿" & cr & "slider=↔" & cr & "wheel=◎" into tGlyphs local tLine @@ -3763,6 +3845,8 @@ function toolHelp pId return "Place a fixed point that never moves." & cr & "An anchor is pinned to the world. Attach joints to it to hold things up — perfect as a motor mount or a pivot." case "delete" return "Remove a part you click on." & cr & "Delete tool. Click any part to remove it, along with any joints attached to it." + case "duplicate" + return "Copy a part you click on." & cr & "Duplicate tool. Click any part to drop an identical copy — same size, colour and settings — then drag it where you want." case "balloon" return "A helium balloon that floats upward." & cr & "A balloon rises on its own. Tie it to a part with a Rope joint to lift that part. A nearby bomb blast pops it." case "bomb" @@ -3791,6 +3875,8 @@ function actionHelp pId switch pId case "mode" return "Switch between Build (arrange) and Run (simulate)." + case "reset" + return "Put every part back where it was when you last pressed Run." case "clear" return "Remove everything and start with an empty stage." case "save"