1
0
mirror of https://github.com/gdamore/tcell.git synced 2025-04-24 13:48:51 +08:00

added webfiles directory

updated README.md
organized functions into original order
This commit is contained in:
Ahoys123 2023-01-02 17:29:40 -08:00 committed by Garrett D'Amore
parent 49cc9d21db
commit b86f5127ac
8 changed files with 594 additions and 317 deletions

View File

@ -264,7 +264,21 @@ Windows console mode applications are supported.
Modern console applications like ConEmu and the Windows 10 terminal,
support all the good features (resize, mouse tracking, etc.)
### Plan9, WASM, and others
### WASM
WASM needs special build flags and extra files in the same directory in order to work. You can build it by executing
```sh
GOOS=js GOARCH=wasm build -o yourfile.wasm
```
You also need 5 other files. Four (`tcell.html`, `tcell.js`, `termstyle.css`, and `beep.wav`) are provided in the `webfiles` directory. The last one, `wasm_exec.js`, can be copied from GOROOT into the current directory by executing
```sh
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./
```
It is recomended use an iframe if you want to embed the app into a webpage:
```html
<iframe src="tcell.html" title="Tcell app"></iframe>
```
### Plan9 and others
These platforms won't work, but compilation stubs are supplied
for folks that want to include parts of this in software for those

View File

@ -35,18 +35,6 @@ func (t *tScreen) SetStyle(style Style) {
func (t *tScreen) Clear() {
t.Fill(' ', t.style)
t.Lock()
t.clear = true
w, h := t.cells.Size()
// because we are going to clear (see t.clear) in the next cycle,
// let's also unmark the dirty bit so that we don't waste cycles
// drawing things that are already dealt with via the clear escape sequence.
for row := 0; row < h; row++ {
for col := 0; col < w; col++ {
t.cells.SetDirty(col, row, false)
}
}
t.Unlock()
}
func (t *tScreen) Fill(r rune, style Style) {
@ -57,30 +45,6 @@ func (t *tScreen) Fill(r rune, style Style) {
t.Unlock()
}
func (t *tScreen) EnableMouse(flags ...MouseFlags) {
var f MouseFlags
flagsPresent := false
for _, flag := range flags {
f |= flag
flagsPresent = true
}
if !flagsPresent {
f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
}
t.Lock()
t.mouseFlags = f
t.enableMouse(f)
t.Unlock()
}
func (t *tScreen) DisableMouse() {
t.Lock()
t.mouseFlags = 0
t.enableMouse(0)
t.Unlock()
}
func (t *tScreen) SetContent(x, y int, mainc rune, combc []rune, style Style) {
t.Lock()
if !t.fini {
@ -117,6 +81,30 @@ func (t *tScreen) Show() {
t.Unlock()
}
func (t *tScreen) EnableMouse(flags ...MouseFlags) {
var f MouseFlags
flagsPresent := false
for _, flag := range flags {
f |= flag
flagsPresent = true
}
if !flagsPresent {
f = MouseMotionEvents | MouseDragEvents | MouseButtonEvents
}
t.Lock()
t.mouseFlags = f
t.enableMouse(f)
t.Unlock()
}
func (t *tScreen) DisableMouse() {
t.Lock()
t.mouseFlags = 0
t.enableMouse(0)
t.Unlock()
}
func (t *tScreen) EnablePaste() {
t.Lock()
t.pasteEnabled = true
@ -225,3 +213,5 @@ func (t *tScreen) UnregisterRuneFallback(orig rune) {
delete(t.fallback, orig)
t.Unlock()
}
func (t *tScreen) Resize(int, int, int, int) {}

View File

@ -544,6 +544,36 @@ func (t *tScreen) finish() {
t.finalize()
}
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
nb := make([]byte, 6)
ob := make([]byte, 6)
num := utf8.EncodeRune(ob, r)
ob = ob[:num]
dst := 0
var err error
if enc := t.encoder; enc != nil {
enc.Reset()
dst, _, err = enc.Transform(nb, ob, true)
}
if err != nil || dst == 0 || nb[0] == '\x1a' {
// Combining characters are elided
if len(buf) == 0 {
if acs, ok := t.acs[r]; ok {
buf = append(buf, []byte(acs)...)
} else if fb, ok := t.fallback[r]; ok {
buf = append(buf, []byte(fb)...)
} else {
buf = append(buf, '?')
}
}
} else {
buf = append(buf, nb[:dst]...)
}
return buf
}
func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
ti := t.ti
if ti.Colors == 0 {
@ -625,47 +655,6 @@ func (t *tScreen) sendFgBg(fg Color, bg Color, attr AttrMask) AttrMask {
return attr
}
func (t *tScreen) draw() {
// clobber cursor position, because we're going to change it all
t.cx = -1
t.cy = -1
// make no style assumptions
t.curstyle = styleInvalid
t.buf.Reset()
t.buffering = true
defer func() {
t.buffering = false
}()
// hide the cursor while we move stuff around
t.hideCursor()
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
if width > 1 {
if x+1 < t.w {
// this is necessary so that if we ever
// go back to drawing that cell, we
// actually will *draw* it.
t.cells.SetDirty(x+1, y, true)
}
}
x += width - 1
}
}
// restore the cursor
t.showCursor()
_, _ = t.buf.WriteTo(t.tty)
}
func (t *tScreen) drawCell(x, y int) int {
ti := t.ti
@ -780,6 +769,19 @@ func (t *tScreen) drawCell(x, y int) int {
return width
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
t.cursorx = x
t.cursory = y
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
t.cursorStyle = cs
t.Unlock()
}
func (t *tScreen) showCursor() {
x, y := t.cursorx, t.cursory
@ -799,22 +801,6 @@ func (t *tScreen) showCursor() {
t.cy = y
}
func (t *tScreen) resize() {
if w, h, e := t.tty.WindowSize(); e == nil {
if w != t.w || h != t.h {
t.cx = -1
t.cy = -1
t.cells.Resize(w, h)
t.cells.Invalidate()
t.h = h
t.w = w
ev := NewEventResize(w, h)
_ = t.PostEvent(ev)
}
}
}
// writeString sends a string to the terminal. The string is sent as-is and
// this function does not expand inline padding indications (of the form
// $<[delay]> where [delay] is msec). In order to have these expanded, use
@ -858,6 +844,47 @@ func (t *tScreen) hideCursor() {
}
}
func (t *tScreen) draw() {
// clobber cursor position, because we're going to change it all
t.cx = -1
t.cy = -1
// make no style assumptions
t.curstyle = styleInvalid
t.buf.Reset()
t.buffering = true
defer func() {
t.buffering = false
}()
// hide the cursor while we move stuff around
t.hideCursor()
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
if width > 1 {
if x+1 < t.w {
// this is necessary so that if we ever
// go back to drawing that cell, we
// actually will *draw* it.
t.cells.SetDirty(x+1, y, true)
}
}
x += width - 1
}
}
// restore the cursor
t.showCursor()
_, _ = t.buf.WriteTo(t.tty)
}
func (t *tScreen) enableMouse(f MouseFlags) {
// Rather than using terminfo to find mouse escape sequences, we rely on the fact that
// pretty much *every* terminal that supports mouse tracking follows the
@ -893,6 +920,22 @@ func (t *tScreen) enablePasting(on bool) {
}
}
func (t *tScreen) resize() {
if w, h, e := t.tty.WindowSize(); e == nil {
if w != t.w || h != t.h {
t.cx = -1
t.cy = -1
t.cells.Resize(w, h)
t.cells.Invalidate()
t.h = h
t.w = w
ev := NewEventResize(w, h)
_ = t.PostEvent(ev)
}
}
}
func (t *tScreen) Colors() int {
// this doesn't change, no need for lock
if t.truecolor {
@ -972,36 +1015,6 @@ func (t *tScreen) buildAcsMap() {
}
}
func (t *tScreen) encodeRune(r rune, buf []byte) []byte {
nb := make([]byte, 6)
ob := make([]byte, 6)
num := utf8.EncodeRune(ob, r)
ob = ob[:num]
dst := 0
var err error
if enc := t.encoder; enc != nil {
enc.Reset()
dst, _, err = enc.Transform(nb, ob, true)
}
if err != nil || dst == 0 || nb[0] == '\x1a' {
// Combining characters are elided
if len(buf) == 0 {
if acs, ok := t.acs[r]; ok {
buf = append(buf, []byte(acs)...)
} else if fb, ok := t.fallback[r]; ok {
buf = append(buf, []byte(fb)...)
} else {
buf = append(buf, '?')
}
}
} else {
buf = append(buf, nb[:dst]...)
}
return buf
}
// buildMouseEvent returns an event based on the supplied coordinates and button
// state. Note that the screen's mouse button state is updated based on the
// input to this function (i.e. it mutates the receiver).
@ -1483,17 +1496,8 @@ func (t *tScreen) inputLoop(stopQ chan struct{}) {
}
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
t.cursorx = x
t.cursory = y
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
t.cursorStyle = cs
t.Unlock()
func (t *tScreen) CharacterSet() string {
return t.charset
}
func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
@ -1523,6 +1527,17 @@ func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
return false
}
func (t *tScreen) HasMouse() bool {
return len(t.mouse) != 0
}
func (t *tScreen) HasKey(k Key) bool {
if k == KeyRune {
return true
}
return t.keyexist[k]
}
func (t *tScreen) SetSize(w, h int) {
if t.setWinSize != "" {
t.TPuts(t.ti.TParm(t.setWinSize, w, h))
@ -1540,17 +1555,6 @@ func (t *tScreen) Resume() error {
return t.engage()
}
func (t *tScreen) CharacterSet() string {
return t.charset
}
func (t *tScreen) HasKey(k Key) bool {
if k == KeyRune {
return true
}
return t.keyexist[k]
}
// engage is used to place the terminal in raw mode and establish screen size, etc.
// Think of this is as tcell "engaging" the clutch, as it's going to be driving the
// terminal interface.
@ -1633,12 +1637,6 @@ func (t *tScreen) disengage() {
_ = t.tty.Stop()
}
func (t *tScreen) HasMouse() bool {
return len(t.mouse) != 0
}
func (t *tScreen) Resize(int, int, int, int) {}
// Beep emits a beep to the terminal.
func (t *tScreen) Beep() error {
t.writeString(string(byte(7)))

View File

@ -12,7 +12,10 @@ import (
)
func NewTerminfoScreen() (Screen, error) {
return &tScreen{}, nil
t := &tScreen{}
t.fallback = make(map[rune]string)
return t, nil
}
type tScreen struct {
@ -38,8 +41,7 @@ type tScreen struct {
}
func (t *tScreen) Init() error {
t.w, t.h = 80, 24
t.fallback = make(map[rune]string)
t.w, t.h = 80, 24 // default for html as of now
t.evch = make(chan Event, 10)
t.quit = make(chan struct{})
@ -59,6 +61,127 @@ func (t *tScreen) Fini() {
close(t.quit)
}
func (t *tScreen) drawCell(x, y int) int {
mainc, combc, style, width := t.cells.GetContent(x, y)
if !t.cells.Dirty(x, y) {
return width
}
if style == StyleDefault {
style = t.style
}
fg, bg := style.fg.Hex(), style.bg.Hex()
var combcarr []interface{} = make([]interface{}, len(combc))
for i, c := range combc {
combcarr[i] = c
}
t.cells.SetDirty(x, y, false)
js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs))
return width
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
js.Global().Call("showCursor", x, y)
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs])
t.Unlock()
}
func (t *tScreen) clearScreen() {
js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
t.clear = false
}
func (t *tScreen) draw() {
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
x += width - 1
}
}
js.Global().Call("show")
}
func (t *tScreen) enableMouse(f MouseFlags) {
if f&MouseButtonEvents != 0 {
js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
}
if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
}
}
func (t *tScreen) enablePasting(on bool) {
if on {
js.Global().Set("onPaste", js.FuncOf(t.onPaste))
} else {
js.Global().Set("onPaste", js.FuncOf(t.unset))
}
}
// resize does nothing, as asking the web window to resize
// without a specified width or height will cause no change.
func (t *tScreen) resize() {}
func (t *tScreen) Colors() int {
return 16777216 // 256 ^ 3
}
func (t *tScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod := ModNone
button := ButtonNone
switch args[2].Int() {
case 0:
if t.mouseFlags&MouseMotionEvents == 0 {
// don't want this event! is a mouse motion event, but user has asked not.
return nil
}
button = ButtonNone
case 1:
button = Button1
case 2:
button = Button3 // Note we prefer to treat right as button 2
case 3:
button = Button2 // And the middle button as button 3
}
if args[3].Bool() { // mod shift
mod |= ModShift
}
if args[4].Bool() { // mod alt
mod |= ModAlt
}
if args[5].Bool() { // mod ctrl
mod |= ModCtrl
}
t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
return nil
}
func (t *tScreen) onKeyEvent(this js.Value, args []js.Value) interface{} {
key := args[0].String()
@ -109,128 +232,36 @@ func (t *tScreen) onPaste(this js.Value, args []js.Value) interface{} {
return nil
}
func (t *tScreen) enableMouse(f MouseFlags) {
if f&MouseButtonEvents != 0 {
js.Global().Set("onMouseClick", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseClick", js.FuncOf(t.unset))
}
if f&MouseDragEvents != 0 || f&MouseMotionEvents != 0 {
js.Global().Set("onMouseMove", js.FuncOf(t.onMouseEvent))
} else {
js.Global().Set("onMouseMove", js.FuncOf(t.unset))
}
}
func (t *tScreen) onMouseEvent(this js.Value, args []js.Value) interface{} {
mod := ModNone
button := ButtonNone
switch args[2].Int() {
case 0:
if t.mouseFlags&MouseMotionEvents == 0 {
// don't want this event! is a mouse motion event, but user has asked not.
return nil
}
button = ButtonNone
case 1:
button = Button1
case 2:
button = Button3 // Note we prefer to treat right as button 2
case 3:
button = Button2 // And the middle button as button 3
}
if args[3].Bool() { // mod shift
mod |= ModShift
}
if args[4].Bool() { // mod alt
mod |= ModAlt
}
if args[5].Bool() { // mod ctrl
mod |= ModCtrl
}
t.PostEventWait(NewEventMouse(args[0].Int(), args[1].Int(), button, mod))
return nil
}
// unset is a dummy function for js when we want nothing to
// happen when javascript calls a function (for example, when
// mouse input is disabled, when onMouseEvent() is called from
// js, it redirects here and does nothing).
func (t *tScreen) unset(this js.Value, args []js.Value) interface{} {
return nil
}
func (t *tScreen) Colors() int {
return 16777216 // 256 ^ 3
}
// resize does nothing, as asking the web window to resize
// without a specified width or height will cause no change.
func (t *tScreen) resize() {}
func (t *tScreen) draw() {
if t.clear {
t.clearScreen()
}
for y := 0; y < t.h; y++ {
for x := 0; x < t.w; x++ {
width := t.drawCell(x, y)
x += width - 1
}
}
js.Global().Call("show")
}
func (t *tScreen) clearScreen() {
js.Global().Call("clearScreen", t.style.fg.Hex(), t.style.bg.Hex())
t.clear = false
}
func (t *tScreen) drawCell(x, y int) int {
mainc, combc, style, width := t.cells.GetContent(x, y)
if !t.cells.Dirty(x, y) {
return width
}
if style == StyleDefault {
style = t.style
}
fg, bg := style.fg.Hex(), style.bg.Hex()
var combcarr []interface{} = make([]interface{}, len(combc))
for i, c := range combc {
combcarr[i] = c
}
t.cells.SetDirty(x, y, false)
js.Global().Call("drawCell", x, y, mainc, combcarr, fg, bg, int(style.attrs))
return width
}
func (t *tScreen) ShowCursor(x, y int) {
t.Lock()
js.Global().Call("showCursor", x, y)
t.Unlock()
}
func (t *tScreen) SetCursorStyle(cs CursorStyle) {
t.Lock()
js.Global().Call("setCursorStyle", curStyleClasses[cs])
t.Unlock()
}
func (t *tScreen) CharacterSet() string {
return "UTF-8"
}
func (t *tScreen) CanDisplay(r rune, checkFallbacks bool) bool {
if utf8.ValidRune(r) {
return true
}
if !checkFallbacks {
return false
}
if _, ok := t.fallback[r]; ok {
return true
}
return false
}
func (t *tScreen) HasMouse() bool {
return true
}
func (t *tScreen) HasKey(k Key) bool {
return true
}
@ -247,6 +278,7 @@ func (t *tScreen) SetSize(w, h int) {
}
// Suspend simply pauses all input and output, and clears the screen.
// There isn't a "default terminal" to go back to.
func (t *tScreen) Suspend() error {
t.Lock()
if !t.running {
@ -278,26 +310,6 @@ func (t *tScreen) Resume() error {
return nil
}
func (t *tScreen) HasKey(k Key) bool {
return true
}
func (t *tScreen) enablePasting(on bool) {
if on {
js.Global().Set("onPaste", js.FuncOf(t.onPaste))
} else {
js.Global().Set("onPaste", js.FuncOf(t.unset))
}
}
func (t *tScreen) HasMouse() bool {
return true
}
func (t *tScreen) Resize(x int, y int, w int, h int) {
t.SetSize(w, h)
}
func (t *tScreen) Beep() error {
js.Global().Call("beep")
return nil
@ -396,34 +408,34 @@ var WebKeyNames = map[string]Key{
"F62": KeyF62,
"F63": KeyF63,
"F64": KeyF64,
"Ctrl-a": KeyCtrlA, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-b": KeyCtrlB, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-c": KeyCtrlC, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-d": KeyCtrlD, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-e": KeyCtrlE, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-f": KeyCtrlF, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-g": KeyCtrlG, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-j": KeyCtrlJ, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-k": KeyCtrlK, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-l": KeyCtrlL, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-n": KeyCtrlN, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-o": KeyCtrlO, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-p": KeyCtrlP, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-q": KeyCtrlQ, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-r": KeyCtrlR, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-s": KeyCtrlS, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-t": KeyCtrlT, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-u": KeyCtrlU, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-v": KeyCtrlV, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-w": KeyCtrlW, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-x": KeyCtrlX, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-y": KeyCtrlY, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-z": KeyCtrlZ, // not supported by HTML- reported as key + modifier ctrl
"Ctrl- ": KeyCtrlSpace, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-_": KeyCtrlUnderscore, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-]": KeyCtrlRightSq, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-\\": KeyCtrlBackslash, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-^": KeyCtrlCarat, // not supported by HTML- reported as key + modifier ctrl
"Ctrl-a": KeyCtrlA, // not reported by HTML- need to do special check
"Ctrl-b": KeyCtrlB, // not reported by HTML- need to do special check
"Ctrl-c": KeyCtrlC, // not reported by HTML- need to do special check
"Ctrl-d": KeyCtrlD, // not reported by HTML- need to do special check
"Ctrl-e": KeyCtrlE, // not reported by HTML- need to do special check
"Ctrl-f": KeyCtrlF, // not reported by HTML- need to do special check
"Ctrl-g": KeyCtrlG, // not reported by HTML- need to do special check
"Ctrl-j": KeyCtrlJ, // not reported by HTML- need to do special check
"Ctrl-k": KeyCtrlK, // not reported by HTML- need to do special check
"Ctrl-l": KeyCtrlL, // not reported by HTML- need to do special check
"Ctrl-n": KeyCtrlN, // not reported by HTML- need to do special check
"Ctrl-o": KeyCtrlO, // not reported by HTML- need to do special check
"Ctrl-p": KeyCtrlP, // not reported by HTML- need to do special check
"Ctrl-q": KeyCtrlQ, // not reported by HTML- need to do special check
"Ctrl-r": KeyCtrlR, // not reported by HTML- need to do special check
"Ctrl-s": KeyCtrlS, // not reported by HTML- need to do special check
"Ctrl-t": KeyCtrlT, // not reported by HTML- need to do special check
"Ctrl-u": KeyCtrlU, // not reported by HTML- need to do special check
"Ctrl-v": KeyCtrlV, // not reported by HTML- need to do special check
"Ctrl-w": KeyCtrlW, // not reported by HTML- need to do special check
"Ctrl-x": KeyCtrlX, // not reported by HTML- need to do special check
"Ctrl-y": KeyCtrlY, // not reported by HTML- need to do special check
"Ctrl-z": KeyCtrlZ, // not reported by HTML- need to do special check
"Ctrl- ": KeyCtrlSpace, // not reported by HTML- need to do special check
"Ctrl-_": KeyCtrlUnderscore, // not reported by HTML- need to do special check
"Ctrl-]": KeyCtrlRightSq, // not reported by HTML- need to do special check
"Ctrl-\\": KeyCtrlBackslash, // not reported by HTML- need to do special check
"Ctrl-^": KeyCtrlCarat, // not reported by HTML- need to do special check
}
var curStyleClasses = map[CursorStyle]string{

BIN
webfiles/beep.wav Normal file

Binary file not shown.

13
webfiles/tcell.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Tcell</title>
<script src="wasm_exec.js"></script>
<link rel="stylesheet" href="termstyle.css">
</head>
<body>
<pre id="terminal"></pre>
<script src="tcell.js"></script>
</body>
</html>

183
webfiles/tcell.js Normal file
View File

@ -0,0 +1,183 @@
const term = document.getElementById("terminal")
var width = 80; var height = 24
const beepAudio = new Audio("beep.wav");
var cx = -1; var cy = -1
var cursorClass = "cursor-blinking-block"
var content // {data: row[height], dirty: bool}
// row = {data: element[width], previous: span}
// dirty/[previous being null] indicates if previous (or entire terminal) needs to be recaclulated.
// dirty is true/null if terminal/previous need to be re-calculated/shown
function initialize() {
resize(width, height) // intialize content
show() // then show the screen
}
function resize(w, h) {
width = w
height = h
content = {data: new Array(height), dirty: true}
for (let i = 0; i < height; i++) {
content.data[i] = {data: new Array(width), previous: null}
}
clearScreen()
}
function clearScreen(fg, bg) {
if (fg) { term.style.color = intToHex(fg) }
if (bg) { term.style.backgroundColor = intToHex(bg) }
content.dirty = true
for (let i = 0; i < height; i++) {
content.data[i].previous = null // we set the row to be recacluated later
for (let j = 0; j < width; j++) {
content.data[i].data[j] = document.createTextNode(" ") // set the entire row to spaces.
}
}
}
function drawCell(x, y, mainc, combc, fg, bg, attrs) {
var combString = String.fromCharCode(mainc)
combc.forEach(char => {combString += String.fromCharCode(char)});
var span = document.createElement("span")
var use = false
if (fg) { span.style.color = intToHex(fg); use = true }
if (bg) { span.style.backgroundColor = intToHex(bg); use = true }
if (attrs != 0) {
use = true
if ((attrs & 1) != 0) { span.classList.add("bold") }
if ((attrs & (1<<1)) != 0) { span.classList.add("blink") }
if ((attrs & (1<<2)) != 0) { span.classList.add("reverse") }
if ((attrs & (1<<3)) != 0) { span.classList.add("underline") }
if ((attrs & (1<<4)) != 0) { span.classList.add("dim") }
if ((attrs & (1<<5)) != 0) { span.classList.add("italic") }
if ((attrs & (1<<6)) != 0) { span.classList.add("strikethrough") }
}
var textnode = document.createTextNode(combString)
span.appendChild(textnode)
content.dirty = true // invalidate terminal- new cell
content.data[y].previous = null // invalidate row- new row
content.data[y].data[x] = use ? span : textnode
}
function show() {
if (!content.dirty) {
return // no new draws; no need to update
}
displayCursor()
term.innerHTML = ""
content.data.forEach(row => {
if (row.previous == null) {
row.previous = document.createElement("span")
row.data.forEach(c => {
row.previous.appendChild(c)
})
row.previous.appendChild(document.createTextNode("\n"))
}
term.appendChild(row.previous)
})
content.dirty = false
}
function showCursor(x, y) {
content.dirty = true
if (!(cx < 0 || cy < 0)) { // if original position is a valid cursor position
content.data[cy].previous = null;
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass)
}
}
cx = x
cy = y
}
function displayCursor() {
content.dirty = true
if (!(cx < 0 || cy < 0)) { // if new position is a valid cursor position
content.data[cy].previous = null;
if (!content.data[cy].data[cx].classList) {
var span = document.createElement("span")
span.appendChild(content.data[cy].data[cx])
content.data[cy].data[cx] = span
}
content.data[cy].data[cx].classList.add(cursorClass)
}
}
function setCursorStyle(newClass) {
if (newClass == cursorClass) {
return
}
if (!(cx < 0 || cy < 0)) {
// mark cursor row as dirty; new class has been applied to (cx, cy)
content.dirty = true
content.data[cy].previous = null
if (content.data[cy].data[cx].classList) {
content.data[cy].data[cx].classList.remove(cursorClass)
}
// adding the new class will be dealt with when displayCursor() is called
}
cursorClass = newClass
}
function beep() {
beepAudio.currentTime = 0;
beepAudio.play();
}
function intToHex(n) {
return "#" + n.toString(16).padStart(6, '0')
}
initialize()
let fontwidth = term.clientWidth / width
let fontheight = term.clientHeight / height
document.addEventListener("keydown", e => {
onKeyEvent(e.key, e.shiftKey, e.altKey, e.ctrlKey, e.metaKey)
})
term.addEventListener("click", e => {
onMouseClick(Math.min((e.offsetX / fontwidth) | 0, width-1), Math.min((e.offsetY / fontheight) | 0, height-1), e.which, e.shiftKey, e.altKey, e.ctrlKey)
})
term.addEventListener("mousemove", e => {
onMouseMove(Math.min((e.offsetX / fontwidth) | 0, width-1), Math.min((e.offsetY / fontheight) | 0, height-1), e.which, e.shiftKey, e.altKey, e.ctrlKey)
})
document.addEventListener("paste", e => {
e.preventDefault();
var text = (e.originalEvent || e).clipboardData.getData('text/plain');
onPaste(true)
for (let i = 0; i < text.length; i++) {
onKeyEvent(text.charAt(i), false, false, false, false)
}
onPaste(false)
});
const go = new Go();
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
});

67
webfiles/termstyle.css Normal file
View File

@ -0,0 +1,67 @@
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
}
#terminal {
background-color: black;
color: green;
display: inline-block;
/* Copy paste! */
user-select: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/* Style attributes */
.bold { font-weight: bold; }
.blink { animation: blinker 1s step-start infinite; }
.reverse { unicode-bidi: bidi-override; direction: rtl; }
.underline { text-decoration: underline; }
.dim { filter: brightness(50) }
.italic { font-style: italic; }
.strikethrough { text-decoration: line-through; }
/* Cursor styles */
.cursor-steady-block { background-color: lightgrey !important; }
.cursor-blinking-block { animation: blinking-block 1s step-start infinite !important; }
@keyframes blinking-block { 50% { background-color: lightgrey; } }
.cursor-steady-underline { text-decoration: underline lightgrey !important; }
.cursor-blinking-underline { animation: blinking-underline 1s step-start infinite !important; }
@keyframes blinking-underline { 50% { text-decoration: underline lightgrey; } }
.cursor-steady-bar { margin-left: -2px; }
.cursor-steady-bar:before {
content: ' ';
width: 2px;
background-color: lightgrey !important;
display: inline-block;
}
.cursor-blinking-bar { margin-left: -2px; }
.cursor-blinking-bar:before {
content: ' ';
width: 2px;
background-color: lightgrey !important;
display: inline-block;
animation: blinker 1s step-start infinite;
}
/* General animations */
@keyframes blinker {
50% { opacity: 0; }
}