aboutsummaryrefslogtreecommitdiff
path: root/liust-50/cmd/liustsim
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2025-11-14 10:56:01 +0100
committerPřemysl Eric Janouch <p@janouch.name>2025-11-14 10:56:01 +0100
commit9061523e325c74c0e46c720933891177383ad4de (patch)
tree3e905d57700c313c23f69e0bee71e3bcbde90628 /liust-50/cmd/liustsim
parent4fb06d3b11f371451382bdb5a50e2dee7d19013c (diff)
downloaddesktop-tools-9061523e325c74c0e46c720933891177383ad4de.tar.gz
desktop-tools-9061523e325c74c0e46c720933891177383ad4de.tar.xz
desktop-tools-9061523e325c74c0e46c720933891177383ad4de.zip
WIP: liustsimHEADmaster
Diffstat (limited to 'liust-50/cmd/liustsim')
-rw-r--r--liust-50/cmd/liustsim/simulator.go170
1 files changed, 119 insertions, 51 deletions
diff --git a/liust-50/cmd/liustsim/simulator.go b/liust-50/cmd/liustsim/simulator.go
index 2ad69a8..fd26656 100644
--- a/liust-50/cmd/liustsim/simulator.go
+++ b/liust-50/cmd/liustsim/simulator.go
@@ -4,6 +4,7 @@ import (
"bufio"
"image"
"image/color"
+ "log"
"os"
"strconv"
"strings"
@@ -11,6 +12,10 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
+ "fyne.io/fyne/v2/container"
+ "fyne.io/fyne/v2/layout"
+ "fyne.io/fyne/v2/theme"
+ "fyne.io/fyne/v2/widget"
"janouch.name/desktop-tools/liust-50/charset"
)
@@ -31,7 +36,6 @@ const (
)
type Display struct {
- image *canvas.Image
chars [displayHeight][displayWidth]uint8
charset uint8
cursorX int
@@ -67,25 +71,32 @@ func (d *Display) drawCharacter(
width, height := bounds.Dx(), bounds.Dy()
for dy := 0; dy < height; dy++ {
for dx := 0; dx < width; dx++ {
- c := charImg.At(bounds.Min.X+dx, bounds.Min.Y+dy)
- if r, _, _, _ := c.RGBA(); r >= 0x8000 {
+ var c color.RGBA
+ if r, _, _, _ := charImg.At(
+ bounds.Min.X+dx, bounds.Min.Y+dy).RGBA(); r >= 0x8000 {
c = color.RGBA{0x00, 0xFF, 0xC0, 0xFF}
} else {
c = color.RGBA{0x20, 0x20, 0x20, 0xFF}
}
- img.Set(1+cx*charWidth+dx, 1+cy*charHeight+dy, c)
+ img.SetRGBA(1+cx*charWidth+dx, 1+cy*charHeight+dy, c)
}
}
}
func (d *Display) Render() image.Image {
+ // XXX: The VFD display doesn't have rectangular pixels,
+ // they are rather elongated in a 3:4 ratio.
width := 1 + displayWidth*charWidth
height := 1 + displayHeight*charHeight
+ // XXX: Not sure if we rather don't want to provide double buffering,
+ // meaning we would cycle between two internal buffers.
img := image.NewRGBA(image.Rect(0, 0, width, height))
+
+ black := [4]uint8{0x00, 0x00, 0x00, 0xFF}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
- img.Set(x, y, color.Black)
+ copy(img.Pix[img.PixOffset(x, y):], black[:])
}
}
@@ -175,36 +186,36 @@ func parseANSI(input string) (command string, params []int) {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-type escapeParser struct {
+type protocolParser struct {
seq strings.Builder
inEsc bool
inCSI bool
display *Display
}
-func newEscapeParser(d *Display) *escapeParser {
- return &escapeParser{display: d}
+func newProtocolParser(d *Display) *protocolParser {
+ return &protocolParser{display: d}
}
-func (ep *escapeParser) reset() {
- ep.inEsc = false
- ep.inCSI = false
- ep.seq.Reset()
+func (pp *protocolParser) reset() {
+ pp.inEsc = false
+ pp.inCSI = false
+ pp.seq.Reset()
}
-func (ep *escapeParser) handleCSICommand() bool {
- cmd, params := parseANSI(ep.seq.String())
+func (pp *protocolParser) handleCSICommand() bool {
+ cmd, params := parseANSI(pp.seq.String())
switch cmd {
case "J": // Clear display
- // XXX: No params case is unverified.
+ // XXX: The no params case is unverified.
if len(params) == 0 || params[0] == 2 {
- ep.display.Clear()
+ pp.display.Clear()
}
case "K": // Delete to end of line
- // XXX: No params case is unverified (but it should work).
+ // XXX: The no params case is unverified (but it should work).
if len(params) == 0 || params[0] == 0 {
- ep.display.ClearToEnd()
+ pp.display.ClearToEnd()
}
case "H": // Cursor position
y, x := 0, 0
@@ -214,101 +225,158 @@ func (ep *escapeParser) handleCSICommand() bool {
if len(params) >= 2 {
x = params[1] - 1
}
- ep.display.SetCursor(x, y)
+ pp.display.SetCursor(x, y)
}
return true
}
-func (ep *escapeParser) handleEscapeSequence(b byte) bool {
- ep.seq.WriteByte(b)
+func (pp *protocolParser) handleEscapeSequence(b byte) bool {
+ pp.seq.WriteByte(b)
- if ep.seq.Len() == 2 && b == '[' {
- ep.inCSI = true
+ if pp.seq.Len() == 2 && b == '[' {
+ pp.inCSI = true
return false
}
- if ep.seq.Len() == 3 && ep.seq.String()[1] == 'R' {
- ep.display.charset = b
- ep.reset()
+ if pp.seq.Len() == 3 && pp.seq.String()[1] == 'R' {
+ pp.display.charset = b
+ pp.reset()
return true
}
- if ep.inCSI && (b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z') {
- refresh := ep.handleCSICommand()
- ep.reset()
+ if pp.inCSI && (b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z') {
+ refresh := pp.handleCSICommand()
+ pp.reset()
return refresh
}
- if ep.seq.Len() == 6 && ep.seq.String()[1:5] == "\\?LC" {
- ep.display.cursorMode = int(ep.seq.String()[5])
+ if pp.seq.Len() == 6 && pp.seq.String()[1:5] == "\\?LC" {
+ pp.display.cursorMode = int(pp.seq.String()[5])
return true
}
return false
}
-func (ep *escapeParser) handleControlChar(b byte) bool {
+func (pp *protocolParser) handleCharacter(b byte) bool {
switch b {
case 0x0A: // LF
- ep.display.LineFeed()
+ pp.display.LineFeed()
return true
case 0x0D: // CR
- ep.display.CarriageReturn()
+ pp.display.CarriageReturn()
return true
case 0x08: // BS
- ep.display.Backspace()
+ pp.display.Backspace()
return true
default:
if b >= 0x20 {
- ep.display.PutChar(b)
+ pp.display.PutChar(b)
return true
}
}
return false
}
-func (ep *escapeParser) handleByte(b byte) (needsRefresh bool) {
+func (pp *protocolParser) handleByte(b byte) (needsRefresh bool) {
if b == 0x1b { // ESC
- ep.reset()
- ep.inEsc = true
- ep.seq.WriteByte(b)
+ pp.reset()
+ pp.inEsc = true
+ pp.seq.WriteByte(b)
return false
}
-
- if ep.inEsc {
- return ep.handleEscapeSequence(b)
+ if pp.inEsc {
+ return pp.handleEscapeSequence(b)
}
- return ep.handleControlChar(b)
+ return pp.handleCharacter(b)
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+type DisplayWidget struct {
+ widget.BaseWidget
+ display *Display
+}
+
+type DisplayRenderer struct {
+ image *canvas.Image
+ label *canvas.Text
+
+ objects []fyne.CanvasObject
+ displayWidget *DisplayWidget
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func main() {
- myApp := app.New()
- window := myApp.NewWindow("Toshiba Tec LIUST-50 Simulator")
+ a := app.New()
+ a.Settings().SetTheme(theme.DarkTheme())
+ window := a.NewWindow("Toshiba Tec LIUST-50 Simulator")
display := NewDisplay()
display.Clear()
+ // TODO(p): It makes the most sense to create a custom widget.
+ //
+ // - How does Fyne render text?
+ // - Do I need to give it a left top position and pixel size, or?
+ // - canvas.NewText → canvas.Text
+ //
+ // - How does the Fyne model work?
+ // - All Windows have a Canvas.
+ // - Everything drawn, including Widgets, is a CanvasObject.
+ // - After CanvasObject updates, call Refresh on them.
+ // - It seems like this does not work hierarchically,
+ // and there's a generic canvas.Refresh() function,
+ // and Refresh() methods generally just invoke that.
+ // - window.SetContent() takes a CanvasObject.
+ // - How do WidgetRenderers work?
+ // - How would a widget that renders itself stretched work?
+ //
+ // - What the heck do I need?
+ // - *canvas.Image, *canvas.Text
+ //
+ // - Behaviour:
+ // - Requesting a minimum size:
+ // - Return the pixel size of the display,
+ // and at the bottom add the label.
+ // - For a character 84 pixels tall,
+ // the TOSHIBA label is 35 pixels tall.
+ // That means the TOSHIBA label is 3 pixels tall.
+ // - Given a size allocation:
+ // - How does the API work?
+ // - Rendering:
+ // - This should really be the work of two CanvasObject subobjects.
+ // - The bitmap will be rendered pixel-stretched.
+ // - Below it, the text will be rendered.
+ //
+ // _______________________
+ // | |
+ // | VFD 2x20 |
+ // |______________________|
+ // |________Label_________|
+ //
+ // -
+ //
img := canvas.NewImageFromImage(display.Render())
img.FillMode = canvas.ImageFillOriginal
img.ScaleMode = canvas.ImageScalePixels
- window.SetContent(img)
+ c := container.New(layout.NewVBoxLayout(), layout.NewSpacer(), img, layout.NewSpacer())
+
+ window.SetContent(c)
window.Resize(fyne.NewSize(600, 100))
go func() {
reader := bufio.NewReader(os.Stdin)
- parser := newEscapeParser(display)
+ parser := newProtocolParser(display)
for {
b, err := reader.ReadByte()
if err != nil {
- fyne.DoAndWait(func() {
- window.SetTitle(err.Error())
- })
+ log.Println(err)
return
}