diff --git a/CHANGELOG.md b/CHANGELOG.md index 19149db..1c18960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,24 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `2`). ### Added +- **Multiply tool.** Alongside Duplicate, a new **Multiply** tool asks how many + copies you want (1–50) and drops them in a tidy grid — each an independent + copy of the part you clicked (same size, colour, material and settings). Great + for crates, dominoes and brick walls. +- **Drop-in vehicles.** A new **VEHICLES** palette section adds a **Car** you + place like any other object: a chassis on two sprung, motorised wheels that + drives off when you press Run. Its three parts share a group tag, so dragging + any one of them moves the whole car as a unit (and the grouping survives + save/load/reset). The "Motor Cart" recipe now builds this same improved + vehicle. +- **Scrolling tool palette.** The left palette is now a single vertically + scrolling group, so the full tool list (SHAPES · TERRAIN · SPECIAL · VEHICLES + · JOINTS) fits a normal-height window — the panel scrolls instead of the + window growing. The stack is back to its original 760 px height. +- **Ramp facing.** Ramps now have a **Facing** setting (high on the left or + right) so you can build slopes in both horizontal directions; it mirrors the + outline and saves/loads with the piece. + - **Terrain objects in the Contraption Builder.** A new **TERRAIN** palette section adds three pieces of static scenery the dynamic parts rest on, roll down and bump into: **Platform** (a solid ledge), **Ramp** (a wedge for @@ -22,6 +40,12 @@ The native shim's ABI is tracked separately by `b2Version()` (currently `2`). - **"Terrain Run" example recipe** — a ball rolls down a ramp, over a hill and onto a platform, showing the new pieces in one click. +### Changed + +- **Slicker tool palette.** Section headers are brighter and sit over a hairline + rule; tool buttons are left-aligned, light up on hover, and keep their accent + when selected — a more polished, professional left sidebar. + ### Fixed - **Bridge & Chain spans ignore Bouncy mode.** Building a span while the diff --git a/examples/box2dxt-contraption-builder.livecodescript b/examples/box2dxt-contraption-builder.livecodescript index 8a53fce..868e2bf 100644 --- a/examples/box2dxt-contraption-builder.livecodescript +++ b/examples/box2dxt-contraption-builder.livecodescript @@ -1325,24 +1325,26 @@ local gInspTab -- active inspector tab: shape | physics local gGravityMode -- world gravity preset: normal|low|zero|reverse|sideways local gPreRun -- snapshot of the build, taken when Run starts -constant kShapeTools = "drag,box,ball,capsule,poly,image,anchor,delete,duplicate" -constant kShapeLabels = "Drag,Box,Ball,Capsule,Poly,Image,Anchor,Delete,Duplicate" +constant kShapeTools = "drag,box,ball,capsule,poly,image,anchor,delete,duplicate,multiply" +constant kShapeLabels = "Drag,Box,Ball,Capsule,Poly,Image,Anchor,Delete,Duplicate,Multiply" constant kSpecialTools = "balloon,bomb,plate,fan,magnet" constant kSpecialLabels = "Balloon,Bomb,Pressure Plate,Fan,Magnet" constant kTerrainTools = "platform,ramp,hill" constant kTerrainLabels = "Platform,Ramp,Hill" +constant kVehicleTools = "car" +constant kVehicleLabels = "Car" constant kJointTools = "hinge,weld,rope,slider,wheel,bridge,chain" constant kJointLabels = "Hinge,Weld,Rope,Slider,Wheel,Bridge,Chain" constant kHingeTorque = 1500, kWheelTorque = 320 -- Layout, in screen pixels. Derived edges (canvas L/R/T/B) are computed from the -- card size in prepArena/buildUI, because LiveCode constants cannot be expressions. -constant kStackW = 1180, kStackH = 868 +constant kStackW = 1180, kStackH = 760 constant kTopBarH = 54, kAccentH = 3 constant kPaletteW = 196, kInspectorW = 248, kStatusH = 26 constant kCanvasInset = 2, kGroundBarH = 22 constant kPalPadX = 12, kPalBtnH = 24, kPalBtnGap = 3 -constant kPalSectionGap = 8, kPalHeaderH = 18 +constant kPalSectionGap = 8, kPalHeaderH = 18, kPalScrollW = 16 -- Materials, motors, feedback timing, and default part sizes (were magic numbers). constant kBounceOn = 0.7, kFlashMs = 150, kSelectLine = 3 @@ -1513,7 +1515,9 @@ on makeTopBar end repeat end makeTopBar --- Left palette: SHAPES, SPECIAL and JOINTS, each under a section header. +-- Left palette: SHAPES, TERRAIN, SPECIAL, VEHICLES and JOINTS, each under a +-- section header. The sections are wrapped in a vertically scrolling group so the +-- whole tool list fits a normal-height window — the panel scrolls, not the stack. on makePalette local tWide, tHigh, tY put the width of this card into tWide @@ -1523,16 +1527,57 @@ on makePalette put makeToolSection("SHAPES", kShapeTools, kShapeLabels, "tool", tY) into tY put makeToolSection("TERRAIN", kTerrainTools, kTerrainLabels, "tool", tY) into tY put makeToolSection("SPECIAL", kSpecialTools, kSpecialLabels, "tool", tY) into tY + put makeToolSection("VEHICLES", kVehicleTools, kVehicleLabels, "tool", tY) into tY put makeToolSection("JOINTS", kJointTools, kJointLabels, "joint", tY) into tY + groupPalette end makePalette --- Build one labelled palette section; returns the Y for the next section. +-- Wrap the section headers + tool buttons in one scrolling group. The grouping +-- idiom: select the controls, then `group` (the new group is returned in `it`). +on groupPalette + local tT, tId, tErr + if there is a group "ui_palettescroll" then exit groupPalette + -- If grouping ever fails on a platform, degrade to an un-grouped (non-scrolling) + -- palette rather than aborting the whole UI build. + try + select empty + repeat for each item tT in "SHAPES,TERRAIN,SPECIAL,VEHICLES,JOINTS" + if there is a field ("ui_hdr_" & tT) then set the selected of field ("ui_hdr_" & tT) to true + if there is a graphic ("ui_hdrrule_" & tT) then set the selected of graphic ("ui_hdrrule_" & tT) to true + end repeat + repeat for each item tId in (kShapeTools & comma & kTerrainTools & comma & kSpecialTools & comma & kVehicleTools) + if there is a button ("ui_tool_" & tId) then set the selected of button ("ui_tool_" & tId) to true + end repeat + repeat for each item tId in kJointTools + if there is a button ("ui_joint_" & tId) then set the selected of button ("ui_joint_" & tId) to true + end repeat + group + set the name of it to "ui_palettescroll" + select empty + set the showName of group "ui_palettescroll" to false + set the showBorder of group "ui_palettescroll" to false + set the borderWidth of group "ui_palettescroll" to 0 + set the margins of group "ui_palettescroll" to 0 + set the opaque of group "ui_palettescroll" to false + set the hScrollbar of group "ui_palettescroll" to false + set the vScrollbar of group "ui_palettescroll" to true + set the unboundedVScroll of group "ui_palettescroll" to false + set the rect of group "ui_palettescroll" to 0, kTopBarH + kAccentH, kPaletteW, (the height of this card) - kStatusH + set the vScroll of group "ui_palettescroll" to 0 + catch tErr + select empty + end try +end groupPalette + +-- Build one labelled palette section: a bright header over a hairline rule, then +-- the tool buttons. Returns the Y for the next section. function makeToolSection pTitle, pIds, pLabels, pKind, pStartY local tY, tI, tId, tLbl, tName, tW put pStartY into tY - makeLabel ("ui_hdr_" & pTitle), kPalPadX, tY, kPaletteW - kPalPadX, tY + kPalHeaderH, pTitle, 10, "120,126,140", true - add kPalHeaderH + 4 to tY - put kPaletteW - 2 * kPalPadX into tW + makeLabel ("ui_hdr_" & pTitle), kPalPadX, tY, kPaletteW - kPalPadX, tY + kPalHeaderH, pTitle, 10, "150,156,172", true + makeBar ("ui_hdrrule_" & pTitle), kPalPadX, tY + kPalHeaderH, kPaletteW - kPalPadX - kPalScrollW, tY + kPalHeaderH + 1, "52,57,70" + add kPalHeaderH + 6 to tY + put kPaletteW - 2 * kPalPadX - kPalScrollW into tW -- leave room for the scrollbar repeat with tI = 1 to the number of items of pIds put item tI of pIds into tId put item tI of pLabels into tLbl @@ -1545,6 +1590,9 @@ function makeToolSection pTitle, pIds, pLabels, pKind, pStartY makeButton tName, (toolGlyph(tId) && tLbl), kPalPadX, tY, tW, kPalBtnH set the uToolId of button tName to tId end if + set the textAlign of button tName to "left" -- menu-style: labels start at the left + set the margins of button tName to 10 + set the tooltip of button tName to line 1 of toolHelp(tId) -- set now; survives grouping add kPalBtnH + kPalBtnGap to tY end repeat return tY + kPalSectionGap @@ -1632,11 +1680,18 @@ on makeAction pId, pLabel, pX, pY, pW, pH set the textColor of button ("ui_act_" & pId) to "196,200,212" end makeAction +-- Delete the chrome. Collect first, then delete, and only touch *top-level* +-- ui_ controls — deleting the palette group cascades to its grouped buttons. on clearUI - local tI, tName - repeat with tI = the number of controls of this card down to 1 - put the short name of control tI of this card into tName - if char 1 to 3 of tName is "ui_" then delete control tI of this card + local tI, tCtrl, tKill + put empty into tKill + repeat with tI = 1 to the number of controls of this card + put the long id of control tI of this card into tCtrl + if char 1 to 3 of the short name of tCtrl is "ui_" and word 1 of the owner of tCtrl is "card" \ + then put tCtrl & cr after tKill + end repeat + repeat for each line tCtrl in tKill + if there is a tCtrl then delete tCtrl end repeat end clearUI @@ -1648,16 +1703,19 @@ on clearSceneControls end repeat end clearSceneControls --- Keep all chrome above the parts and joint markers. +-- Keep all chrome above the parts and joint markers. Only raise *top-level* ui_ +-- controls (the palette group rises as a unit; raising its members individually +-- would pull them out of the group). on raiseUI - local tI, tName, tList + local tI, tCtrl, tList put empty into tList repeat with tI = 1 to the number of controls of this card - put the short name of control tI of this card into tName - if char 1 to 3 of tName is "ui_" then put tName & cr after tList + put the long id of control tI of this card into tCtrl + if char 1 to 3 of the short name of tCtrl is "ui_" and word 1 of the owner of tCtrl is "card" \ + then put tCtrl & cr after tList end repeat - repeat for each line tName in tList - set the layer of control tName of this card to top + repeat for each line tCtrl in tList + if there is a tCtrl then set the layer of tCtrl to top end repeat end raiseUI @@ -1709,7 +1767,7 @@ on makeButton pName, pLabel, pX, pY, pW, pH end makeButton on highlightAll - highlightGroup "ui_tool_", (kShapeTools & "," & kTerrainTools & "," & kSpecialTools), gTool, (gJointTool is empty), "72,190,130" + highlightGroup "ui_tool_", (kShapeTools & "," & kTerrainTools & "," & kSpecialTools & "," & kVehicleTools), gTool, (gJointTool is empty), "72,190,130" highlightGroup "ui_joint_", kJointTools, gJointTool, true, "232,150,72" highlightActions end highlightAll @@ -1816,6 +1874,10 @@ on mouseDown if tHit is not empty then duplicatePart tHit exit mouseDown end if + if gTool is "multiply" then + if tHit is not empty then multiplyPart tHit + exit mouseDown + end if if tHit is not empty then put tHit into gDragCtrl put "build" into gDragMode @@ -1836,10 +1898,23 @@ end mouseDown on mouseMove pX, pY if gDragMode is "build" and gDragCtrl is not empty then - local tCx, tCy + local tCx, tCy, tOld, tDx, tDy, tGroup, tP put max(gArenaL, min(gArenaR, pX)) into tCx put max(gArenaT, min(gArenaB, pY)) into tCy + put the loc of gDragCtrl into tOld + put round(tCx) - item 1 of tOld into tDx -- how far the grabbed part moved + put round(tCy) - item 2 of tOld into tDy set the loc of gDragCtrl to round(tCx) & "," & round(tCy) + -- a grouped assembly (e.g. a car) moves as one: shift its other parts too + put the uGroup of gDragCtrl into tGroup + if tGroup is not empty then + repeat for each line tP in gParts + if tP is empty or tP is gDragCtrl then next repeat + if the uGroup of tP is tGroup then + set the loc of tP to (item 1 of the loc of tP + tDx) & "," & (item 2 of the loc of tP + tDy) + end if + end repeat + end if put true into gDragMoved renderJoints end if @@ -1851,11 +1926,13 @@ on mouseUp exit mouseUp end if if gDragMode is "build" and gDragCtrl is not empty and gDragMoved then - if kindHasBody(the uKind of gDragCtrl) then - local tLoc - put the loc of gDragCtrl into tLoc - -- re-seat the body where the graphic was dropped (angle preserved) - b2kMoveTo gDragCtrl, item 1 of tLoc, item 2 of tLoc, b2kAngle(gDragCtrl) + local tGroup, tP + reseatDragged gDragCtrl + put the uGroup of gDragCtrl into tGroup + if tGroup is not empty then + repeat for each line tP in gParts + if tP is not empty and tP is not gDragCtrl and the uGroup of tP is tGroup then reseatDragged tP + end repeat end if renderBuild end if @@ -1864,6 +1941,14 @@ on mouseUp put empty into gDragMode end mouseUp +-- Re-seat a dragged part's body where its graphic was dropped (angle preserved). +on reseatDragged pCtrl + if pCtrl is empty or not kindHasBody(the uKind of pCtrl) then exit reseatDragged + local tLoc + put the loc of pCtrl into tLoc + b2kMoveTo pCtrl, item 1 of tLoc, item 2 of tLoc, b2kAngle(pCtrl) +end reseatDragged + on mouseRelease if gDragMode is "run" then b2kRelease put empty into gDragCtrl @@ -2099,6 +2184,9 @@ function placePart pX, pY case "hill" put placeTerrain(gTool, pX, pY) into tCtrl break + case "car" + put placeVehicle(pX, pY) into tCtrl + break case "image" put importImage(pX, pY) into tCtrl break @@ -2228,16 +2316,18 @@ function terrainColor pKind end terrainColor -- Screen-space points for a terrain outline: the canonical shape scaled to --- pW x pH with its bounding box centred on (pCx,pCy), then rotated pAngleDeg --- about that centre. Convex and <= 8 verts, so b2kAddPolygon accepts it. -function terrainPoints pKind, pCx, pCy, pW, pH, pAngleDeg +-- pW x pH with its bounding box centred on (pCx,pCy), optionally mirrored +-- left↔right (pFlip, for ramps facing the other way), then rotated pAngleDeg +-- about the centre. Convex and <= 8 verts, so b2kAddPolygon accepts it. +function terrainPoints pKind, pCx, pCy, pW, pH, pAngleDeg, pFlip local tHw, tHh, tBase, tPt, tx, ty, tC, tS, tA, tRot set the itemDelimiter to comma -- we parse "x,y" points below put pW / 2 into tHw put pH / 2 into tHh + if pFlip is true then put - tHw into tHw -- mirror about the centre (ramps) switch pKind case "ramp" - -- right triangle: flat base, upright right edge, slope rising rightward + -- right triangle: flat base, upright high edge, slope rising to one side put (pCx - tHw) & "," & (pCy + tHh) & cr & (pCx + tHw) & "," & (pCy + tHh) \ & cr & (pCx + tHw) & "," & (pCy - tHh) into tBase break @@ -2283,14 +2373,15 @@ function placeTerrain pKind, pX, pY, pW, pH set the filled of tRef to true set the backgroundColor of tRef to tColor set the foregroundColor of tRef to "20,20,24" - set the lineSize of tRef to 1 - set the points of tRef to terrainPoints(pKind, round(pX), round(pY), pW, pH, 0) + set the lineSize of tRef to 2 -- a bolder edge reads as solid ground + set the points of tRef to terrainPoints(pKind, round(pX), round(pY), pW, pH, 0, false) set the loc of tRef to round(pX) & "," & round(pY) b2kAddPolygon tRef, false -- static: never falls, never syncs tagPart tRef, pKind, tColor, empty set the uW of tRef to pW set the uH of tRef to pH set the uAngle of tRef to 0 + set the uFlip of tRef to false set the uFriction of tRef to 0.6 -- terrain grips by default b2kSetFriction tRef, 0.6 return tRef @@ -2304,12 +2395,52 @@ on regenTerrain pCtrl put item 1 of the loc of pCtrl into tCx put item 2 of the loc of pCtrl into tCy set the points of pCtrl to terrainPoints(the uKind of pCtrl, tCx, tCy, \ - numOr(the uW of pCtrl, 120), numOr(the uH of pCtrl, 26), numOr(the uAngle of pCtrl, 0)) + numOr(the uW of pCtrl, 120), numOr(the uH of pCtrl, 26), numOr(the uAngle of pCtrl, 0), \ + (the uFlip of pCtrl is true)) set the loc of pCtrl to tCx & "," & tCy -- keep centred on the body origin b2kReshape pCtrl, "poly" reapplyMaterial pCtrl end regenTerrain +-- ===================================================================== +-- Vehicle: a ready-made car you drop in like any object — a chassis on two +-- sprung, motorised wheels. All three parts share a uGroup tag so dragging one +-- moves the whole car, and the wheel joints/motors save, load and reset normally. +-- ===================================================================== +function placeVehicle pX, pY + local tGid, tChassis, tWB, tWF, tCW, tCH, tWheel, tDx, tDy, tSpeed, tW + put "veh" & the milliseconds & "_" & random(99999) into tGid + put 152 into tCW -- chassis width + put 34 into tCH -- chassis height + put 48 into tWheel -- wheel diameter + put round(tCW * 0.31) into tDx -- wheel offset from centre (x) + put round(tCH * 0.9) into tDy -- wheel sits just below the chassis + put max(160, gMotorSpeed) into tSpeed -- a lively default drive speed + -- chassis: a light body keeps the centre of mass low for a stable ride + put placeBox(pX, pY, tCW, tCH, "212,84,72") into tChassis + set the uDensity of tChassis to 0.7 + b2kSetDensity tChassis, 0.7 + set the uBounce of tChassis to 0 + b2kSetBounce tChassis, 0 + set the uGroup of tChassis to tGid + -- two grippy wheels + put placeBall(pX - tDx, pY + tDy, tWheel, "40,42,50") into tWB + put placeBall(pX + tDx, pY + tDy, tWheel, "40,42,50") into tWF + repeat for each line tW in (tWB & cr & tWF) + set the uFriction of tW to 1 + b2kSetFriction tW, 1 + set the uDensity of tW to 1 + b2kSetDensity tW, 1 + set the uBounce of tW to 0 + b2kSetBounce tW, 0 + set the uGroup of tW to tGid + end repeat + -- sprung, motorised wheel joints (connectJoint adds the suspension + motor) + get connectJoint("wheel", tChassis, tWB, item 1 of the loc of tWB, item 2 of the loc of tWB, tSpeed) + get connectJoint("wheel", tChassis, tWF, item 1 of the loc of tWF, item 2 of the loc of tWF, tSpeed) + return tChassis +end placeVehicle + -- Tag a part with the data save/load needs, register it, apply the material. on tagPart pCtrl, pKind, pColor, pFile if pCtrl is empty then exit tagPart @@ -2845,33 +2976,91 @@ 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.) +-- Make one independent copy of a part at (pX,pY): same size, colour, material and +-- special settings. The copy carries no group tag, so copying a vehicle part +-- yields a free part rather than something tied to the original's assembly. +function copyPart pCtrl, pX, pY + local tNew + put rebuildPart(the uKind of pCtrl, pX, pY, numOr(the uW of pCtrl, the width of pCtrl), \ + numOr(the uH of pCtrl, the height of pCtrl), \ + (the uColor of pCtrl), (the uFile of pCtrl)) into tNew + if tNew is empty then return empty + 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) + set the uGroup of tNew to empty + return tNew +end copyPart + 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 + local tLoc, tNx, tNy, tNew 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, numOr(the uW of pCtrl, the width of pCtrl), \ - numOr(the uH of pCtrl, the height of pCtrl), \ - (the uColor of pCtrl), (the uFile of pCtrl)) into tNew + put copyPart(pCtrl, tNx, tNy) 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 + put "Duplicated a " & friendlyKind(the uKind of pCtrl) & ". Drag the copy where you want it." into gStatus updateHud end duplicatePart +-- Multiply: drop a whole batch of copies in a tidy grid, asking how many first. +on multiplyPart pCtrl + if pCtrl is empty then exit multiplyPart + if pCtrl is not among the lines of gParts then exit multiplyPart + local tN, tLoc, tOx, tOy, tStep, tCols, tI, tX, tY, tNew, tLast, tMade + ask "How many copies would you like?" with "5" + if it is empty or the result is "Cancel" then + put "Multiply cancelled." into gStatus + updateHud + exit multiplyPart + end if + if it is not a number then + put "Please type a whole number (1–50)." into gStatus + updateHud + exit multiplyPart + end if + put min(50, max(1, round(it))) into tN + put the loc of pCtrl into tLoc + put item 1 of tLoc into tOx + put item 2 of tLoc into tOy + put max(28, max(numOr(the uW of pCtrl, the width of pCtrl), \ + numOr(the uH of pCtrl, the height of pCtrl)) + 14) into tStep -- spacing so copies don't pile up + put max(1, trunc((gArenaR - tOx - tStep) / tStep)) into tCols -- columns that fit to the right + put 0 into tMade + put empty into tLast + lock screen + repeat with tI = 1 to tN + put tOx + ((tI - 1) mod tCols + 1) * tStep into tX + put tOy + ((tI - 1) div tCols) * tStep into tY + put max(gArenaL + tStep / 2, min(gArenaR - tStep / 2, tX)) into tX + put max(gArenaT + tStep / 2, min(gArenaB - tStep / 2, tY)) into tY + put copyPart(pCtrl, round(tX), round(tY)) into tNew + if tNew is not empty then + add 1 to tMade + put tNew into tLast + end if + end repeat + renderBuild + unlock screen + if tLast is not empty then + put "drag" into gTool + selectPart tLast + highlightAll + end if + put "Made " & tMade & " copies of the " & friendlyKind(the uKind of pCtrl) & "." into gStatus + updateHud +end multiplyPart + on removeJointAt pIndex if gSelJoint is pIndex then deselectJoint try @@ -2953,15 +3142,7 @@ end liveJointCount on buildSampleCart if gMode is "run" then toggleMode clearAll - local tCx, tCy, tChassis, tWB, tWF - put (gArenaL + gArenaR) div 2 into tCx - put gArenaT + 110 into tCy - put placeBox(tCx, tCy, 132, 36, "228,132,60") into tChassis - put placeBall(tCx - 42, tCy + 30, 48, "40,40,48") into tWB - put placeBall(tCx + 42, tCy + 30, 48, "40,40,48") into tWF - get connectJoint("wheel", tChassis, tWB, item 1 of the loc of tWB, item 2 of the loc of tWB, gMotorSpeed) - get connectJoint("wheel", tChassis, tWF, item 1 of the loc of tWF, item 2 of the loc of tWF, gMotorSpeed) - put true into gMotorOn + get placeVehicle((gArenaL + gArenaR) div 2, gArenaT + 110) highlightAll renderBuild put "Sample cart built. Press Run — the motorised wheels drive it." into gStatus @@ -3041,9 +3222,10 @@ function partSpecial pCtrl local tS, tKind put the uKind of pCtrl into tKind put empty into tS - if kindIsTerrain(tKind) then -- static scenery: grip + baked-in angle + if kindIsTerrain(tKind) then -- static scenery: grip + baked-in angle (+ ramp facing) put addKV(tS, "fric=" & (the uFriction of pCtrl)) into tS put addKV(tS, "tang=" & numOr(the uAngle of pCtrl, 0)) into tS + if tKind is "ramp" then put addKV(tS, "flip=" & (the uFlip of pCtrl is true)) into tS return tS end if if kindHasBody(tKind) then @@ -3059,6 +3241,7 @@ function partSpecial pCtrl put addKV(tS, "lspd=" & (the uLaunchSpeed of pCtrl)) into tS put addKV(tS, "ldir=" & (the uLaunchDir of pCtrl)) into tS put addKV(tS, "spin=" & (the uSpinRate of pCtrl)) into tS + if the uGroup of pCtrl is not empty then put addKV(tS, "grp=" & (the uGroup of pCtrl)) into tS end if switch tKind case "fan" @@ -3454,12 +3637,13 @@ on applyPartSpecial pCtrl, pSpecial if pCtrl is empty then exit applyPartSpecial local tKind, tV put the uKind of pCtrl into tKind - if kindIsTerrain(tKind) then -- restore grip + angle, then redraw the outline + if kindIsTerrain(tKind) then -- restore grip + angle + facing, then redraw put specialValue(pSpecial, "fric") into tV if tV is not empty then set the uFriction of pCtrl to tV put specialValue(pSpecial, "tang") into tV if tV is empty then put 0 into tV set the uAngle of pCtrl to tV + set the uFlip of pCtrl to (specialValue(pSpecial, "flip") is "true") regenTerrain pCtrl exit applyPartSpecial end if @@ -3507,6 +3691,8 @@ on applyPartSpecial pCtrl, pSpecial if tV is not empty then set the uLaunchDir of pCtrl to tV put specialValue(pSpecial, "spin") into tV if tV is not empty then set the uSpinRate of pCtrl to tV + put specialValue(pSpecial, "grp") into tV + if tV is not empty then set the uGroup of pCtrl to tV put specialValue(pSpecial, "ang") into tV if tV is not empty and tV is not 0 then b2kMoveTo pCtrl, item 1 of the loc of pCtrl, item 2 of the loc of pCtrl, tV @@ -3895,8 +4081,9 @@ function partProps pKind return "width,height,color,angle,friction" case "anchor" return "width,height,color" - case "platform" case "ramp" + return "width,height,angle,facing,friction,color" + case "platform" case "hill" return "width,height,angle,friction,color" case "fan" @@ -3925,6 +4112,9 @@ function propLabel pCtrl, pKey case "angle" if kindIsTerrain(the uKind of pCtrl) then return "Angle: " & numOr(the uAngle of pCtrl, 0) & " deg" return "Angle: " & round(b2kAngle(pCtrl)) & " deg" + case "facing" + if the uFlip of pCtrl is true then return "Facing: high on the left" + return "Facing: high on the right" case "color" return "Colour (cycle with +/−)" case "bounce" @@ -4061,6 +4251,10 @@ on adjustPartProp pCtrl, pKey, pDir setPartAngle pCtrl, round(b2kAngle(pCtrl)) + pDir * 15 end if break + case "facing" + set the uFlip of pCtrl to not (the uFlip of pCtrl is true) + regenTerrain pCtrl + break case "color" put partSwatches() into tPal put the number of lines of tPal into tCnt @@ -4294,7 +4488,7 @@ end swatchIndexOf -- Which settings tab a property key belongs to. function propGroup pKey - if pKey is among the items of "width,height,length,thickness,diameter,size,angle,color" then return "shape" + if pKey is among the items of "width,height,length,thickness,diameter,size,angle,facing,color" then return "shape" if pKey is among the items of "bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material" then return "physics" if pKey is among the items of "launchspeed,launchdir,spinrate" then return "motion" return "special" @@ -5275,16 +5469,30 @@ end sizeLabel on mouseEnter local tName put the short name of the target into tName - if "ui_tool_" is in tName then hoverHelp the uToolId of the target - else if "ui_joint_" is in tName then hoverHelp the uJointId of the target + if "ui_tool_" is in tName then + hoverTool the long id of the target + hoverHelp the uToolId of the target + else if "ui_joint_" is in tName then + hoverTool the long id of the target + hoverHelp the uJointId of the target + end if end mouseEnter on mouseLeave local tName put the short name of the target into tName - if "ui_tool_" is in tName or "ui_joint_" is in tName then restoreInspector + if "ui_tool_" is in tName or "ui_joint_" is in tName then + highlightAll -- restore the correct idle / selected colour + restoreInspector + end if end mouseLeave +-- Lighten an *idle* palette button on hover (selected buttons keep their accent; +-- mouseLeave repaints everything via highlightAll). +on hoverTool pBtn + if the backgroundColor of pBtn is "44,48,58" then set the backgroundColor of pBtn to "58,64,78" +end hoverTool + -- ===================================================================== -- Plain-language labels, tooltips and help text -- ===================================================================== @@ -5292,10 +5500,10 @@ 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 \ + & "duplicate=Duplicate" & cr & "multiply=Multiply" & cr \ & "balloon=Helium Balloon" & cr & "bomb=Bomb" & cr & "plate=Pressure Plate" & cr \ & "fan=Fan / Wind" & cr & "magnet=Magnet" & cr \ - & "platform=Platform" & cr & "ramp=Ramp" & cr & "hill=Hill" & cr & "hinge=Hinge Joint" & cr \ + & "platform=Platform" & cr & "ramp=Ramp" & cr & "hill=Hill" & cr & "car=Car" & cr & "hinge=Hinge Joint" & cr \ & "weld=Weld Joint" & cr & "rope=Rope Joint" & cr & "slider=Slider Joint" & cr \ & "wheel=Wheel Joint" & cr & "bridge=Bridge" & cr & "chain=Chain (rope)" into tNames local tLine @@ -5315,9 +5523,9 @@ end niceName function toolGlyph pId local tGlyphs put "drag=✥" & cr & "box=■" & cr & "ball=●" & cr & "capsule=▬" & cr & "poly=◆" & cr \ - & "image=▦" & cr & "anchor=▼" & cr & "delete=✕" & cr & "duplicate=▣" & cr & "balloon=◯" & cr & "bomb=◉" & cr \ + & "image=▦" & cr & "anchor=▼" & cr & "delete=✕" & cr & "duplicate=▣" & cr & "multiply=⧉" & cr & "balloon=◯" & cr & "bomb=◉" & cr \ & "plate=▭" & cr & "fan=▷" & cr & "magnet=◐" & cr \ - & "platform=━" & cr & "ramp=◣" & cr & "hill=∩" & cr & "hinge=○" & cr & "weld=▰" & cr \ + & "platform=━" & cr & "ramp=◣" & cr & "hill=∩" & cr & "car=⊙" & cr & "hinge=○" & cr & "weld=▰" & cr \ & "rope=∿" & cr & "slider=↔" & cr & "wheel=◎" & cr & "bridge=◠" & cr & "chain=∾" into tGlyphs local tLine set the itemDelimiter to "=" @@ -5352,6 +5560,8 @@ function toolHelp pId 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 "multiply" + return "Make many copies of a part at once." & cr & "Multiply tool. Click any part and choose how many copies to make; they drop into a tidy grid, each identical to the original. Great for crates, dominoes and bricks." 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" @@ -5368,6 +5578,8 @@ function toolHelp pId return "Place a fixed slope for rolling and sliding." & cr & "A ramp is a fixed wedge. Balls roll down it and parts slide along it. Rotate it for any incline and raise its grip to slow the slide — great for launches and runs." case "hill" return "Place a fixed rounded mound." & cr & "A hill is a fixed, rounded mound that parts roll over and settle against. Size and rotate it to shape your landscape." + case "car" + return "Drop a ready-made motor car." & cr & "A complete car: a chassis on two sprung, motorised wheels. Drop it in, press Run and it drives off. Drag the body and the whole car moves with it; tune or reverse each wheel's motor in the joint inspector." case "hinge" return "Pin two parts so they rotate." & cr & "A hinge is a pin two parts spin around — like an axle or a pendulum. Pin to an Anchor or empty space, switch Motor on, and it drives itself." case "weld"