@@ -69,6 +69,8 @@ const RESERVED_PARALLAX_BLOCK = Object.freeze({
6969} ) ;
7070
7171const DEFAULT_TILESET_SWATCH_COLOR = "#64748b" ;
72+ const TILE_PALETTE_EMPTY_TITLE = "No tiles loaded" ;
73+ const TILE_PALETTE_EMPTY_HINT = "Import or create tileset" ;
7274
7375function normalizeSamplePresetPath ( pathValue ) {
7476 if ( typeof pathValue !== "string" ) {
@@ -650,7 +652,7 @@ class TileMapEditorApp {
650652 constructor ( documentModel ) {
651653 this . documentModel = documentModel ;
652654 this . selectedLayerId = documentModel . layers [ 0 ] ?. id || "" ;
653- this . activeTileId = 1 ;
655+ this . activeTileId = this . findFirstNonEmptyTileId ( ) ;
654656 this . activeTool = "paint" ;
655657 this . canvasZoom = 1 ;
656658 this . hoverCell = null ;
@@ -1094,7 +1096,7 @@ class TileMapEditorApp {
10941096 this . documentModel = createInitialDocument ( { width, height, tileSize, mapName } ) ;
10951097 this . selectedLayerId = this . documentModel . layers [ 0 ] ?. id || "" ;
10961098 this . selectedMarkerId = "" ;
1097- this . activeTileId = 1 ;
1099+ this . activeTileId = this . findFirstNonEmptyTileId ( ) ;
10981100 this . tilesetImage = null ;
10991101 this . tilesetImageCache = new Map ( ) ;
11001102 this . clearTransientImageSources ( ) ;
@@ -1368,6 +1370,52 @@ class TileMapEditorApp {
13681370 return tiles [ 0 ] ?. id ?? 0 ;
13691371 }
13701372
1373+ getSelectableTiles ( ) {
1374+ const tileset = Array . isArray ( this . documentModel ?. tileset ) ? this . documentModel . tileset : [ ] ;
1375+ return tileset . filter ( ( tile ) => Number . isFinite ( Number ( tile ?. id ) ) && Number ( tile . id ) > 0 ) ;
1376+ }
1377+
1378+ hasTileSelection ( ) {
1379+ const selectedTileId = Number . parseInt ( this . activeTileId , 10 ) ;
1380+ if ( ! Number . isFinite ( selectedTileId ) ) {
1381+ return false ;
1382+ }
1383+ return this . getSelectableTiles ( ) . some ( ( tile ) => Number ( tile . id ) === selectedTileId ) ;
1384+ }
1385+
1386+ ensureFirstTileSelection ( ) {
1387+ const selectableTiles = this . getSelectableTiles ( ) ;
1388+ if ( selectableTiles . length <= 0 ) {
1389+ this . activeTileId = 0 ;
1390+ return false ;
1391+ }
1392+ if ( this . hasTileSelection ( ) ) {
1393+ return false ;
1394+ }
1395+ this . activeTileId = selectableTiles [ 0 ] . id ;
1396+ return true ;
1397+ }
1398+
1399+ syncTileSelectionControlState ( ) {
1400+ const hasSelection = this . hasTileSelection ( ) ;
1401+ this . refs . activeToolSelect . disabled = ! hasSelection ;
1402+ this . refs . addLayerButton . disabled = ! hasSelection ;
1403+ this . refs . removeLayerButton . disabled = ! hasSelection ;
1404+ this . refs . layerVisibilityToggle . disabled = ! hasSelection ;
1405+ this . refs . markerTypeSelect . disabled = ! hasSelection ;
1406+ this . refs . markerNameInput . disabled = ! hasSelection ;
1407+ this . refs . clearMarkersButton . disabled = ! hasSelection ;
1408+ this . refs . mapCanvas . classList . toggle ( "is-selection-disabled" , ! hasSelection ) ;
1409+ this . refs . mapCanvas . setAttribute ( "aria-disabled" , hasSelection ? "false" : "true" ) ;
1410+ if ( ! hasSelection ) {
1411+ this . refs . simulateButton . disabled = true ;
1412+ this . refs . playSimulationButton . disabled = true ;
1413+ this . refs . pauseSimulationButton . disabled = true ;
1414+ this . refs . restartSimulationButton . disabled = true ;
1415+ this . refs . exitSimulationButton . disabled = true ;
1416+ }
1417+ }
1418+
13711419 readTilesetAtlasSettingsFromInputs ( ) {
13721420 const fallback = sanitizeTilesetAtlas ( this . documentModel . tilesetAtlas , this . documentModel . map . tileSize ) ;
13731421 return {
@@ -1807,6 +1855,11 @@ class TileMapEditorApp {
18071855 return ;
18081856 }
18091857
1858+ if ( ! this . hasTileSelection ( ) ) {
1859+ this . updateStatus ( `${ TILE_PALETTE_EMPTY_TITLE } . ${ TILE_PALETTE_EMPTY_HINT } .` ) ;
1860+ return ;
1861+ }
1862+
18101863 if ( this . activeTool === "marker" ) {
18111864 if ( event . button === 2 ) {
18121865 this . removeMarkerAtCell ( cell . col , cell . row ) ;
@@ -1826,13 +1879,17 @@ class TileMapEditorApp {
18261879
18271880 handleCanvasPointerMove ( event ) {
18281881 const cell = this . getCellFromMouseEvent ( event ) ;
1882+ const hoverChanged = ( cell ?. col ?? - 1 ) !== ( this . hoverCell ?. col ?? - 1 )
1883+ || ( cell ?. row ?? - 1 ) !== ( this . hoverCell ?. row ?? - 1 ) ;
18291884 this . hoverCell = cell ;
18301885
18311886 if ( cell && this . isPointerPainting && ( this . activeTool === "paint" || this . activeTool === "erase" ) ) {
18321887 this . applyCellEdit ( cell . col , cell . row , this . activeTool ) ;
18331888 }
18341889
1835- this . renderCanvas ( ) ;
1890+ if ( hoverChanged || this . isPointerPainting ) {
1891+ this . renderCanvas ( ) ;
1892+ }
18361893 if ( cell ) {
18371894 this . refs . canvasMeta . textContent = `Cell ${ cell . col } , ${ cell . row } ` ;
18381895 } else {
@@ -1841,9 +1898,12 @@ class TileMapEditorApp {
18411898 }
18421899
18431900 handleCanvasPointerLeave ( ) {
1901+ const hadHover = Boolean ( this . hoverCell ) ;
18441902 this . hoverCell = null ;
18451903 this . isPointerPainting = false ;
1846- this . renderCanvas ( ) ;
1904+ if ( hadHover ) {
1905+ this . renderCanvas ( ) ;
1906+ }
18471907 this . refs . canvasMeta . textContent = `${ this . documentModel . map . width } x${ this . documentModel . map . height } ` ;
18481908 }
18491909
@@ -1878,9 +1938,14 @@ class TileMapEditorApp {
18781938
18791939 if ( mode === "picker" ) {
18801940 const sampled = normalizeCellValue ( layer . data [ row ] [ col ] ) ;
1881- this . activeTileId = sampled ;
1882- this . renderTileset ( ) ;
1883- this . updateStatus ( `Sampled tile ${ sampled } at ${ col } , ${ row } .` ) ;
1941+ if ( sampled > 0 ) {
1942+ this . activeTileId = sampled ;
1943+ this . renderTileset ( ) ;
1944+ this . syncTileSelectionControlState ( ) ;
1945+ this . updateStatus ( `Sampled tile ${ sampled } at ${ col } , ${ row } .` ) ;
1946+ } else {
1947+ this . updateStatus ( "No tile sampled at this cell." ) ;
1948+ }
18841949 return ;
18851950 }
18861951
@@ -2583,6 +2648,7 @@ class TileMapEditorApp {
25832648 }
25842649
25852650 renderAll ( ) {
2651+ this . ensureFirstTileSelection ( ) ;
25862652 this . renderLayerList ( ) ;
25872653 this . renderTileset ( ) ;
25882654 this . renderTilesetMeta ( ) ;
@@ -2595,6 +2661,7 @@ class TileMapEditorApp {
25952661 this . updateRemediationUI ( ) ;
25962662 this . updateEditorExperienceUI ( ) ;
25972663 this . updateDebugVisualizationUI ( ) ;
2664+ this . syncTileSelectionControlState ( ) ;
25982665 this . syncUxContractState ( ) ;
25992666 }
26002667
@@ -2655,12 +2722,17 @@ class TileMapEditorApp {
26552722 renderTileset ( ) {
26562723 const container = this . refs . tilePalette ;
26572724 container . innerHTML = "" ;
2725+ const selectableTiles = this . getSelectableTiles ( ) ;
2726+ if ( selectableTiles . length <= 0 ) {
2727+ container . innerHTML = `<p class="tile-palette-empty"><strong>${ TILE_PALETTE_EMPTY_TITLE } </strong><span>${ TILE_PALETTE_EMPTY_HINT } </span></p>` ;
2728+ return ;
2729+ }
26582730
26592731 const fragment = document . createDocumentFragment ( ) ;
2660- this . documentModel . tileset . forEach ( ( tile ) => {
2732+ selectableTiles . forEach ( ( tile ) => {
26612733 const button = document . createElement ( "button" ) ;
26622734 button . type = "button" ;
2663- button . className = `tile-swatch${ tile . id === this . activeTileId ? " active" : "" } ` ;
2735+ button . className = `tile-swatch${ Number ( tile . id ) === Number ( this . activeTileId ) ? " active" : "" } ` ;
26642736 button . dataset . tileId = String ( tile . id ) ;
26652737 button . title = `${ tile . name } (${ tile . id } )` ;
26662738
@@ -2708,6 +2780,7 @@ class TileMapEditorApp {
27082780 button . addEventListener ( "click" , ( ) => {
27092781 this . activeTileId = tile . id ;
27102782 this . renderTileset ( ) ;
2783+ this . syncTileSelectionControlState ( ) ;
27112784 this . updateStatus ( `Selected tile ${ tile . name } (${ tile . id } ).` ) ;
27122785 } ) ;
27132786
@@ -2849,9 +2922,16 @@ class TileMapEditorApp {
28492922 const context = this . refs . canvasContext ;
28502923 const { width, height, tileSize } = this . documentModel . map ;
28512924
2852- canvas . width = width * tileSize ;
2853- canvas . height = height * tileSize ;
2925+ const nextWidth = width * tileSize ;
2926+ const nextHeight = height * tileSize ;
2927+ if ( canvas . width !== nextWidth ) {
2928+ canvas . width = nextWidth ;
2929+ }
2930+ if ( canvas . height !== nextHeight ) {
2931+ canvas . height = nextHeight ;
2932+ }
28542933
2934+ context . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
28552935 context . fillStyle = "#0f172a" ;
28562936 context . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
28572937
@@ -3268,7 +3348,7 @@ function bootTileMapStudio() {
32683348 ? snapshot . selectedLayerId
32693349 : nextDocument . layers [ 0 ] ?. id || "" ;
32703350 this . selectedMarkerId = typeof snapshot ?. selectedMarkerId === "string" ? snapshot . selectedMarkerId : "" ;
3271- this . activeTileId = Number . isInteger ( snapshot ?. activeTileId ) ? snapshot . activeTileId : 1 ;
3351+ this . activeTileId = Number . isInteger ( snapshot ?. activeTileId ) ? snapshot . activeTileId : this . findFirstNonEmptyTileId ( ) ;
32723352 this . tilesetImageCache = new Map ( ) ;
32733353 this . clearTransientImageSources ( ) ;
32743354 this . resolveAssetRefsFromRegistry ( ) ;
0 commit comments