@@ -156,6 +156,24 @@ function getSelectedObjectKeysInObjectOrder() {
156156 return keys . filter ( ( key ) => selectedKeySet . has ( key ) ) ;
157157}
158158
159+ function getSelectedShapeTypeValue ( ) {
160+ return refs . newShapeType instanceof HTMLSelectElement
161+ ? normalizeText ( refs . newShapeType . value ) . toLowerCase ( )
162+ : "" ;
163+ }
164+
165+ function updateAddShapeButtonState ( ) {
166+ if ( ! ( refs . addShapeButton instanceof HTMLButtonElement ) ) {
167+ return ;
168+ }
169+ const selectedType = getSelectedShapeTypeValue ( ) ;
170+ const disabled = selectedType === "flattened" ;
171+ refs . addShapeButton . disabled = disabled ;
172+ refs . addShapeButton . title = disabled
173+ ? "Flattened objects are created only with the Flatten button."
174+ : "" ;
175+ }
176+
159177function updateFlattenButtonState ( ) {
160178 const validSelection = getValidSelectedObjectKeys ( ) ;
161179 state . selectedObjectKeys = validSelection ;
@@ -421,6 +439,12 @@ function createShapePreset(shapeType) {
421439 color : "#f8f8f2"
422440 } ;
423441 }
442+ if ( type === "flattened" ) {
443+ return {
444+ shape : "flattened" ,
445+ components : [ ]
446+ } ;
447+ }
424448 if ( type === "polygon" ) {
425449 return {
426450 shape : "polygon" ,
@@ -438,7 +462,7 @@ function createShapePreset(shapeType) {
438462 innerRadius : 26
439463 } ;
440464 }
441- if ( type === "wall-3-sides " ) {
465+ if ( type === "wall-multi-side " ) {
442466 return {
443467 shape : "wall" ,
444468 color : "#f8f8f2" ,
@@ -453,7 +477,12 @@ function createShapePreset(shapeType) {
453477 return { color : "#f8f8f2" , radius : 42 } ;
454478 }
455479 if ( type === "oval" ) {
456- return { color : "#f8f8f2" , width : 140 , height : 90 } ;
480+ return {
481+ shape : "oval" ,
482+ color : "#f8f8f2" ,
483+ width : 140 ,
484+ height : 90
485+ } ;
457486 }
458487 if ( type === "square" ) {
459488 return { color : "#f8f8f2" , width : 96 , height : 96 } ;
@@ -565,12 +594,14 @@ function inferShapeTypeFromSelectedObject() {
565594}
566595
567596function inferShapeTypeFromObjectKey ( objectKey ) {
597+ const game = getSelectedGameOption ( ) ;
598+ const normalizedGameId = normalizeText ( game ?. id ) . toLowerCase ( ) ;
568599 const selectedObject = toObject ( state . activeSkin ?. objects ?. [ objectKey ] ) ;
569600 const shape = normalizeText ( selectedObject . shape ) . toLowerCase ( ) ;
570601 if ( shape === "wall" ) {
571- return "wall-3-sides " ;
602+ return "wall-multi-side " ;
572603 }
573- if ( shape && [ "circle" , "oval" , "rectangle" , "square" , "polygon" , "star" , "ring" , "hud-color" ] . includes ( shape ) ) {
604+ if ( shape && [ "circle" , "oval" , "rectangle" , "square" , "polygon" , "star" , "ring" , "flattened" , " hud-color"] . includes ( shape ) ) {
574605 return shape ;
575606 }
576607 const hasSides = Number . isFinite ( Number ( selectedObject . sides ) ) ;
@@ -580,13 +611,14 @@ function inferShapeTypeFromObjectKey(objectKey) {
580611 const hasWidth = Number . isFinite ( Number ( selectedObject . width ) ) ;
581612 const hasHeight = Number . isFinite ( Number ( selectedObject . height ) ) ;
582613 const hasThickness = Number . isFinite ( Number ( selectedObject . thickness ) ) ;
614+ const hasComponents = Array . isArray ( selectedObject . components ) ;
583615 const hasWallFlags = [ "left" , "right" , "top" , "bottom" ] . some ( ( key ) => typeof selectedObject [ key ] === "boolean" ) ;
584616 const colorPropertyCount = Object . values ( selectedObject )
585617 . filter ( ( value ) => typeof value === "string" && parseHexForPicker ( value ) )
586618 . length ;
587619
588620 if ( hasThickness && hasWallFlags ) {
589- return "wall-3-sides " ;
621+ return "wall-multi-side " ;
590622 }
591623 if ( hasSides && hasOuter && hasInner ) {
592624 return "star" ;
@@ -597,6 +629,12 @@ function inferShapeTypeFromObjectKey(objectKey) {
597629 if ( hasOuter && hasInner ) {
598630 return "ring" ;
599631 }
632+ if ( normalizedGameId === "breakout" && normalizeText ( objectKey ) . toLowerCase ( ) === "wall" && hasThickness ) {
633+ return "flattened" ;
634+ }
635+ if ( hasComponents ) {
636+ return "flattened" ;
637+ }
600638 if ( hasRadius && hasWidth && hasHeight ) {
601639 return "oval" ;
602640 }
@@ -626,6 +664,7 @@ function syncShapeTypeControlFromSelection() {
626664 if ( optionExists ) {
627665 refs . newShapeType . value = inferredShapeType ;
628666 }
667+ updateAddShapeButtonState ( ) ;
629668}
630669
631670function syncSelectedObjectUiFromSelection ( ) {
@@ -677,6 +716,23 @@ function setObjectPropertyValue(objectKey, propertyKey, value) {
677716 const nextValue = typeof value === "number"
678717 ? clampNumericProperty ( propertyKey , value )
679718 : value ;
719+ const normalizedPropertyKey = normalizeText ( propertyKey ) . toLowerCase ( ) ;
720+ const isSquareObject = inferShapeTypeFromObjectKey ( objectKey ) === "square" ;
721+ if ( isSquareObject
722+ && typeof nextValue === "number"
723+ && Number . isFinite ( nextValue )
724+ && [ "size" , "width" , "height" ] . includes ( normalizedPropertyKey ) ) {
725+ const squareSize = clampNumericProperty ( "size" , nextValue ) ;
726+ state . activeSkin . objects [ objectKey ] . size = squareSize ;
727+ state . activeSkin . objects [ objectKey ] . width = squareSize ;
728+ state . activeSkin . objects [ objectKey ] . height = squareSize ;
729+ updateEditorFromState ( "visual-editor" ) ;
730+ syncSelectedObjectUiFromSelection ( ) ;
731+ renderPaletteList ( ) ;
732+ renderObjectControls ( ) ;
733+ drawSelectedObjectPreview ( ) ;
734+ return ;
735+ }
680736 state . activeSkin . objects [ objectKey ] [ propertyKey ] = nextValue ;
681737 updateEditorFromState ( "visual-editor" ) ;
682738 syncSelectedObjectUiFromSelection ( ) ;
@@ -735,10 +791,10 @@ function renderPaletteList() {
735791 }
736792 const swatchName = normalizeText ( entry ?. name ) || `Swatch ${ index + 1 } ` ;
737793 const swatchSymbol = normalizeText ( entry ?. symbol ) ;
738- const suffix = swatchSymbol ? ` [ ${ swatchSymbol } ] ` : "" ;
794+ const prefix = swatchSymbol ? `${ swatchSymbol } ` : "" ;
739795 return {
740796 id : `${ sharedPalette ?. paletteId || "shared-palette" } .${ index } ` ,
741- label : `${ swatchName } ${ suffix } ` ,
797+ label : `${ prefix } ${ swatchName } ` ,
742798 color
743799 } ;
744800 } )
@@ -858,6 +914,7 @@ function renderObjectControls() {
858914
859915 const selectedKey = state . selectedObjectKey ;
860916 const selectedObject = toObject ( state . activeSkin ?. objects ?. [ selectedKey ] ) ;
917+ const inferredShapeType = inferShapeTypeFromObjectKey ( selectedKey ) ;
861918 const entries = Object . entries ( selectedObject ) ;
862919 if ( ! selectedKey || entries . length === 0 ) {
863920 const note = document . createElement ( "p" ) ;
@@ -867,10 +924,39 @@ function renderObjectControls() {
867924 return ;
868925 }
869926
927+ if ( inferredShapeType === "square" ) {
928+ const sizeCandidates = [ Number ( selectedObject . size ) , Number ( selectedObject . width ) , Number ( selectedObject . height ) ]
929+ . filter ( ( entry ) => Number . isFinite ( entry ) ) ;
930+ const sizeValue = clampNumericProperty ( "size" , sizeCandidates . find ( ( entry ) => entry > 0 ) ?? sizeCandidates [ 0 ] ?? 96 ) ;
931+ const sizeRow = document . createElement ( "div" ) ;
932+ sizeRow . className = "skin-editor-row skin-editor-row--number" ;
933+ const sizeLabel = document . createElement ( "span" ) ;
934+ sizeLabel . className = "skin-editor-row-label" ;
935+ sizeLabel . textContent = "Size" ;
936+ const sizeInput = createBasicField ( "skin-editor-field" , String ( sizeValue ) ) ;
937+ sizeInput . type = "number" ;
938+ sizeInput . step = numberStep ( sizeValue ) ;
939+ sizeInput . min = "1" ;
940+ sizeInput . addEventListener ( "input" , ( ) => {
941+ const parsed = clampNumericProperty ( "size" , Number ( sizeInput . value ) ) ;
942+ if ( ! Number . isFinite ( parsed ) ) {
943+ return ;
944+ }
945+ sizeInput . value = String ( parsed ) ;
946+ setObjectPropertyValue ( selectedKey , "size" , parsed ) ;
947+ } ) ;
948+ sizeRow . appendChild ( sizeLabel ) ;
949+ sizeRow . appendChild ( sizeInput ) ;
950+ refs . objectControls . appendChild ( sizeRow ) ;
951+ }
952+
870953 entries . forEach ( ( [ propertyKey , propertyValue ] ) => {
871954 if ( propertyKey === "shape" ) {
872955 return ;
873956 }
957+ if ( inferredShapeType === "square" && [ "width" , "height" , "size" ] . includes ( normalizeText ( propertyKey ) . toLowerCase ( ) ) ) {
958+ return ;
959+ }
874960 if ( typeof propertyValue === "number" && Number . isFinite ( propertyValue ) ) {
875961 const minValue = / s i d e s ? / i. test ( normalizeText ( propertyKey ) )
876962 ? 3
@@ -1029,6 +1115,20 @@ function drawSelectedObjectPreview() {
10291115 context . fill ( ) ;
10301116 return ;
10311117 }
1118+ if ( shapeType === "oval" ) {
1119+ context . beginPath ( ) ;
1120+ context . ellipse (
1121+ centerX ,
1122+ centerY ,
1123+ Math . max ( 6 , width / 2 ) ,
1124+ Math . max ( 6 , height / 2 ) ,
1125+ 0 ,
1126+ 0 ,
1127+ Math . PI * 2
1128+ ) ;
1129+ context . fill ( ) ;
1130+ return ;
1131+ }
10321132 if ( shapeType === "ring" || ( Number . isFinite ( Number ( object . outerRadius ) ) && Number . isFinite ( Number ( object . innerRadius ) ) ) ) {
10331133 context . beginPath ( ) ;
10341134 context . arc ( centerX , centerY , outerRadius , 0 , Math . PI * 2 ) ;
@@ -1271,9 +1371,12 @@ function addShapeFromControls() {
12711371 setStatus ( "Load a skin before adding a shape." ) ;
12721372 return ;
12731373 }
1274- const selectedType = refs . newShapeType instanceof HTMLSelectElement
1275- ? normalizeText ( refs . newShapeType . value ) . toLowerCase ( )
1276- : "rectangle" ;
1374+ const selectedType = getSelectedShapeTypeValue ( ) || "rectangle" ;
1375+ if ( selectedType === "flattened" ) {
1376+ updateAddShapeButtonState ( ) ;
1377+ setStatus ( "Flattened objects are created only with the Flatten button." ) ;
1378+ return ;
1379+ }
12771380 const typedName = refs . newShapeName instanceof HTMLInputElement
12781381 ? refs . newShapeName . value
12791382 : "" ;
@@ -1517,6 +1620,7 @@ async function loadPresetFromQuery() {
15171620}
15181621
15191622function bindEvents ( ) {
1623+ updateAddShapeButtonState ( ) ;
15201624 updateFlattenButtonState ( ) ;
15211625 updateObjectOrderButtonState ( ) ;
15221626 refs . loadButton ?. addEventListener ( "click" , ( ) => {
@@ -1533,6 +1637,9 @@ function bindEvents() {
15331637 refs . howToUseButton ?. addEventListener ( "click" , ( ) => {
15341638 window . location . href = "./how_to_use.html" ;
15351639 } ) ;
1640+ refs . newShapeType ?. addEventListener ( "change" , ( ) => {
1641+ updateAddShapeButtonState ( ) ;
1642+ } ) ;
15361643 refs . syncVisualButton ?. addEventListener ( "click" , syncVisualFromJson ) ;
15371644 refs . addShapeButton ?. addEventListener ( "click" , addShapeFromControls ) ;
15381645 refs . renameObjectButton ?. addEventListener ( "click" , renameObjectFromControls ) ;
0 commit comments