From 7737f65ea5126d12c06871f51a7d337a3a9ba6eb Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 29 Apr 2022 15:06:55 +0900 Subject: [PATCH] fsh update --- assets/disk0/tvdos/bin/flsh.js | 106 ---------------- assets/disk0/tvdos/bin/fsh.js | 116 +++++++++++++++--- assets/disk0/tvdos/gl.js | 60 ++++----- assets/disk0/tvdos/wall.bytes | 3 + assets/disk0/tvdos/wall.png | Bin 0 -> 16511 bytes assets/fsh_wallpaper.kra | 3 + terranmon.txt | 5 +- .../torvald/tsvm/GraphicsJSR223Delegate.kt | 15 ++- .../tsvm/peripheral/GraphicsAdapter.kt | 57 ++++++++- 9 files changed, 206 insertions(+), 159 deletions(-) delete mode 100644 assets/disk0/tvdos/bin/flsh.js create mode 100644 assets/disk0/tvdos/wall.bytes create mode 100644 assets/disk0/tvdos/wall.png create mode 100644 assets/fsh_wallpaper.kra diff --git a/assets/disk0/tvdos/bin/flsh.js b/assets/disk0/tvdos/bin/flsh.js deleted file mode 100644 index 798e149..0000000 --- a/assets/disk0/tvdos/bin/flsh.js +++ /dev/null @@ -1,106 +0,0 @@ -let CURRENT_DRIVE = "A"; - -let shell_pwd = [""]; - -const welcome_text = "TSVM Disk Operating System, version " + _TVDOS.VERSION; - -function print_prompt_text() { - // oh-my-zsh-like prompt - con.color_pair(239,161); - print(" "+CURRENT_DRIVE+":"); - con.color_pair(161,253); - con.addch(16);con.curs_right(); - con.color_pair(0,253); - print(" /"+shell_pwd.join("/")+" "); - con.color_pair(253,255); - con.addch(16);con.curs_right(); - con.addch(32);con.curs_right(); - con.color_pair(239,255); -} - -function greet() { - con.color_pair(0,253); - //print(welcome_text + " ".repeat(_fsh.scrwidth - welcome_text.length)); - print(welcome_text + " ".repeat(80 - welcome_text.length)); - con.color_pair(239,255); - println(); -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -con.clear(); - -greet(); - -let cmdHistory = []; // zeroth element is the oldest -let cmdHistoryScroll = 0; // 0 for outside-of-buffer, 1 for most recent -while (true) { - print_prompt_text(); - - let cmdbuf = ""; - - while (true) { - let key = con.getch(); - - // printable chars - if (key >= 32 && key <= 126) { - let s = String.fromCharCode(key); - cmdbuf += s; - print(s); - } - // backspace - else if (key === 8 && cmdbuf.length > 0) { - cmdbuf = cmdbuf.substring(0, cmdbuf.length - 1); - print(String.fromCharCode(key)); - } - // enter - else if (key === 10 || key === 13) { - println(); - try { - println("You entered: " + cmdbuf); - } - catch (e) { - println(e); - } - finally { - if (cmdbuf.trim().length > 0) - cmdHistory.push(cmdbuf); - - cmdHistoryScroll = 0; - break; - } - } - // up arrow - else if (key === 19 && cmdHistory.length > 0 && cmdHistoryScroll < cmdHistory.length) { - cmdHistoryScroll += 1; - - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; - // re-type the new command - print(cmdbuf); - - } - // down arrow - else if (key === 20) { - if (cmdHistoryScroll > 0) { - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = cmdHistory[cmdHistory.length - cmdHistoryScroll]; - // re-type the new command - print(cmdbuf); - - cmdHistoryScroll -= 1; - } - else { - // back the cursor in order to type new cmd - let x = 0; - for (x = 0; x < cmdbuf.length; x++) print(String.fromCharCode(8)); - cmdbuf = ""; - } - } - } -} \ No newline at end of file diff --git a/assets/disk0/tvdos/bin/fsh.js b/assets/disk0/tvdos/bin/fsh.js index 9b19eba..eec9de6 100644 --- a/assets/disk0/tvdos/bin/fsh.js +++ b/assets/disk0/tvdos/bin/fsh.js @@ -1,4 +1,4 @@ -graphics.setBackground(3,3,3); +graphics.setBackground(2,1,3); graphics.resetPalette(); function captureUserInput() { @@ -18,7 +18,15 @@ _fsh.brandName = "f\xb3Sh"; _fsh.brandLogoTexSmall = new GL.Texture(24, 14, gzip.decomp(base64.atob( "H4sIAAAAAAAAAPv/Hy/4Qbz458+fIeILQQBIwoSh6qECuMVBukCmIJkDVQ+RQNgLE0MX/w+1lyhxqIUwTLJ/sQMAcIXsbVABAAA=" ))); -_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.apps_n_files"]; +_fsh.scrlayout = ["com.fsh.clock","com.fsh.calendar","com.fsh.todo_list", "com.fsh.quick_access"]; + +_fsh.drawWallpaper = function() { + filesystem.open("A", "/tvdos/wall.bytes", "R") + let b = sys.malloc(250880) + dma.comToRam(0, 0, b, 250880) + dma.ramToFrame(b, 0, 250880) + sys.free(b) +}; _fsh.drawTitlebar = function(titletext) { GL.drawTexPattern(_fsh.titlebarTex, 0, 0, 560, 14); @@ -58,7 +66,7 @@ _fsh.registerNewWidget = function(widget) { _fsh.widgets[widget.identifier] = widget; } -var clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7); +var clockWidget = new _fsh.Widget("com.fsh.clock", _fsh.scrwidth - 8, 7*2); clockWidget.numberSheet = new GL.SpriteSheet(19, 22, new GL.Texture(190, 22, gzip.decomp(base64.atob( "H4sIAAAAAAAAAMWVW3LEMAgE739aHcFJJV5ZMD2I9ToVfcl4GBr80HF8r/FaR1ozMuIyoUu87lEXI0al5qVR5AebSwchSaNE6Nyo1Nw5HXF3SfPT4Bshl"+ "EycA8RD96mLlHbuhTgOrfLnUDZspafbSQWk56WEGvQEtWaWwgb8iz7a8AOXhsraO/q9Qw2/GnXovfVN+q2wM/p/oddn2cjF239GX3y11+SWCtc6FTHC1v"+ @@ -84,12 +92,12 @@ clockWidget.draw = function(charXoff, charYoff) { if (ordinalDay == 119) dayName = 7; // Verddag var years = ((timeInMinutes / (60*24*30*120))|0) + 125; // draw timepiece - GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff); - GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff); - GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5); - GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14); - GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff); - GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff); + GL.drawSprite(clockWidget.numberSheet, (hours / 10)|0, 0, xoff, yoff, 1); + GL.drawSprite(clockWidget.numberSheet, hours % 10, 0, xoff + 24, yoff, 1); + GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 5, 1); + GL.drawTexImage(clockWidget.clockColon, xoff + 48, yoff + 14, 1); + GL.drawSprite(clockWidget.numberSheet, (mins / 10)|0, 0, xoff + 57, yoff, 1); + GL.drawSprite(clockWidget.numberSheet, mins % 10, 0, xoff + 81, yoff, 1); // print month and date con.move(1 + charYoff, 17 + charXoff); print(clockWidget.monthNames[months]+" "+visualDay); @@ -100,24 +108,100 @@ clockWidget.draw = function(charXoff, charYoff) { }; +var calendarWidget = new _fsh.Widget("com.fsh.calendar", (_fsh.scrwidth - 8) / 2, 7*6) +calendarWidget.dayLabels = [ + " 1 2 3 4 5 6 7 \xFA\xFA", + " 8 9 10 11 12 13 14 \xFA\xFA", + "15 16 17 18 19 20 21 \xFA\xFA", + "22 23 24 25 26 27 28 \xFA\xFA", + "29 30 1 2 3 4 5 \xFA\xFA", + " 6 7 8 9 10 11 12 \xFA\xFA", + "13 14 15 16 17 18 19 \xFA\xFA", + "20 21 22 23 24 25 26 \xFA\xFA", + "27 28 29 30 1 2 3 \xFA\xFA", + " 4 5 6 7 8 9 10 \xFA\xFA", + "11 12 13 14 15 16 17 \xFA\xFA", + "18 19 20 21 22 23 24 \xFA\xFA", + "25 26 27 28 29 30 1 \xFA\xFA", + " 2 3 4 5 6 7 8 \xFA\xFA", + " 9 10 11 12 13 14 15 \xFA\xFA", + "16 17 18 19 20 21 22 \xFA\xFA", + "23 24 25 26 27 28 29 30" +] +calendarWidget.seasonCols = [229,39,215,73,253] +calendarWidget.draw = function(charXoff, charYoff) { + con.color_pair(254, 255) + let xoff = charXoff * 7 + let yoff = charYoff * 14 + 3 + + let timeInMinutes = ((sys.currentTimeInMills() / 60000)|0) + let ordinalDay = ((timeInMinutes / (60*24))|0) % 120 + let offset = (ordinalDay / 7)|0 + + con.move(charXoff, charYoff) + print("Mo Ty Mi To Fr La Su Ve") + + for (let i = -3; i <= 3; i++) { + let lineOff = (offset + i) % 17 + let line = calendarWidget.dayLabels[lineOff] + let textCol = 0 + + con.move(charXoff + 4 + i, charYoff) + + for (let x = 0; x <= 23; x++) { + let paintingDayOrd = lineOff*7 + ((x/3)|0) + if (x >= 21 && lineOff != 16) textCol = calendarWidget.seasonCols[4] + else textCol = calendarWidget.seasonCols[(paintingDayOrd / 30)|0] + + // special colour for spaces between numbers + if (x % 3 == 2) con.color_pair(255,255) + // mark today + else if (paintingDayOrd == ordinalDay) con.color_pair(0,textCol) + // paint normal day number with seasonal colour + else con.color_pair(textCol,255) + + con.addch(line.charCodeAt(x)) + con.curs_right() + } + } +} + + +// change graphics mode and check if it's supported +graphics.setGraphicsMode(3) +if (graphics.getGraphicsMode() == 0) { + printerrln("Insufficient VRAM") + return 1 +} + // register widgets -_fsh.registerNewWidget(clockWidget); +_fsh.registerNewWidget(clockWidget) +_fsh.registerNewWidget(calendarWidget) // screen init -con.color_pair(254, 255); -con.clear(); -con.curs_set(0); -_fsh.drawTitlebar(); +con.color_pair(254, 255) +con.clear() +con.curs_set(0) +graphics.clearPixels(255) +graphics.clearPixels2(255) +graphics.setFramebufferScroll(0,0) +_fsh.drawWallpaper() +_fsh.drawTitlebar() // TEST con.move(2,1); -print("Hit backspace to exit"); +print("Hit backspace to exit") + +// TODO update for events: key down (updates some widgets), timer (updates clock and calendar widgets) while (true) { captureUserInput(); if (getKeyPushed(0) == 67) break; - _fsh.widgets["com.fsh.clock"].draw(25, 2); + _fsh.widgets["com.fsh.clock"].draw(25, 3); + _fsh.widgets["com.fsh.calendar"].draw(8, 12); + + sys.spin();sys.spin() } con.move(3,1); diff --git a/assets/disk0/tvdos/gl.js b/assets/disk0/tvdos/gl.js index 4969330..9bcc880 100644 --- a/assets/disk0/tvdos/gl.js +++ b/assets/disk0/tvdos/gl.js @@ -35,33 +35,34 @@ GL.SpriteSheet = function(tilew, tileh, tex) { } this.getOffX = function(x) { // THIS, or: GL.SpriteSheet.prototype.getOffX - var tx = this.tileWidth * (x|0); + let tx = this.tileWidth * (x|0); if (tx + this.tileWidth > this.texture.width) throw "Sprite x-offset of "+tx+" is greater than sprite width "+this.texture.width; return tx; }; this.getOffY = function(y) { - var ty = this.tileHeight * (y|0); + let ty = this.tileHeight * (y|0); if (ty + this.tileHeight > this.texture.height) throw "Sprite y-offset of "+ty+" is greater than sprite height "+this.texture.height; return ty; }; }; -GL.drawTexPattern = function(texture, x, y, width, height, fgcol, bgcol) { +GL.drawTexPattern = function(texture, x, y, width, height, framebuffer, fgcol, bgcol) { if (!(texture instanceof GL.Texture) && !(texture instanceof GL.MonoTex)) throw Error("Texture is not a GL Texture types"); + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 for (let yy = 0; yy < height; yy++) { for (let xx = 0; xx < width;) { let tx = xx % texture.width; let ty = yy % texture.height; if (texture instanceof GL.Texture) { let c = texture.texData[ty * texture.width + tx]; - graphics.plotPixel(x + xx, y + yy, c); + paint(x + xx, y + yy, c); } else if (texture instanceof GL.MonoTex) { let octet = texture.texData[ty * (texture.width >> 3) + (tx >> 3)]; for (let i = 0; i < 8; i++) { let bit = ((octet >>> (7 - i)) & 1 != 0) - graphics.plotPixel(x + xx + i, y + yy, bit ? bgcol : fgcol); + paint(x + xx + i, y + yy, bit ? bgcol : fgcol); } } @@ -69,24 +70,25 @@ GL.drawTexPattern = function(texture, x, y, width, height, fgcol, bgcol) { } } }; -GL.drawTexPatternOver = function(texture, x, y, width, height, fgcol) { +GL.drawTexPatternOver = function(texture, x, y, width, height, framebuffer, fgcol) { if (!(texture instanceof GL.Texture) && !(texture instanceof GL.MonoTex)) throw Error("Texture is not a GL Texture types"); + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 for (let yy = 0; yy < height; yy++) { for (let xx = 0; xx < width;) { let tx = xx % texture.width; let ty = yy % texture.height; if (texture instanceof GL.Texture) { + let c = texture.texData[ty * texture.width + tx]; if ((c & 255) != 255) { - let c = texture.texData[ty * texture.width + tx]; - graphics.plotPixel(x + xx, y + yy, c); + paint(x + xx, y + yy, c); } } else if (texture instanceof GL.MonoTex) { let octet = texture.texData[ty * (texture.width >> 3) + (tx >> 3)]; for (let i = 0; i < 8; i++) { let bit = ((octet >>> (7 - i)) & 1 != 0) - if (bit) graphics.plotPixel(x + xx + i, y + yy, fgcol); + if (bit) paint(x + xx + i, y + yy, fgcol); } } @@ -97,14 +99,14 @@ GL.drawTexPatternOver = function(texture, x, y, width, height, fgcol) { /* * Draws a texture verbatim - color of 255 will be written to the screen buffer */ -GL.drawTexImage = function(texture, x, y, fgcol, bgcol) { - GL.drawTexPattern(texture, x, y, texture.width, texture.height, fgcol, bgcol); +GL.drawTexImage = function(texture, x, y, framebuffer, fgcol, bgcol) { + GL.drawTexPattern(texture, x, y, texture.width, texture.height, framebuffer, fgcol, bgcol); }; /* * Draws texture with blitting - color of 255 will pass-thru what's already on the screen buffer */ -GL.drawTexImageOver = function(texture, x, y, fgcol) { - GL.drawTexPatternOver(texture, x, y, texture.width, texture.height, fgcol); +GL.drawTexImageOver = function(texture, x, y, framebuffer, fgcol) { + GL.drawTexPatternOver(texture, x, y, texture.width, texture.height, framebuffer, fgcol); }; /* * @param xi x-index in the spritesheet, ZERO-BASED INDEX @@ -114,16 +116,17 @@ GL.drawTexImageOver = function(texture, x, y, fgcol) { * @param overrideFG if the value is set and the current pixel of the sheet is not 255, plots this colour instead * @param overrideBG if the value is set and the current pixel of the sheet is 255, plots this colour instead */ -GL.drawSprite = function(sheet, xi, yi, x, y, overrideFG, overrideBG) { - var offx = sheet.getOffX(xi); - var offy = sheet.getOffY(yi); - for (var ty = 0; ty < sheet.tileHeight; ty++) { - for (var tx = 0; tx < sheet.tileWidth; tx++) { - var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; +GL.drawSprite = function(sheet, xi, yi, x, y, framebuffer, overrideFG, overrideBG) { + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 + let offx = sheet.getOffX(xi); + let offy = sheet.getOffY(yi); + for (let ty = 0; ty < sheet.tileHeight; ty++) { + for (let tx = 0; tx < sheet.tileWidth; tx++) { + let c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; if ((c & 255) == 255) - graphics.plotPixel(x + tx, (y + ty)|0, (overrideBG !== undefined) ? overrideBG : c); + paint(x + tx, (y + ty)|0, (overrideBG !== undefined) ? overrideBG : c); else - graphics.plotPixel(x + tx, (y + ty)|0, (overrideFG !== undefined) ? overrideFG : c); + paint(x + tx, (y + ty)|0, (overrideFG !== undefined) ? overrideFG : c); } } }; @@ -134,14 +137,15 @@ GL.drawSprite = function(sheet, xi, yi, x, y, overrideFG, overrideBG) { * @param y y-position on the framebuffer where the sprite will be drawn * @param overrideFG if the value is set and the current pixel of the sheet is not 255, plots this colour instead */ -GL.drawSpriteOver = function(sheet, xi, yi, x, y, overrideFG) { - var offx = sheet.getOffX(xi); - var offy = sheet.getOffY(yi); - for (var ty = 0; ty < sheet.tileHeight; ty++) { - for (var tx = 0; tx < sheet.tileWidth; tx++) { - var c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; +GL.drawSpriteOver = function(sheet, xi, yi, x, y, framebuffer, overrideFG) { + let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2 + let offx = sheet.getOffX(xi); + let offy = sheet.getOffY(yi); + for (let ty = 0; ty < sheet.tileHeight; ty++) { + for (let tx = 0; tx < sheet.tileWidth; tx++) { + let c = sheet.texture.texData[(ty + offy) * sheet.texture.width + (tx + offx)]; if ((c & 255) != 255) { - graphics.plotPixel(x + tx, (y + ty)|0, overrideFG || c); + paint(x + tx, (y + ty)|0, overrideFG || c); } } } diff --git a/assets/disk0/tvdos/wall.bytes b/assets/disk0/tvdos/wall.bytes new file mode 100644 index 0000000..a9919a3 --- /dev/null +++ b/assets/disk0/tvdos/wall.bytes @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9d409e3c294508bc15a8e549e4b4c5f3a630fe211678ba8644466a7ce1cd16a +size 250880 diff --git a/assets/disk0/tvdos/wall.png b/assets/disk0/tvdos/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..a819c1055eba304a2884fef823d71e7e8e4299ca GIT binary patch literal 16511 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 { framebuffer.fillWith(arg1.toByte()) - framebuffer2?.fillWith(arg2.toByte()) + } + 4 -> { + framebuffer2?.fillWith(arg1.toByte()) } 3 -> { for (it in 0 until 1024) { @@ -784,6 +786,54 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi } } } + else if (graphicsMode == 3 && framebuffer2 != null) { + val layerOrder = (if (graphicsMode == 1) LAYERORDERS4 else LAYERORDERS2)[layerArrangement] + + val fb1 = if (layerOrder[0] == 0) framebuffer else framebuffer2 + val fb2 = if (layerOrder[0] == 0) framebuffer2 else framebuffer + + for (y in 0 until HEIGHT) { + var xoff = scanlineOffsets[2L * y].toUint() or scanlineOffsets[2L * y + 1].toUint().shl(8) + if (xoff.and(0x8000) != 0) xoff = xoff or 0xFFFF0000.toInt() + val xs = (0 + xoff).coerceIn(0, WIDTH - 1)..(WIDTH - 1 + xoff).coerceIn(0, WIDTH - 1) + + if (xoff in -(WIDTH - 1) until WIDTH) { + for (x in xs) { + val colourIndex1 = fb1[y.toLong() * WIDTH + (x - xoff)].toUint() + val colourIndex2 = fb2[y.toLong() * WIDTH + (x - xoff)].toUint() + val colour1 = Color( + paletteOfFloats[4 * colourIndex1], + paletteOfFloats[4 * colourIndex1 + 1], + paletteOfFloats[4 * colourIndex1 + 2], + paletteOfFloats[4 * colourIndex1 + 3] + ) + val colour2 = Color( + paletteOfFloats[4 * colourIndex2], + paletteOfFloats[4 * colourIndex2 + 1], + paletteOfFloats[4 * colourIndex2 + 2], + paletteOfFloats[4 * colourIndex2 + 3] + ) + val colour = listOf(colour1, colour2).fold(Color(0)) { dest, src -> + // manually alpha compositing + // out_color = {src_color * src_alpha + dest_color * dest_alpha * (1-src_alpha)} / out_alpha + // see https://gamedev.stackexchange.com/a/115786 + val outAlpha = (dest.a + (1f - dest.a) * src.a).coerceIn(0.0001f, 1f) // identical to 1 - (1 - dest.a) * (1 - src.a) but this is more optimised form + + // src.a + dest.a - src.a*dest.a) + Color( + (src.r * src.a + dest.r * dest.a * (1f - src.a)) / outAlpha, + (src.g * src.a + dest.g * dest.a * (1f - src.a)) / outAlpha, + (src.b * src.a + dest.b* dest.a * (1f - src.a)) / outAlpha, + outAlpha + ) + } + + framebufferOut.setColor(colour) + framebufferOut.drawPixel(x, y) + } + } + } + } else if (isRefSize && (graphicsMode == 1 || graphicsMode == 2)) { val layerOrder = (if (graphicsMode == 1) LAYERORDERS4 else LAYERORDERS2)[layerArrangement] for (y in 0..223) { @@ -849,11 +899,6 @@ open class GraphicsAdapter(private val assetsRoot: String, val vm: VM, val confi if (xoff in -(WIDTH - 1) until WIDTH) { for (x in xs) { - // this only works because framebuffer is guaranteed to be 8bpp - /*framebuffer2.pixels.put( - y * WIDTH + x, - framebuffer.pixels.get(y * WIDTH + (x - xoff)) // coerceIn not required as (x - xoff) never escapes 0..559 - )*/ val colourIndex = framebuffer[y.toLong() * WIDTH + (x - xoff)].toUint() // coerceIn not required as (x - xoff) never escapes 0..559 framebufferOut.setColor(paletteOfFloats[4*colourIndex], paletteOfFloats[4*colourIndex+1], paletteOfFloats[4*colourIndex+2], paletteOfFloats[4*colourIndex+3]) framebufferOut.drawPixel(x, y)