import * as util from 'util' ; import { Remote } from '../remotes/remotebase' ; import { BaseView } from './baseview' ; import { ImageConvert } from '../misc/imageconvert' ; import { WebviewPanel } from 'vscode' ; import { Utility } from '../misc/utility' ; import * as Random from 'rng' ; import { Log } from '../log' ; /**
 * Constants to be used for the palete selection in the drop down menu.
 */ enum PaletteSelection { CURRENT , PALETTE_0 , PALETTE_1 , DEFAULT , FALSE_COLORS } ; /**
 * A Webview that shows the ZX Next sprite patterns.
 * The view cannot be edited.
 *
 * The display consists of:
 * - index
 * - Pattern as image
 *
 * The range of the indices can be chosen. Eg. "5 10" or "5 10, 17 2".
 * There exist a checkbox that allows for live update of the patterns and palettes.
 */ export class ZxNextSpritePatternsView extends BaseView { protected static spritesPaletteTransparentIndex : number ; protected static spritePatterns : Map < number , Array < number > > ; protected static spritePalettes : Map < number , Array < number > > ; protected static currentPaletteNumber : number ; protected retrievingError ; /**
     * Initializes the static variables.
     * Called at launchRequest.
     */ public static staticInit ( ) { ZxNextSpritePatternsView . currentPaletteNumber = - 1 ; ZxNextSpritePatternsView . spritesPaletteTransparentIndex = 0 ; ZxNextSpritePatternsView . spritePatterns = new Map < number , Array < number > > ( ) ; ZxNextSpritePatternsView . spritePalettes = new Map < number , Array < number > > ( ) ; BaseView . staticViewClasses . push ( ZxNextSpritePatternsView ) ; } param reason protected static staticUpdate ( reason ? ) { ZxNextSpritePatternsView . currentPaletteNumber = - 1 ; if ( ! reason || reason . step != true ) { ZxNextSpritePatternsView . spritePatterns . clear ( ) ; ZxNextSpritePatternsView . spritePalettes . delete ( PaletteSelection . PALETTE_0 ) ; ZxNextSpritePatternsView . spritePalettes . delete ( PaletteSelection . PALETTE_1 ) ; } } protected patternIds : Array < number > ; protected usedBckgColor = 0 ; protected usedPalette = PaletteSelection . CURRENT ; protected patternDataValid = false ; protected processingWebViewMessage = false ; param parent param title param indexRanges constructor ( title , indexRanges ) { super ( ) ; this . patternIds = new Array < number > ( ) ; while ( true ) { const start = indexRanges . shift ( ) ; if ( start == undefined ) break ; let end = indexRanges . shift ( ) || - 1 ; Utility . assert ( end >= 0 ) ; end += start ; for ( let k = start ; k < end ; k ++ ) { if ( k > 63 ) break ; this . patternIds . push ( k ) ; } } Utility . assert ( this . vscodePanel ) ; ( this . vscodePanel as WebviewPanel ) . title = title ; } param message protected async webViewMessageReceived ( message ) { if ( this . processingWebViewMessage ) return ; this . processingWebViewMessage = true ; switch ( message . command ) { case 'reload' : ZxNextSpritePatternsView . staticUpdate ( ) ; const views = BaseView . staticGetAllViews ( ZxNextSpritePatternsView ) ; for ( const view of views ) { await view . update ( ) ; } break ; case 'bckgColor' : this . usedBckgColor = message . value ; break ; case 'palette' : this . usedPalette = message . value ; ZxNextSpritePatternsView . currentPaletteNumber = - 1 ; await this . update ( ) ; break ; default : Utility . assert ( false ) ; break ; } this . processingWebViewMessage = false ; } param selectedIndex returns protected static staticGetPaletteNumberFromSelectedIndex ( selectedIndex ) { let paletteNumber ; switch ( selectedIndex ) { case PaletteSelection . PALETTE_0 : paletteNumber = 0 ; break ; case PaletteSelection . PALETTE_1 : paletteNumber = 1 ; break ; case PaletteSelection . CURRENT : paletteNumber = ZxNextSpritePatternsView . currentPaletteNumber ; break ; default : Utility . assert ( false ) ; break ; } return paletteNumber ; } param paletteNumber returns protected static staticGetSelectedIndexFromPaletteNumber ( paletteNumber ) { if ( paletteNumber == 0 ) return PaletteSelection . PALETTE_0 ; else { Utility . assert ( paletteNumber == 1 ) ; return PaletteSelection . PALETTE_1 ; } } param selectedIndex return protected static staticGetPaletteForSelectedIndex ( selectedIndex ) { Log . log ( 'ZxNextSpritePatternView::staticGetPaletteForSelectedIndex a, selectedIndex=' + selectedIndex + ', currentPaletteNumber=' + ZxNextSpritePatternsView . currentPaletteNumber ) ; let paletteSelection = selectedIndex ; if ( selectedIndex == PaletteSelection . CURRENT ) { switch ( ZxNextSpritePatternsView . currentPaletteNumber ) { case 0 : paletteSelection = PaletteSelection . PALETTE_0 ; break ; case 1 : paletteSelection = PaletteSelection . PALETTE_1 ; break ; default : Utility . assert ( false ) ; break ; } } const palette = ZxNextSpritePatternsView . spritePalettes . get ( paletteSelection ) ; Log . log ( 'ZxNextSpritePatternView::staticGetPaletteForSelectedIndex b' ) ; return palette ; } param paletteNumber param palette protected static staticSetPaletteForPaletteNumber ( paletteNumber , palette ) { let selectedIndex = ZxNextSpritePatternsView . staticGetSelectedIndexFromPaletteNumber ( paletteNumber ) ; ZxNextSpritePatternsView . spritePalettes . set ( selectedIndex , palette ) ; } /**
     * Create the default palette.
     */ protected createDefaultPalette ( ) { const defaultPalette = new Array < number > ( 3 * 256 ) ; let k = 0 ; const offs = [ 0x0 , 0x2 , 0x5 , 0x7 , 0x8 , 0xA , 0xD , 0xF ] ; for ( let i = 0 ; i < 256 ; i ++ ) { const j = i % 8 ; const high = ( i << 1 ) & 0xF0 ; const val = high + offs [ j ] ; defaultPalette [ k ++ ] = ( val & 0b11100000 ) ; defaultPalette [ k ++ ] = ( ( val << 3 ) & 0b11100000 ) ; defaultPalette [ k ++ ] = ( ( val << 6 ) & 0b11000000 ) | ( ( val >>> 3 ) & 0b00100000 ) ; } return defaultPalette ; } /**
     * Create a false color palette. The purpose is to make
     * sprites/patterns visible (although with false color)
     * even if the palette is incorrect.
     */ protected createFalseColorsPalette ( ) { const falseColorsPalette = new Array < number > ( 3 * 256 ) ; let k = 0 ; const rng = new Random . MT ( 12345 ) ; for ( let i = 0 ; i < 256 ; i ++ ) { falseColorsPalette [ k ++ ] = rng . range ( 0 , 255 ) ; falseColorsPalette [ k ++ ] = rng . range ( 0 , 255 ) ; falseColorsPalette [ k ++ ] = rng . range ( 0 , 255 ) ; } return falseColorsPalette ; } /**
     * First checks which palette is in use, then loads it from the emulator.
     */ protected async getSpritesPalette ( ) { if ( ZxNextSpritePatternsView . currentPaletteNumber >= 0 ) { return ; } let value = await Remote . getTbblueRegister ( 75 ) ; ZxNextSpritePatternsView . spritesPaletteTransparentIndex = value ; value = await Remote . getTbblueRegister ( 0x43 ) ; ZxNextSpritePatternsView . currentPaletteNumber = ( value >>> 3 ) & 0x01 ; if ( ZxNextSpritePatternsView . staticGetPaletteForSelectedIndex ( this . usedPalette ) ) { return ; } let paletteNumber ; const usedPal = this . usedPalette ; switch ( usedPal ) { case PaletteSelection . DEFAULT : ZxNextSpritePatternsView . spritePalettes . set ( usedPal , this . createDefaultPalette ( ) ) ; return ; case PaletteSelection . FALSE_COLORS : ZxNextSpritePatternsView . spritePalettes . set ( usedPal , this . createFalseColorsPalette ( ) ) ; return ; default : paletteNumber = ZxNextSpritePatternsView . staticGetPaletteNumberFromSelectedIndex ( usedPal ) ; break ; } const paletteArray = await Remote . getTbblueSpritesPalette ( paletteNumber ) ; const loadedPalette = new Array < number > ( 3 * 256 ) ; let k = 0 ; for ( const color of paletteArray ) { loadedPalette [ k ++ ] = color & 0b11100000 ; loadedPalette [ k ++ ] = ( color << 3 ) & 0b11100000 ; loadedPalette [ k ++ ] = ( ( color << 6 ) & 0b11000000 ) + ( ( color >>> 3 ) & 0b00100000 ) ; } ZxNextSpritePatternsView . staticSetPaletteForPaletteNumber ( paletteNumber , loadedPalette ) ; } /**
     * Retrieves the sprite patterns from the Remote.
     * It knows which patterns to request from the loaded sprites.
     * And it requests only that data that has not been requested before.
      */ protected async getSpritePatterns ( ) { if ( this . patternIds . length == 0 ) { return ; } const usedIndex = new Array < number > ( ) ; for ( const index of this . patternIds ) { const pattern = ZxNextSpritePatternsView . spritePatterns . get ( index ) ; if ( ! pattern ) { usedIndex . push ( index ) ; const spritePatterns = await Remote . getTbblueSpritePatterns ( index , 1 ) ; const indexPop = usedIndex . shift ( ) ! ; Utility . assert ( indexPop != undefined ) ; if ( spritePatterns . length > 0 ) ZxNextSpritePatternsView . spritePatterns . set ( indexPop , spritePatterns [ 0 ] ) ; } } } param reason public async update ( reason ? ) { try { Log . log ( 'ZxNextSpritePatternView::update a' ) ; this . patternDataValid = ( ! reason || reason . step != true ) ; await this . getSpritesPalette ( ) ; try { Log . log ( 'ZxNextSpritePatternView::update b' ) ; await this . getSpritePatterns ( ) ; } catch ( e ) { this . retrievingError = e . message ; } ; Log . log ( 'ZxNextSpritePatternView::update c' ) ; this . setHtml ( ) ; Log . log ( 'ZxNextSpritePatternView::update d' ) ; } catch ( e ) { Remote . emit ( 'warning' , e . message ) ; } ; } /**
     * Creates the js scripts and the UI elements.
     */ protected createScriptsAndButtons ( ) { const format = `
		<script>

		//----- Does a reload of everything. -----
		function reload() {
			// Send new value for address to vscode
			vscode.postMessage({
				command: 'reload'
			});
		}

		//----- To change the background color of the sprite image -----
		function bckgSelected() {
			// Get color for index
			let color = bckgSelector[bckgSelector.selectedIndex].value;
			// Update sprite patterns backgrounds
			let cells = document.getElementsByClassName("classPattern");
			for(let i=0; i<cells.length; i++) {
				 cells[i].style.background = color;
			}

			// Send request to vscode
			vscode.postMessage({
				command: 'bckgColor',
				value: bckgSelector.selectedIndex
			});
		}

		//----- To change the used palette -----
		function paletteSelected() {
			// Send request to vscode that user selected a new palette.
			// Leads to a complete redraw.
			vscode.postMessage({
				command: 'palette',
				value: paletteSelector.selectedIndex
			});
		}

		</script>

		%s

		<div %s>
		<button onclick="reload()">Reload</button>

		<!-- To change the background color of the sprite pattern -->
		<select id="bckgSelector" onchange="bckgSelected(this);">
			<option value="black">Black Background</option>
			<option value="white">White Background</option>
			<option value="gray">Gray Background</option>
		</select>

		<!-- To change the used palette -->
		<select id="paletteSelector" onchange="paletteSelected(this);">
			<option>Current Palette (%d)</option>
			<option>Sprite Palette 0</option>
			<option>Sprite Palette 1</option>
			<option>Default Palette</option>
			<option>False Colors Palette</option>
		</select>
		</div>

		<br>

		<script>
			// Also select the right index
			let bckgSelector = document.getElementById("bckgSelector");
			bckgSelector.selectedIndex = %d;

			// Set the palette selection
			let paletteSelector = document.getElementById("paletteSelector");
			paletteSelector.selectedIndex = %d;
		</script>
		` ; const invalid = ( this . patternDataValid ) ? "" : "*" ; const hidden = ( this . retrievingError ) ? "hidden" : "" ; const html = util . format ( format , invalid , hidden , ZxNextSpritePatternsView . currentPaletteNumber , this . usedBckgColor , this . usedPalette ) ; return html ; } param currentValue param prevValue protected getTableTdWithBold ( currentValue , prevValue ) { let td = ' <td>' ; td += ( currentValue == prevValue ) ? currentValue : '<b>' + currentValue + '</b>' ; td += '</td>\n' ; return td ; } /**
     * Creates several html table out of the sprite pattern data.
     */ protected createHtmlTable ( ) { if ( this . retrievingError ) return '<div>' + this . retrievingError + '</div>' ; Log . log ( 'ZxNextSpritePatternView::createHtmlTable a' ) ; let palette = ZxNextSpritePatternsView . staticGetPaletteForSelectedIndex ( this . usedPalette ) ; Utility . assert ( palette ) ; let table = `
<style>
	.classPattern {
		width:auto;
		height:2em;
	}
	.classImg {
		image-rendering:pixelated;
		width:auto;
		height:2em;
	}
</style>
` ; let k = 0 ; let count = this . patternIds . length ; for ( const patternId of this . patternIds ) { count -- ; let pattern = ZxNextSpritePatternsView . spritePatterns . get ( patternId ) ; if ( ! pattern ) { pattern = new Array < number > ( 16 * 16 ) ; } if ( k % 16 == 0 ) { table += `<table style="text-align:center; float:left" border="1"
					cellpadding="0">
					<colgroup>
						<col>
						<col>
						<col>
						<col>
					</colgroup>
					<tr>
						<th>Index</th>
						<th>8bit Pattern</th>
						<th>4bit N6=0</th>
						<th>4bit N6=1</th>
					</tr>
				` ; } table += '<tr>\n<td>' + patternId + '</td>\n' ; const buf8b = Buffer . from ( ImageConvert . createGifFromArray ( 16 , 16 , pattern , palette , ZxNextSpritePatternsView . spritesPaletteTransparentIndex ) ) ; const base64String8b = buf8b . toString ( 'base64' ) ; table += ' <td class="classPattern"><img class="classImg" src="data:image/gif;base64,' + base64String8b + '"></td>\n' ; const pattern4b0 = new Array < number > ( 256 ) ; for ( let i = 0 ; i < 128 ; i ++ ) { const val = pattern [ i ] ; pattern4b0 [ 2 * i ] = val >>> 4 ; pattern4b0 [ 2 * i + 1 ] = val & 0x0F ; } const buf4b0 = Buffer . from ( ImageConvert . createGifFromArray ( 16 , 16 , pattern4b0 , palette , ZxNextSpritePatternsView . spritesPaletteTransparentIndex & 0x0F ) ) ; const base64String4b0 = buf4b0 . toString ( 'base64' ) ; table += ' <td class="classPattern"><img class="classImg" src="data:image/gif;base64,' + base64String4b0 + '"></td>\n' ; const pattern4b1 = new Array < number > ( 256 ) ; for ( let i = 0 ; i < 128 ; i ++ ) { const val = pattern [ 128 + i ] ; pattern4b1 [ 2 * i ] = val >>> 4 ; pattern4b1 [ 2 * i + 1 ] = val & 0x0F ; } const buf4b1 = Buffer . from ( ImageConvert . createGifFromArray ( 16 , 16 , pattern4b1 , palette , ZxNextSpritePatternsView . spritesPaletteTransparentIndex & 0x0F ) ) ; const base64String4b1 = buf4b1 . toString ( 'base64' ) ; table += ' <td class="classPattern"><img class="classImg" src="data:image/gif;base64,' + base64String4b1 + '"></td>\n' ; table += '</tr>\n\n' ; if ( ( k % 16 == 15 ) || ( count == 0 ) ) { table += '</table>\n\n' ; } k ++ ; } Log . log ( 'ZxNextSpritePatternView::createHtmlTable b' ) ; return table ; } /**
     * Creates the html surrounding the table etc.
     */ protected createHtmlSkeleton ( ) { const html = `<!DOCTYPE html>
		<html lang="en">
		<head>
			<meta charset="UTF-8">
			<meta name="viewport" content="width=device-width, initial-scale=1.0">
			<title>Dump</title>
		</head>

		<body style="font-family: Courier">

		<script>
			const vscode = acquireVsCodeApi();
		</script>

		%s

		<script>
			// Choose right background color (bckgSelected)
			bckgSelector.onchange(bckgSelector);
		</script>

		</body>

		</html>` ; return html ; } /**
    * Sets the html code to display the sprite patterns.
    */ protected setHtml ( ) { const format = this . createHtmlSkeleton ( ) ; const ui = this . createScriptsAndButtons ( ) ; const table = this . createHtmlTable ( ) ; const html = util . format ( format , ui + table ) ; this . vscodePanel . webview . html = html ; } }