diff --git a/appServer.go b/appServer.go index e5dec7f..afd4452 100644 --- a/appServer.go +++ b/appServer.go @@ -78,8 +78,8 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { io.WriteString(w, app.getStartPage()) case "/ws": - if brige := CreateSocketBrige(w, req); brige != nil { - go app.socketReader(brige) + if bridge := CreateSocketBridge(w, req); bridge != nil { + go app.socketReader(bridge) } default: @@ -96,12 +96,12 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } -func (app *application) socketReader(brige webBrige) { +func (app *application) socketReader(bridge webBridge) { var session Session events := make(chan DataObject, 1024) for { - message, ok := brige.readMessage() + message, ok := bridge.readMessage() if !ok { events <- NewDataObject("disconnect") return @@ -116,28 +116,28 @@ func (app *application) socketReader(brige webBrige) { switch command { case "startSession": answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { + if session, answer = app.startSession(obj, events, bridge); session != nil { + if !bridge.writeMessage(answer) { return } session.onStart() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) } case "reconnect": if sessionText, ok := obj.PropertyValue("session"); ok { if sessionID, err := strconv.Atoi(sessionText); err == nil { if session = app.sessions[sessionID]; session != nil { - session.setBrige(events, brige) + session.setBridge(events, bridge) answer := allocStringBuilder() defer freeStringBuilder(answer) session.writeInitScript(answer) - if !brige.writeMessage(answer.String()) { + if !bridge.writeMessage(answer.String()) { return } session.onReconnect() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) return } DebugLogF("Session #%d not exists", sessionID) @@ -149,12 +149,12 @@ func (app *application) socketReader(brige webBrige) { } answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { + if session, answer = app.startSession(obj, events, bridge); session != nil { + if !bridge.writeMessage(answer) { return } session.onStart() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) } case "answer": @@ -173,7 +173,7 @@ func (app *application) socketReader(brige webBrige) { } } -func sessionEventHandler(session Session, events chan DataObject, brige webBrige) { +func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) { for { data := <-events @@ -185,7 +185,7 @@ func sessionEventHandler(session Session, events chan DataObject, brige webBrige case "session-close": session.onFinish() session.App().removeSession(session.ID()) - brige.close() + bridge.close() default: session.handleEvent(command, data) @@ -193,13 +193,13 @@ func sessionEventHandler(session Session, events chan DataObject, brige webBrige } } -func (app *application) startSession(params DataObject, events chan DataObject, brige webBrige) (Session, string) { +func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) { if app.createContentFunc == nil { return nil, "" } session := newSession(app, app.nextSessionID(), "", params) - session.setBrige(events, brige) + session.setBridge(events, bridge) if !session.setContent(app.createContentFunc(session)) { return nil, "" } diff --git a/appWasm.go b/appWasm.go index f8bdccc..1ebfc66 100644 --- a/appWasm.go +++ b/appWasm.go @@ -17,7 +17,7 @@ type wasmApp struct { params AppParams createContentFunc func(Session) SessionContent session Session - brige webBrige + bridge webBridge close chan DataObject } @@ -36,28 +36,28 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any { /* case "startSession": answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { + if session, answer = app.startSession(obj, events, bridge); session != nil { + if !bridge.writeMessage(answer) { return } session.onStart() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) } case "reconnect": if sessionText, ok := obj.PropertyValue("session"); ok { if sessionID, err := strconv.Atoi(sessionText); err == nil { if session = app.sessions[sessionID]; session != nil { - session.setBrige(events, brige) + session.setBridge(events, bridge) answer := allocStringBuilder() defer freeStringBuilder(answer) session.writeInitScript(answer) - if !brige.writeMessage(answer.String()) { + if !bridge.writeMessage(answer.String()) { return } session.onReconnect() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) return } DebugLogF("Session #%d not exists", sessionID) @@ -69,12 +69,12 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any { } answer := "" - if session, answer = app.startSession(obj, events, brige); session != nil { - if !brige.writeMessage(answer) { + if session, answer = app.startSession(obj, events, bridge); session != nil { + if !bridge.writeMessage(answer) { return } session.onStart() - go sessionEventHandler(session, events, brige) + go sessionEventHandler(session, events, bridge) } case "disconnect": @@ -84,7 +84,7 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any { case "session-close": session.onFinish() session.App().removeSession(session.ID()) - brige.close() + bridge.close() */ case "answer": @@ -109,7 +109,7 @@ func (app *wasmApp) removeSession(id int) { func (app *wasmApp) createSession() Session { session := newSession(app, 0, "", ParseDataText(js.Global().Call("sessionInfo", "").String())) - session.setBrige(app.close, app.brige) + session.setBridge(app.close, app.bridge) session.setContent(app.createContentFunc(session)) return session } @@ -196,7 +196,7 @@ func (app *wasmApp) init(params AppParams) { body.Call("appendChild", div) if params.TitleColor != 0 { - app.brige.callFunc("setTitleColor", params.TitleColor.cssString()) + app.bridge.callFunc("setTitleColor", params.TitleColor.cssString()) } } @@ -212,7 +212,7 @@ func StartApp(addr string, createContentFunc func(Session) SessionContent, param app := new(wasmApp) app.createContentFunc = createContentFunc - app.brige = createWasmBrige() + app.bridge = createWasmBridge() app.init(params) <-app.close diff --git a/app_scripts.js b/app_scripts.js index defb07e..8586733 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1799,13 +1799,15 @@ function appendStyles(styles) { function getCanvasContext(elementId) { const canvas = document.getElementById(elementId) - const ctx = canvas.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - //var gradient; - //var path; - //var img; - ctx.canvas.width = dpr * canvas.clientWidth; - ctx.canvas.height = dpr * canvas.clientHeight; - ctx.scale(dpr, dpr); - return ctx; + if (canvas) { + const ctx = canvas.getContext('2d'); + if (ctx) { + const dpr = window.devicePixelRatio || 1; + ctx.canvas.width = dpr * canvas.clientWidth; + ctx.canvas.height = dpr * canvas.clientHeight; + ctx.scale(dpr, dpr); + return ctx; + } + } + return null; } diff --git a/canvas.go b/canvas.go index a1d4656..35abf05 100644 --- a/canvas.go +++ b/canvas.go @@ -486,6 +486,19 @@ func (canvas *canvasData) SetLineCap(cap int) { } func (canvas *canvasData) SetLineDash(dash []float64, offset float64) { + /*buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + lead := '[' + for _, val := range dash { + buffer.WriteRune(lead) + lead = ',' + buffer.WriteString(fmt.Sprintf("%g", val)) + } + buffer.WriteRune(']') + + canvas.session.callCanvasFunc("setLineDash", buffer.String()) + */ canvas.session.callCanvasFunc("setLineDash", dash) if offset >= 0 { canvas.session.updateCanvasProperty("lineDashOffset", offset) diff --git a/session.go b/session.go index 3d8e349..8c57ca2 100644 --- a/session.go +++ b/session.go @@ -7,7 +7,7 @@ import ( "strings" ) -type webBrige interface { +type webBridge interface { startUpdateScript(htmlID string) bool finishUpdateScript(htmlID string) callFunc(funcName string, args ...any) bool @@ -111,7 +111,7 @@ type Session interface { nextViewID() string styleProperty(styleTag, property string) any - setBrige(events chan DataObject, brige webBrige) + setBridge(events chan DataObject, bridge webBridge) writeInitScript(writer *strings.Builder) callFunc(funcName string, args ...any) updateInnerHTML(htmlID, html string) @@ -175,7 +175,7 @@ type sessionData struct { ignoreUpdates bool popups *popupManager images *imageManager - brige webBrige + bridge webBridge events chan DataObject animationCounter int animationCSS string @@ -219,9 +219,9 @@ func (session *sessionData) ID() int { return session.sessionID } -func (session *sessionData) setBrige(events chan DataObject, brige webBrige) { +func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) { session.events = events - session.brige = brige + session.bridge = bridge } func (session *sessionData) close() { @@ -313,7 +313,7 @@ func (session *sessionData) reload() { } func (session *sessionData) ignoreViewUpdates() bool { - return session.brige == nil || session.ignoreUpdates + return session.bridge == nil || session.ignoreUpdates } func (session *sessionData) setIgnoreViewUpdates(ignore bool) { @@ -351,8 +351,8 @@ func (session *sessionData) imageManager() *imageManager { } func (session *sessionData) callFunc(funcName string, args ...any) { - if session.brige != nil { - session.brige.callFunc(funcName, args...) + if session.bridge != nil { + session.bridge.callFunc(funcName, args...) } else { ErrorLog("No connection") } @@ -360,8 +360,8 @@ func (session *sessionData) callFunc(funcName string, args ...any) { func (session *sessionData) updateInnerHTML(htmlID, html string) { if !session.ignoreViewUpdates() { - if session.brige != nil { - session.brige.updateInnerHTML(htmlID, html) + if session.bridge != nil { + session.bridge.updateInnerHTML(htmlID, html) } else { ErrorLog("No connection") } @@ -370,8 +370,8 @@ func (session *sessionData) updateInnerHTML(htmlID, html string) { func (session *sessionData) appendToInnerHTML(htmlID, html string) { if !session.ignoreViewUpdates() { - if session.brige != nil { - session.brige.appendToInnerHTML(htmlID, html) + if session.bridge != nil { + session.bridge.appendToInnerHTML(htmlID, html) } else { ErrorLog("No connection") } @@ -379,90 +379,90 @@ func (session *sessionData) appendToInnerHTML(htmlID, html string) { } func (session *sessionData) updateCSSProperty(htmlID, property, value string) { - if !session.ignoreViewUpdates() && session.brige != nil { - session.brige.updateCSSProperty(htmlID, property, value) + if !session.ignoreViewUpdates() && session.bridge != nil { + session.bridge.updateCSSProperty(htmlID, property, value) } } func (session *sessionData) updateProperty(htmlID, property string, value any) { - if !session.ignoreViewUpdates() && session.brige != nil { - session.brige.updateProperty(htmlID, property, value) + if !session.ignoreViewUpdates() && session.bridge != nil { + session.bridge.updateProperty(htmlID, property, value) } } func (session *sessionData) removeProperty(htmlID, property string) { - if !session.ignoreViewUpdates() && session.brige != nil { - session.brige.removeProperty(htmlID, property) + if !session.ignoreViewUpdates() && session.bridge != nil { + session.bridge.removeProperty(htmlID, property) } } func (session *sessionData) startUpdateScript(htmlID string) bool { - if session.brige != nil { - return session.brige.startUpdateScript(htmlID) + if session.bridge != nil { + return session.bridge.startUpdateScript(htmlID) } return false } func (session *sessionData) finishUpdateScript(htmlID string) { - if session.brige != nil { - session.brige.finishUpdateScript(htmlID) + if session.bridge != nil { + session.bridge.finishUpdateScript(htmlID) } } func (session *sessionData) cavnasStart(htmlID string) { - if session.brige != nil { - session.brige.cavnasStart(htmlID) + if session.bridge != nil { + session.bridge.cavnasStart(htmlID) } } func (session *sessionData) callCanvasFunc(funcName string, args ...any) { - if session.brige != nil { - session.brige.callCanvasFunc(funcName, args...) + if session.bridge != nil { + session.bridge.callCanvasFunc(funcName, args...) } } func (session *sessionData) updateCanvasProperty(property string, value any) { - if session.brige != nil { - session.brige.updateCanvasProperty(property, value) + if session.bridge != nil { + session.bridge.updateCanvasProperty(property, value) } } func (session *sessionData) createCanvasVar(funcName string, args ...any) any { - if session.brige != nil { - return session.brige.createCanvasVar(funcName, args...) + if session.bridge != nil { + return session.bridge.createCanvasVar(funcName, args...) } return nil } func (session *sessionData) callCanvasVarFunc(v any, funcName string, args ...any) { - if session.brige != nil && v != nil { - session.brige.callCanvasVarFunc(v, funcName, args...) + if session.bridge != nil && v != nil { + session.bridge.callCanvasVarFunc(v, funcName, args...) } } func (session *sessionData) callCanvasImageFunc(url string, property string, funcName string, args ...any) { - if session.brige != nil { - session.brige.callCanvasImageFunc(url, property, funcName, args...) + if session.bridge != nil { + session.bridge.callCanvasImageFunc(url, property, funcName, args...) } } func (session *sessionData) cavnasFinish() { - if session.brige != nil { - session.brige.cavnasFinish() + if session.bridge != nil { + session.bridge.cavnasFinish() } } func (session *sessionData) runScript(script string) { - if session.brige != nil { - session.brige.writeMessage(script) + if session.bridge != nil { + session.bridge.writeMessage(script) } else { ErrorLog("No connection") } } func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMetrics { - if session.brige != nil { - return session.brige.canvasTextMetrics(htmlID, font, text) + if session.bridge != nil { + return session.bridge.canvasTextMetrics(htmlID, font, text) } ErrorLog("No connection") @@ -470,8 +470,8 @@ func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMet } func (session *sessionData) htmlPropertyValue(htmlID, name string) string { - if session.brige != nil { - return session.brige.htmlPropertyValue(htmlID, name) + if session.bridge != nil { + return session.bridge.htmlPropertyValue(htmlID, name) } ErrorLog("No connection") @@ -479,7 +479,7 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string { } func (session *sessionData) handleAnswer(data DataObject) { - session.brige.answerReceived(data) + session.bridge.answerReceived(data) } func (session *sessionData) handleRootSize(data DataObject) { @@ -620,7 +620,7 @@ func (session *sessionData) SetTitleColor(color Color) { } func (session *sessionData) RemoteAddr() string { - return session.brige.remoteAddr() + return session.bridge.remoteAddr() } func (session *sessionData) OpenURL(urlStr string) { diff --git a/sessionTheme.go b/sessionTheme.go index 8149fab..cad3bd3 100644 --- a/sessionTheme.go +++ b/sessionTheme.go @@ -325,7 +325,7 @@ func (session *sessionData) SetLanguage(lang string) { if lang != session.language { session.language = lang - if session.rootView != nil && session.brige != nil { + if session.rootView != nil && session.bridge != nil { buffer := allocStringBuilder() defer freeStringBuilder(buffer) @@ -334,7 +334,7 @@ func (session *sessionData) SetLanguage(lang string) { //buffer.WriteString("';\nscanElementsSize();") //session.runScript(buffer.String()) - session.brige.updateInnerHTML("ruiRootView", buffer.String()) + session.bridge.updateInnerHTML("ruiRootView", buffer.String()) } } } diff --git a/wasmBridge.go b/wasmBridge.go new file mode 100644 index 0000000..5eddabb --- /dev/null +++ b/wasmBridge.go @@ -0,0 +1,177 @@ +//go:build wasm + +package rui + +import ( + "fmt" + "strconv" + "sync" + "syscall/js" +) + +type wasmBridge struct { + queue chan string + answer map[int]chan DataObject + answerID int + answerMutex sync.Mutex + closed bool + canvas js.Value +} + +func createWasmBridge() webBridge { + bridge := new(wasmBridge) + bridge.queue = make(chan string, 1000) + bridge.answerID = 1 + bridge.answer = make(map[int]chan DataObject) + bridge.closed = false + + return bridge +} + +func (bridge *wasmBridge) startUpdateScript(htmlID string) bool { + return false +} + +func (bridge *wasmBridge) finishUpdateScript(htmlID string) { +} + +func (bridge *wasmBridge) callFunc(funcName string, args ...any) bool { + if ProtocolInDebugLog { + text := funcName + "(" + for i, arg := range args { + if i > 0 { + text += fmt.Sprintf(", `%v`", arg) + } else { + text += fmt.Sprintf("`%v`", arg) + } + } + DebugLog(text + ")") + } + + js.Global().Call(funcName, args...) + return true +} + +func (bridge *wasmBridge) updateInnerHTML(htmlID, html string) { + bridge.callFunc("updateInnerHTML", htmlID, html) +} + +func (bridge *wasmBridge) appendToInnerHTML(htmlID, html string) { + bridge.callFunc("appendToInnerHTML", htmlID, html) +} + +func (bridge *wasmBridge) updateCSSProperty(htmlID, property, value string) { + bridge.callFunc("updateCSSProperty", htmlID, property, value) +} + +func (bridge *wasmBridge) updateProperty(htmlID, property string, value any) { + bridge.callFunc("updateProperty", htmlID, property, value) +} + +func (bridge *wasmBridge) removeProperty(htmlID, property string) { + bridge.callFunc("removeProperty", htmlID, property) +} + +func (bridge *wasmBridge) close() { +} + +func (bridge *wasmBridge) readMessage() (string, bool) { + return "", false +} + +func (bridge *wasmBridge) writeMessage(script string) bool { + if ProtocolInDebugLog { + DebugLog("Run script:") + DebugLog(script) + } + + window := js.Global().Get("window") + window.Call("execScript", script) + + return true +} + +func (bridge *wasmBridge) cavnasStart(htmlID string) { + bridge.canvas = js.Global().Call("getCanvasContext", htmlID) +} + +func (bridge *wasmBridge) callCanvasFunc(funcName string, args ...any) { + if !bridge.canvas.IsNull() { + for i, arg := range args { + if array, ok := arg.([]float64); ok { + arr := make([]any, len(array)) + for k, x := range array { + arr[k] = x + } + args[i] = js.ValueOf(array) + } + } + + bridge.canvas.Call(funcName, args...) + } +} + +func (bridge *wasmBridge) callCanvasVarFunc(v any, funcName string, args ...any) { + if jsVar, ok := v.(js.Value); ok && !jsVar.IsNull() { + jsVar.Call(funcName, args...) + } +} + +func (bridge *wasmBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) { + image := js.Global().Get("images").Call("get", url) + if !image.IsUndefined() && !image.IsNull() { + result := image.Call(funcName, args...) + if property != "" { + bridge.canvas.Set(property, result) + } + } +} + +func (bridge *wasmBridge) createCanvasVar(funcName string, args ...any) any { + if bridge.canvas.IsNull() { + return bridge.canvas + } + return bridge.canvas.Call(funcName, args...) +} + +func (bridge *wasmBridge) updateCanvasProperty(property string, value any) { + if !bridge.canvas.IsNull() { + bridge.canvas.Set(property, value) + } +} + +func (bridge *wasmBridge) cavnasFinish() { +} + +func (bridge *wasmBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics { + result := js.Global().Call("canvasTextMetrics", 0, htmlID, font, text).String() + DebugLog(result) + + // TODO + return TextMetrics{} +} + +func (bridge *wasmBridge) htmlPropertyValue(htmlID, name string) string { + return "" +} + +func (bridge *wasmBridge) answerReceived(answer DataObject) { + if text, ok := answer.PropertyValue("answerID"); ok { + if id, err := strconv.Atoi(text); err == nil { + if chanel, ok := bridge.answer[id]; ok { + chanel <- answer + delete(bridge.answer, id) + } else { + ErrorLog("Bad answerID = " + text + " (chan not found)") + } + } else { + ErrorLog("Invalid answerID = " + text) + } + } else { + ErrorLog("answerID not found") + } +} + +func (bridge *wasmBridge) remoteAddr() string { + return "localhost" +} diff --git a/wasmBrige.go b/wasmBrige.go deleted file mode 100644 index ac72bea..0000000 --- a/wasmBrige.go +++ /dev/null @@ -1,144 +0,0 @@ -//go:build wasm - -package rui - -import ( - "fmt" - "strconv" - "sync" - "syscall/js" -) - -type wasmBrige struct { - queue chan string - answer map[int]chan DataObject - answerID int - answerMutex sync.Mutex - closed bool -} - -func createWasmBrige() webBrige { - brige := new(wasmBrige) - brige.queue = make(chan string, 1000) - brige.answerID = 1 - brige.answer = make(map[int]chan DataObject) - brige.closed = false - - return brige -} - -func (brige *wasmBrige) startUpdateScript(htmlID string) bool { - return false -} - -func (brige *wasmBrige) finishUpdateScript(htmlID string) { -} - -func (brige *wasmBrige) callFunc(funcName string, args ...any) bool { - if ProtocolInDebugLog { - text := funcName + "(" - for i, arg := range args { - if i > 0 { - text += fmt.Sprintf(", `%v`", arg) - } else { - text += fmt.Sprintf("`%v`", arg) - } - } - DebugLog(text + ")") - } - - js.Global().Call(funcName, args...) - return true -} - -func (brige *wasmBrige) updateInnerHTML(htmlID, html string) { - brige.callFunc("updateInnerHTML", htmlID, html) -} - -func (brige *wasmBrige) appendToInnerHTML(htmlID, html string) { - brige.callFunc("appendToInnerHTML", htmlID, html) -} - -func (brige *wasmBrige) updateCSSProperty(htmlID, property, value string) { - brige.callFunc("updateCSSProperty", htmlID, property, value) -} - -func (brige *wasmBrige) updateProperty(htmlID, property string, value any) { - brige.callFunc("updateProperty", htmlID, property, value) -} - -func (brige *wasmBrige) removeProperty(htmlID, property string) { - brige.callFunc("removeProperty", htmlID, property) -} - -func (brige *wasmBrige) close() { -} - -func (brige *wasmBrige) readMessage() (string, bool) { - return "", false -} - -func (brige *wasmBrige) writeMessage(script string) bool { - if ProtocolInDebugLog { - DebugLog("Run script:") - DebugLog(script) - } - - window := js.Global().Get("window") - window.Call("execScript", script) - - return true -} - -/* - func (brige *wasmBrige) runGetterScript(script string) DataObject { - brige.answerMutex.Lock() - answerID := brige.answerID - brige.answerID++ - brige.answerMutex.Unlock() - - answer := make(chan DataObject) - brige.answer[answerID] = answer - errorText := "" - - js.Global().Set("answerID", strconv.Itoa(answerID)) - - window := js.Global().Get("window") - window.Call("execScript", script) - - result := NewDataObject("error") - result.SetPropertyValue("text", errorText) - delete(brige.answer, answerID) - return result - } -*/ - -func (brige *wasmBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics { - // TODO - return TextMetrics{} -} - -func (brige *wasmBrige) htmlPropertyValue(htmlID, name string) string { - return "" -} - -func (brige *wasmBrige) answerReceived(answer DataObject) { - if text, ok := answer.PropertyValue("answerID"); ok { - if id, err := strconv.Atoi(text); err == nil { - if chanel, ok := brige.answer[id]; ok { - chanel <- answer - delete(brige.answer, id) - } else { - ErrorLog("Bad answerID = " + text + " (chan not found)") - } - } else { - ErrorLog("Invalid answerID = " + text) - } - } else { - ErrorLog("answerID not found") - } -} - -func (brige *wasmBrige) remoteAddr() string { - return "localhost" -} diff --git a/webBridge.go b/webBridge.go new file mode 100644 index 0000000..54b3209 --- /dev/null +++ b/webBridge.go @@ -0,0 +1,418 @@ +//go:build !wasm + +package rui + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/gorilla/websocket" +) + +type wsBridge struct { + conn *websocket.Conn + answer map[int]chan DataObject + answerID int + answerMutex sync.Mutex + closed bool + buffer strings.Builder + canvasBuffer strings.Builder + canvasVarNumber int + updateScripts map[string]*strings.Builder +} + +type canvasVar struct { + name string +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 8096, +} + +func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge { + conn, err := upgrader.Upgrade(w, req, nil) + if err != nil { + ErrorLog(err.Error()) + return nil + } + + bridge := new(wsBridge) + bridge.answerID = 1 + bridge.answer = make(map[int]chan DataObject) + bridge.conn = conn + bridge.closed = false + bridge.updateScripts = map[string]*strings.Builder{} + return bridge +} + +func (bridge *wsBridge) close() { + bridge.closed = true + bridge.conn.Close() +} + +func (bridge *wsBridge) startUpdateScript(htmlID string) bool { + if _, ok := bridge.updateScripts[htmlID]; ok { + return false + } + buffer := allocStringBuilder() + bridge.updateScripts[htmlID] = buffer + buffer.WriteString("var element = document.getElementById('") + buffer.WriteString(htmlID) + buffer.WriteString("');\nif (element) {\n") + return true +} + +func (bridge *wsBridge) finishUpdateScript(htmlID string) { + if buffer, ok := bridge.updateScripts[htmlID]; ok { + buffer.WriteString("scanElementsSize();\n}\n") + bridge.writeMessage(buffer.String()) + freeStringBuilder(buffer) + delete(bridge.updateScripts, htmlID) + } +} + +func (bridge *wsBridge) argToString(arg any) (string, bool) { + switch arg := arg.(type) { + case string: + arg = strings.ReplaceAll(arg, "\\", `\\`) + arg = strings.ReplaceAll(arg, "'", `\'`) + arg = strings.ReplaceAll(arg, "\n", `\n`) + arg = strings.ReplaceAll(arg, "\r", `\r`) + arg = strings.ReplaceAll(arg, "\t", `\t`) + arg = strings.ReplaceAll(arg, "\b", `\b`) + arg = strings.ReplaceAll(arg, "\f", `\f`) + arg = strings.ReplaceAll(arg, "\v", `\v`) + return `'` + arg + `'`, true + + case rune: + switch arg { + case '\t': + return `'\t'`, true + case '\r': + return `'\r'`, true + case '\n': + return `'\n'`, true + case '\b': + return `'\b'`, true + case '\f': + return `'\f'`, true + case '\v': + return `'\v'`, true + case '\'': + return `'\''`, true + case '\\': + return `'\\'`, true + } + if arg < ' ' { + return fmt.Sprintf(`'\x%02d'`, int(arg)), true + } + return `'` + string(arg) + `'`, true + + case bool: + if arg { + return "true", true + } else { + return "false", true + } + + case float32: + return fmt.Sprintf("%g", float64(arg)), true + + case float64: + return fmt.Sprintf("%g", arg), true + + case []float64: + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + lead := '[' + for _, val := range arg { + buffer.WriteRune(lead) + lead = ',' + buffer.WriteString(fmt.Sprintf("%g", val)) + } + buffer.WriteRune(']') + return buffer.String(), true + + case canvasVar: + return arg.name, true + + default: + if n, ok := isInt(arg); ok { + return fmt.Sprintf("%d", n), true + } + } + + ErrorLog("Unsupported agument type") + return "", false +} + +func (bridge *wsBridge) callFunc(funcName string, args ...any) bool { + bridge.buffer.Reset() + bridge.buffer.WriteString(funcName) + bridge.buffer.WriteRune('(') + for i, arg := range args { + argText, ok := bridge.argToString(arg) + if !ok { + return false + } + + if i > 0 { + bridge.buffer.WriteString(", ") + } + bridge.buffer.WriteString(argText) + } + bridge.buffer.WriteString(");") + + funcText := bridge.buffer.String() + if ProtocolInDebugLog { + DebugLog("Run func: " + funcText) + } + if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil { + ErrorLog(err.Error()) + return false + } + return true +} + +func (bridge *wsBridge) updateInnerHTML(htmlID, html string) { + bridge.callFunc("updateInnerHTML", htmlID, html) +} + +func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) { + bridge.callFunc("appendToInnerHTML", htmlID, html) +} + +func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) { + if buffer, ok := bridge.updateScripts[htmlID]; ok { + buffer.WriteString(`element.style['`) + buffer.WriteString(property) + buffer.WriteString(`'] = '`) + buffer.WriteString(value) + buffer.WriteString("';\n") + } else { + bridge.callFunc("updateCSSProperty", htmlID, property, value) + } +} + +func (bridge *wsBridge) updateProperty(htmlID, property string, value any) { + if buffer, ok := bridge.updateScripts[htmlID]; ok { + if val, ok := bridge.argToString(value); ok { + buffer.WriteString(`element.setAttribute('`) + buffer.WriteString(property) + buffer.WriteString(`', `) + buffer.WriteString(val) + buffer.WriteString(");\n") + } + } else { + bridge.callFunc("updateProperty", htmlID, property, value) + } +} + +func (bridge *wsBridge) removeProperty(htmlID, property string) { + if buffer, ok := bridge.updateScripts[htmlID]; ok { + buffer.WriteString(`if (element.hasAttribute('`) + buffer.WriteString(property) + buffer.WriteString(`')) { element.removeAttribute('`) + buffer.WriteString(property) + buffer.WriteString("');}\n") + } else { + bridge.callFunc("removeProperty", htmlID, property) + } +} + +func (bridge *wsBridge) cavnasStart(htmlID string) { + bridge.canvasBuffer.Reset() + bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`) + bridge.canvasBuffer.WriteString(htmlID) + bridge.canvasBuffer.WriteString(`');`) +} + +func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) { + bridge.canvasBuffer.WriteString("\nctx.") + bridge.canvasBuffer.WriteString(funcName) + bridge.canvasBuffer.WriteRune('(') + for i, arg := range args { + if i > 0 { + bridge.canvasBuffer.WriteString(", ") + } + argText, _ := bridge.argToString(arg) + bridge.canvasBuffer.WriteString(argText) + } + bridge.canvasBuffer.WriteString(");") +} + +func (bridge *wsBridge) updateCanvasProperty(property string, value any) { + bridge.canvasBuffer.WriteString("\nctx.") + bridge.canvasBuffer.WriteString(property) + bridge.canvasBuffer.WriteString(" = ") + argText, _ := bridge.argToString(value) + bridge.canvasBuffer.WriteString(argText) + bridge.canvasBuffer.WriteString(";") +} + +func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any { + bridge.canvasVarNumber++ + result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)} + bridge.canvasBuffer.WriteString("\nvar ") + bridge.canvasBuffer.WriteString(result.name) + bridge.canvasBuffer.WriteString(" = ctx.") + bridge.canvasBuffer.WriteString(funcName) + bridge.canvasBuffer.WriteRune('(') + for i, arg := range args { + if i > 0 { + bridge.canvasBuffer.WriteString(", ") + } + argText, _ := bridge.argToString(arg) + bridge.canvasBuffer.WriteString(argText) + } + bridge.canvasBuffer.WriteString(");") + return result +} + +func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) { + varName, ok := v.(canvasVar) + if !ok { + return + } + bridge.canvasBuffer.WriteString("\n") + bridge.canvasBuffer.WriteString(varName.name) + bridge.canvasBuffer.WriteRune('.') + bridge.canvasBuffer.WriteString(funcName) + bridge.canvasBuffer.WriteRune('(') + for i, arg := range args { + if i > 0 { + bridge.canvasBuffer.WriteString(", ") + } + argText, _ := bridge.argToString(arg) + bridge.canvasBuffer.WriteString(argText) + } + bridge.canvasBuffer.WriteString(");") +} + +func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) { + + bridge.canvasBuffer.WriteString("\nimg = images.get('") + bridge.canvasBuffer.WriteString(url) + bridge.canvasBuffer.WriteString("');\nif (img) {\n") + if property != "" { + bridge.canvasBuffer.WriteString("ctx.") + bridge.canvasBuffer.WriteString(property) + bridge.canvasBuffer.WriteString(" = ") + } + bridge.canvasBuffer.WriteString("ctx.") + bridge.canvasBuffer.WriteString(funcName) + bridge.canvasBuffer.WriteString("(img") + for _, arg := range args { + bridge.canvasBuffer.WriteString(", ") + argText, _ := bridge.argToString(arg) + bridge.canvasBuffer.WriteString(argText) + } + bridge.canvasBuffer.WriteString(");\n}") +} + +func (bridge *wsBridge) cavnasFinish() { + bridge.canvasBuffer.WriteString("\n") + script := bridge.canvasBuffer.String() + if ProtocolInDebugLog { + DebugLog("Run script:") + DebugLog(script) + } + if bridge.conn == nil { + ErrorLog("No connection") + } else if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { + ErrorLog(err.Error()) + } +} + +func (bridge *wsBridge) readMessage() (string, bool) { + _, p, err := bridge.conn.ReadMessage() + if err != nil { + if !bridge.closed { + ErrorLog(err.Error()) + } + return "", false + } + + return string(p), true +} + +func (bridge *wsBridge) writeMessage(script string) bool { + if ProtocolInDebugLog { + DebugLog("Run script:") + DebugLog(script) + } + if bridge.conn == nil { + ErrorLog("No connection") + return false + } + if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { + ErrorLog(err.Error()) + return false + } + return true +} + +func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics { + result := TextMetrics{} + + bridge.answerMutex.Lock() + answerID := bridge.answerID + bridge.answerID++ + bridge.answerMutex.Unlock() + + answer := make(chan DataObject) + bridge.answer[answerID] = answer + + if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) { + data := <-answer + result.Width = dataFloatProperty(data, "width") + } + + delete(bridge.answer, answerID) + return result +} + +func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string { + bridge.answerMutex.Lock() + answerID := bridge.answerID + bridge.answerID++ + bridge.answerMutex.Unlock() + + answer := make(chan DataObject) + bridge.answer[answerID] = answer + + if bridge.callFunc("getPropertyValue", answerID, htmlID, name) { + data := <-answer + if value, ok := data.PropertyValue("value"); ok { + return value + } + } + + delete(bridge.answer, answerID) + return "" +} + +func (bridge *wsBridge) answerReceived(answer DataObject) { + if text, ok := answer.PropertyValue("answerID"); ok { + if id, err := strconv.Atoi(text); err == nil { + if chanel, ok := bridge.answer[id]; ok { + chanel <- answer + delete(bridge.answer, id) + } else { + ErrorLog("Bad answerID = " + text + " (chan not found)") + } + } else { + ErrorLog("Invalid answerID = " + text) + } + } else { + ErrorLog("answerID not found") + } +} + +func (bridge *wsBridge) remoteAddr() string { + return bridge.conn.RemoteAddr().String() +} diff --git a/webBrige.go b/webBrige.go deleted file mode 100644 index 1a88174..0000000 --- a/webBrige.go +++ /dev/null @@ -1,418 +0,0 @@ -//go:build !wasm - -package rui - -import ( - "fmt" - "net/http" - "strconv" - "strings" - "sync" - - "github.com/gorilla/websocket" -) - -type wsBrige struct { - conn *websocket.Conn - answer map[int]chan DataObject - answerID int - answerMutex sync.Mutex - closed bool - buffer strings.Builder - canvasBuffer strings.Builder - canvasVarNumber int - updateScripts map[string]*strings.Builder -} - -type canvasVar struct { - name string -} - -var upgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 8096, -} - -func CreateSocketBrige(w http.ResponseWriter, req *http.Request) webBrige { - conn, err := upgrader.Upgrade(w, req, nil) - if err != nil { - ErrorLog(err.Error()) - return nil - } - - brige := new(wsBrige) - brige.answerID = 1 - brige.answer = make(map[int]chan DataObject) - brige.conn = conn - brige.closed = false - brige.updateScripts = map[string]*strings.Builder{} - return brige -} - -func (brige *wsBrige) close() { - brige.closed = true - brige.conn.Close() -} - -func (brige *wsBrige) startUpdateScript(htmlID string) bool { - if _, ok := brige.updateScripts[htmlID]; ok { - return false - } - buffer := allocStringBuilder() - brige.updateScripts[htmlID] = buffer - buffer.WriteString("var element = document.getElementById('") - buffer.WriteString(htmlID) - buffer.WriteString("');\nif (element) {\n") - return true -} - -func (brige *wsBrige) finishUpdateScript(htmlID string) { - if buffer, ok := brige.updateScripts[htmlID]; ok { - buffer.WriteString("scanElementsSize();\n}\n") - brige.writeMessage(buffer.String()) - freeStringBuilder(buffer) - delete(brige.updateScripts, htmlID) - } -} - -func (brige *wsBrige) argToString(arg any) (string, bool) { - switch arg := arg.(type) { - case string: - arg = strings.ReplaceAll(arg, "\\", `\\`) - arg = strings.ReplaceAll(arg, "'", `\'`) - arg = strings.ReplaceAll(arg, "\n", `\n`) - arg = strings.ReplaceAll(arg, "\r", `\r`) - arg = strings.ReplaceAll(arg, "\t", `\t`) - arg = strings.ReplaceAll(arg, "\b", `\b`) - arg = strings.ReplaceAll(arg, "\f", `\f`) - arg = strings.ReplaceAll(arg, "\v", `\v`) - return `'` + arg + `'`, true - - case rune: - switch arg { - case '\t': - return `'\t'`, true - case '\r': - return `'\r'`, true - case '\n': - return `'\n'`, true - case '\b': - return `'\b'`, true - case '\f': - return `'\f'`, true - case '\v': - return `'\v'`, true - case '\'': - return `'\''`, true - case '\\': - return `'\\'`, true - } - if arg < ' ' { - return fmt.Sprintf(`'\x%02d'`, int(arg)), true - } - return `'` + string(arg) + `'`, true - - case bool: - if arg { - return "true", true - } else { - return "false", true - } - - case float32: - return fmt.Sprintf("%g", float64(arg)), true - - case float64: - return fmt.Sprintf("%g", arg), true - - case []float64: - buffer := allocStringBuilder() - defer freeStringBuilder(buffer) - lead := '[' - for _, val := range arg { - buffer.WriteRune(lead) - lead = ',' - buffer.WriteString(fmt.Sprintf("%g", val)) - } - buffer.WriteRune(']') - return buffer.String(), true - - case canvasVar: - return arg.name, true - - default: - if n, ok := isInt(arg); ok { - return fmt.Sprintf("%d", n), true - } - } - - ErrorLog("Unsupported agument type") - return "", false -} - -func (brige *wsBrige) callFunc(funcName string, args ...any) bool { - brige.buffer.Reset() - brige.buffer.WriteString(funcName) - brige.buffer.WriteRune('(') - for i, arg := range args { - argText, ok := brige.argToString(arg) - if !ok { - return false - } - - if i > 0 { - brige.buffer.WriteString(", ") - } - brige.buffer.WriteString(argText) - } - brige.buffer.WriteString(");") - - funcText := brige.buffer.String() - if ProtocolInDebugLog { - DebugLog("Run func: " + funcText) - } - if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil { - ErrorLog(err.Error()) - return false - } - return true -} - -func (brige *wsBrige) updateInnerHTML(htmlID, html string) { - brige.callFunc("updateInnerHTML", htmlID, html) -} - -func (brige *wsBrige) appendToInnerHTML(htmlID, html string) { - brige.callFunc("appendToInnerHTML", htmlID, html) -} - -func (brige *wsBrige) updateCSSProperty(htmlID, property, value string) { - if buffer, ok := brige.updateScripts[htmlID]; ok { - buffer.WriteString(`element.style['`) - buffer.WriteString(property) - buffer.WriteString(`'] = '`) - buffer.WriteString(value) - buffer.WriteString("';\n") - } else { - brige.callFunc("updateCSSProperty", htmlID, property, value) - } -} - -func (brige *wsBrige) updateProperty(htmlID, property string, value any) { - if buffer, ok := brige.updateScripts[htmlID]; ok { - if val, ok := brige.argToString(value); ok { - buffer.WriteString(`element.setAttribute('`) - buffer.WriteString(property) - buffer.WriteString(`', `) - buffer.WriteString(val) - buffer.WriteString(");\n") - } - } else { - brige.callFunc("updateProperty", htmlID, property, value) - } -} - -func (brige *wsBrige) removeProperty(htmlID, property string) { - if buffer, ok := brige.updateScripts[htmlID]; ok { - buffer.WriteString(`if (element.hasAttribute('`) - buffer.WriteString(property) - buffer.WriteString(`')) { element.removeAttribute('`) - buffer.WriteString(property) - buffer.WriteString("');}\n") - } else { - brige.callFunc("removeProperty", htmlID, property) - } -} - -func (brige *wsBrige) cavnasStart(htmlID string) { - brige.canvasBuffer.Reset() - brige.canvasBuffer.WriteString(`const ctx = getCanvasContext('`) - brige.canvasBuffer.WriteString(htmlID) - brige.canvasBuffer.WriteString(`');`) -} - -func (brige *wsBrige) callCanvasFunc(funcName string, args ...any) { - brige.canvasBuffer.WriteString("\nctx.") - brige.canvasBuffer.WriteString(funcName) - brige.canvasBuffer.WriteRune('(') - for i, arg := range args { - if i > 0 { - brige.canvasBuffer.WriteString(", ") - } - argText, _ := brige.argToString(arg) - brige.canvasBuffer.WriteString(argText) - } - brige.canvasBuffer.WriteString(");") -} - -func (brige *wsBrige) updateCanvasProperty(property string, value any) { - brige.canvasBuffer.WriteString("\nctx.") - brige.canvasBuffer.WriteString(property) - brige.canvasBuffer.WriteString(" = ") - argText, _ := brige.argToString(value) - brige.canvasBuffer.WriteString(argText) - brige.canvasBuffer.WriteString(";") -} - -func (brige *wsBrige) createCanvasVar(funcName string, args ...any) any { - brige.canvasVarNumber++ - result := canvasVar{name: fmt.Sprintf("v%d", brige.canvasVarNumber)} - brige.canvasBuffer.WriteString("\nvar ") - brige.canvasBuffer.WriteString(result.name) - brige.canvasBuffer.WriteString(" = ctx.") - brige.canvasBuffer.WriteString(funcName) - brige.canvasBuffer.WriteRune('(') - for i, arg := range args { - if i > 0 { - brige.canvasBuffer.WriteString(", ") - } - argText, _ := brige.argToString(arg) - brige.canvasBuffer.WriteString(argText) - } - brige.canvasBuffer.WriteString(");") - return result -} - -func (brige *wsBrige) callCanvasVarFunc(v any, funcName string, args ...any) { - varName, ok := v.(canvasVar) - if !ok { - return - } - brige.canvasBuffer.WriteString("\n") - brige.canvasBuffer.WriteString(varName.name) - brige.canvasBuffer.WriteRune('.') - brige.canvasBuffer.WriteString(funcName) - brige.canvasBuffer.WriteRune('(') - for i, arg := range args { - if i > 0 { - brige.canvasBuffer.WriteString(", ") - } - argText, _ := brige.argToString(arg) - brige.canvasBuffer.WriteString(argText) - } - brige.canvasBuffer.WriteString(");") -} - -func (brige *wsBrige) callCanvasImageFunc(url string, property string, funcName string, args ...any) { - - brige.canvasBuffer.WriteString("\nimg = images.get('") - brige.canvasBuffer.WriteString(url) - brige.canvasBuffer.WriteString("');\nif (img) {\n") - if property != "" { - brige.canvasBuffer.WriteString("ctx.") - brige.canvasBuffer.WriteString(property) - brige.canvasBuffer.WriteString(" = ") - } - brige.canvasBuffer.WriteString("ctx.") - brige.canvasBuffer.WriteString(funcName) - brige.canvasBuffer.WriteString("(img") - for _, arg := range args { - brige.canvasBuffer.WriteString(", ") - argText, _ := brige.argToString(arg) - brige.canvasBuffer.WriteString(argText) - } - brige.canvasBuffer.WriteString(");\n}") -} - -func (brige *wsBrige) cavnasFinish() { - brige.canvasBuffer.WriteString("\n") - script := brige.canvasBuffer.String() - if ProtocolInDebugLog { - DebugLog("Run script:") - DebugLog(script) - } - if brige.conn == nil { - ErrorLog("No connection") - } else if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { - ErrorLog(err.Error()) - } -} - -func (brige *wsBrige) readMessage() (string, bool) { - _, p, err := brige.conn.ReadMessage() - if err != nil { - if !brige.closed { - ErrorLog(err.Error()) - } - return "", false - } - - return string(p), true -} - -func (brige *wsBrige) writeMessage(script string) bool { - if ProtocolInDebugLog { - DebugLog("Run script:") - DebugLog(script) - } - if brige.conn == nil { - ErrorLog("No connection") - return false - } - if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { - ErrorLog(err.Error()) - return false - } - return true -} - -func (brige *wsBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics { - result := TextMetrics{} - - brige.answerMutex.Lock() - answerID := brige.answerID - brige.answerID++ - brige.answerMutex.Unlock() - - answer := make(chan DataObject) - brige.answer[answerID] = answer - - if brige.callFunc("canvasTextMetrics", answerID, htmlID, font, text) { - data := <-answer - result.Width = dataFloatProperty(data, "width") - } - - delete(brige.answer, answerID) - return result -} - -func (brige *wsBrige) htmlPropertyValue(htmlID, name string) string { - brige.answerMutex.Lock() - answerID := brige.answerID - brige.answerID++ - brige.answerMutex.Unlock() - - answer := make(chan DataObject) - brige.answer[answerID] = answer - - if brige.callFunc("getPropertyValue", answerID, htmlID, name) { - data := <-answer - if value, ok := data.PropertyValue("value"); ok { - return value - } - } - - delete(brige.answer, answerID) - return "" -} - -func (brige *wsBrige) answerReceived(answer DataObject) { - if text, ok := answer.PropertyValue("answerID"); ok { - if id, err := strconv.Atoi(text); err == nil { - if chanel, ok := brige.answer[id]; ok { - chanel <- answer - delete(brige.answer, id) - } else { - ErrorLog("Bad answerID = " + text + " (chan not found)") - } - } else { - ErrorLog("Invalid answerID = " + text) - } - } else { - ErrorLog("answerID not found") - } -} - -func (brige *wsBrige) remoteAddr() string { - return brige.conn.RemoteAddr().String() -}