mirror of https://github.com/anoshenko/rui.git
commit
cbca1e7c87
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
||||||
|
# v0.14.0
|
||||||
|
* Added the ability to work without creating a WebSocket. Added NoSocket property to AppParams.
|
||||||
|
* Added SocketAutoClose property to AppParams.
|
||||||
|
* Added the ability to run a timer on the client side. Added StartTimer and StopTimer methods to Session interface.
|
||||||
|
* Added "cell-vertical-self-align", and "cell-horizontal-self-align" properties
|
||||||
|
* Bug fixing
|
||||||
|
|
||||||
|
# v0.13.x
|
||||||
|
* Added NewHandler function
|
||||||
|
* Bug fixing
|
||||||
|
|
||||||
# v0.13.0
|
# v0.13.0
|
||||||
|
|
||||||
* Added SetHotKey function to Session interface
|
* Added SetHotKey function to Session interface
|
||||||
|
|
16
README.md
16
README.md
|
@ -2465,9 +2465,10 @@ The SizeUnit value of type SizeInFraction can be either integer or fractional.
|
||||||
The "grid-row-gap" and "grid-column-gap" SizeUnit properties (GridRowGap and GridColumnGap constants)
|
The "grid-row-gap" and "grid-column-gap" SizeUnit properties (GridRowGap and GridColumnGap constants)
|
||||||
allow you to set the distance between the rows and columns of the container, respectively. The default is 0px.
|
allow you to set the distance between the rows and columns of the container, respectively. The default is 0px.
|
||||||
|
|
||||||
### "cell-vertical-align" property
|
### "cell-vertical-align" and "cell-vertical-self-align" properties
|
||||||
|
|
||||||
The "cell-vertical-align" property (constant CellVerticalAlign) of type int sets the vertical alignment of children within the cell they are occupying. Valid values:
|
The "cell-vertical-align" int property (constant CellVerticalAlign) sets the default vertical alignment of children
|
||||||
|
within the cell they are occupying. Valid values:
|
||||||
|
|
||||||
| Value | Constant | Name | Alignment |
|
| Value | Constant | Name | Alignment |
|
||||||
|:-----:|--------------|-----------|---------------------|
|
|:-----:|--------------|-----------|---------------------|
|
||||||
|
@ -2478,9 +2479,13 @@ The "cell-vertical-align" property (constant CellVerticalAlign) of type int sets
|
||||||
|
|
||||||
The default value is StretchAlign (3)
|
The default value is StretchAlign (3)
|
||||||
|
|
||||||
### "cell-horizontal-align" property
|
The "cell-vertical-self-align" int property (constant CellVerticalAlign) sets the vertical alignment of children
|
||||||
|
within the cell they are occupying. This property should be set not for the grid, but for the children.
|
||||||
|
|
||||||
The "cell-horizontal-align" property (constant CellHorizontalAlign) of type int sets the horizontal alignment of children within the occupied cell. Valid values:
|
### "cell-horizontal-align" and "cell-horizontal-self-align" properties
|
||||||
|
|
||||||
|
The "cell-horizontal-align" int property (constant CellHorizontalSelfAlign) sets the horizontal alignment
|
||||||
|
of children within the occupied cell. Valid values:
|
||||||
|
|
||||||
| Value | Constant | Name | Alignment |
|
| Value | Constant | Name | Alignment |
|
||||||
|:-----:|--------------|-----------|--------------------|
|
|:-----:|--------------|-----------|--------------------|
|
||||||
|
@ -2491,6 +2496,9 @@ The "cell-horizontal-align" property (constant CellHorizontalAlign) of type int
|
||||||
|
|
||||||
The default value is StretchAlign (3)
|
The default value is StretchAlign (3)
|
||||||
|
|
||||||
|
The "cell-horizontal-self-align" int property (constant CellVerticalSelfAlign) sets the horizontal alignment of children
|
||||||
|
within the cell they are occupying. This property should be set not for the grid, but for the children.
|
||||||
|
|
||||||
## ColumnLayout
|
## ColumnLayout
|
||||||
|
|
||||||
ColumnLayout is a container that implements the ViewsContainer interface.
|
ColumnLayout is a container that implements the ViewsContainer interface.
|
||||||
|
|
193
appServer.go
193
appServer.go
|
@ -20,6 +20,9 @@ import (
|
||||||
//go:embed app_socket.js
|
//go:embed app_socket.js
|
||||||
var socketScripts string
|
var socketScripts string
|
||||||
|
|
||||||
|
//go:embed app_post.js
|
||||||
|
var httpPostScripts string
|
||||||
|
|
||||||
func debugLog(text string) {
|
func debugLog(text string) {
|
||||||
log.Println("\033[34m" + text)
|
log.Println("\033[34m" + text)
|
||||||
}
|
}
|
||||||
|
@ -28,11 +31,16 @@ func errorLog(text string) {
|
||||||
log.Println("\033[31m" + text)
|
log.Println("\033[31m" + text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sessionInfo struct {
|
||||||
|
session Session
|
||||||
|
response chan string
|
||||||
|
}
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
params AppParams
|
params AppParams
|
||||||
createContentFunc func(Session) SessionContent
|
createContentFunc func(Session) SessionContent
|
||||||
sessions map[int]Session
|
sessions map[int]sessionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) getStartPage() string {
|
func (app *application) getStartPage() string {
|
||||||
|
@ -40,14 +48,26 @@ func (app *application) getStartPage() string {
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
buffer.WriteString("<!DOCTYPE html>\n<html>\n")
|
buffer.WriteString("<!DOCTYPE html>\n<html>\n")
|
||||||
getStartPage(buffer, app.params, socketScripts)
|
getStartPage(buffer, app.params)
|
||||||
buffer.WriteString("\n</html>")
|
buffer.WriteString("\n</html>")
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) Params() AppParams {
|
||||||
|
params := app.params
|
||||||
|
if params.NoSocket {
|
||||||
|
params.SocketAutoClose = 0
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
func (app *application) Finish() {
|
func (app *application) Finish() {
|
||||||
for _, session := range app.sessions {
|
for _, session := range app.sessions {
|
||||||
session.close()
|
session.session.close()
|
||||||
|
if session.response != nil {
|
||||||
|
close(session.response)
|
||||||
|
session.response = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
@ -69,7 +89,12 @@ func (app *application) nextSessionID() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) removeSession(id int) {
|
func (app *application) removeSession(id int) {
|
||||||
delete(app.sessions, id)
|
if info, ok := app.sessions[id]; ok {
|
||||||
|
if info.response != nil {
|
||||||
|
close(info.response)
|
||||||
|
}
|
||||||
|
delete(app.sessions, id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -79,6 +104,11 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
|
case "POST":
|
||||||
|
if req.URL.Path == "/" {
|
||||||
|
app.postHandler(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
case "GET":
|
case "GET":
|
||||||
switch req.URL.Path {
|
switch req.URL.Path {
|
||||||
case "/":
|
case "/":
|
||||||
|
@ -86,10 +116,20 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
io.WriteString(w, app.getStartPage())
|
io.WriteString(w, app.getStartPage())
|
||||||
|
|
||||||
case "/ws":
|
case "/ws":
|
||||||
if bridge := CreateSocketBridge(w, req); bridge != nil {
|
if bridge := createSocketBridge(w, req); bridge != nil {
|
||||||
go app.socketReader(bridge)
|
go app.socketReader(bridge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "/script.js":
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if app.params.NoSocket {
|
||||||
|
io.WriteString(w, httpPostScripts)
|
||||||
|
} else {
|
||||||
|
io.WriteString(w, socketScripts)
|
||||||
|
}
|
||||||
|
io.WriteString(w, "\n")
|
||||||
|
io.WriteString(w, defaultScripts)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
filename := req.URL.Path[1:]
|
filename := req.URL.Path[1:]
|
||||||
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
||||||
|
@ -104,7 +144,87 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) socketReader(bridge webBridge) {
|
func setSessionIDCookie(w http.ResponseWriter, sessionID int) {
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "session",
|
||||||
|
Value: strconv.Itoa(sessionID),
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
if reqBody, err := io.ReadAll(req.Body); err == nil {
|
||||||
|
message := string(reqBody)
|
||||||
|
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj := ParseDataText(message); obj != nil {
|
||||||
|
var session Session = nil
|
||||||
|
var response chan string = nil
|
||||||
|
|
||||||
|
if cookie, err := req.Cookie("session"); err == nil {
|
||||||
|
sessionID, err := strconv.Atoi(cookie.Value)
|
||||||
|
if err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
} else if info, ok := app.sessions[sessionID]; ok && info.response != nil {
|
||||||
|
response = info.response
|
||||||
|
session = info.session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command := obj.Tag()
|
||||||
|
startSession := false
|
||||||
|
|
||||||
|
if session == nil || command == "startSession" {
|
||||||
|
events := make(chan DataObject, 1024)
|
||||||
|
bridge := createHttpBridge(req)
|
||||||
|
response = bridge.response
|
||||||
|
answer := ""
|
||||||
|
session, answer = app.startSession(obj, events, bridge, response)
|
||||||
|
|
||||||
|
bridge.writeMessage(answer)
|
||||||
|
session.onStart()
|
||||||
|
if command == "session-resume" {
|
||||||
|
session.onResume()
|
||||||
|
}
|
||||||
|
bridge.sendResponse()
|
||||||
|
|
||||||
|
setSessionIDCookie(w, session.ID())
|
||||||
|
startSession = true
|
||||||
|
|
||||||
|
go sessionEventHandler(session, events, bridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !startSession {
|
||||||
|
switch command {
|
||||||
|
case "nop":
|
||||||
|
session.sendResponse()
|
||||||
|
|
||||||
|
case "session-close":
|
||||||
|
session.onFinish()
|
||||||
|
session.App().removeSession(session.ID())
|
||||||
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
|
if !session.handleAnswer(command, obj) {
|
||||||
|
session.addToEventsQueue(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
io.WriteString(w, <-response)
|
||||||
|
for len(response) > 0 {
|
||||||
|
io.WriteString(w, <-response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) socketReader(bridge *wsBridge) {
|
||||||
var session Session
|
var session Session
|
||||||
events := make(chan DataObject, 1024)
|
events := make(chan DataObject, 1024)
|
||||||
|
|
||||||
|
@ -116,7 +236,7 @@ func (app *application) socketReader(bridge webBridge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ProtocolInDebugLog {
|
if ProtocolInDebugLog {
|
||||||
DebugLog(message)
|
DebugLog("🖥️ -> " + message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj := ParseDataText(message); obj != nil {
|
if obj := ParseDataText(message); obj != nil {
|
||||||
|
@ -124,7 +244,7 @@ func (app *application) socketReader(bridge webBridge) {
|
||||||
switch command {
|
switch command {
|
||||||
case "startSession":
|
case "startSession":
|
||||||
answer := ""
|
answer := ""
|
||||||
if session, answer = app.startSession(obj, events, bridge); session != nil {
|
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
|
||||||
if !bridge.writeMessage(answer) {
|
if !bridge.writeMessage(answer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -133,22 +253,18 @@ func (app *application) socketReader(bridge webBridge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "reconnect":
|
case "reconnect":
|
||||||
|
session = nil
|
||||||
if sessionText, ok := obj.PropertyValue("session"); ok {
|
if sessionText, ok := obj.PropertyValue("session"); ok {
|
||||||
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
||||||
if session = app.sessions[sessionID]; session != nil {
|
if info, ok := app.sessions[sessionID]; ok {
|
||||||
|
session = info.session
|
||||||
session.setBridge(events, bridge)
|
session.setBridge(events, bridge)
|
||||||
answer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(answer)
|
|
||||||
|
|
||||||
session.writeInitScript(answer)
|
|
||||||
if !bridge.writeMessage(answer.String()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session.onReconnect()
|
|
||||||
go sessionEventHandler(session, events, bridge)
|
go sessionEventHandler(session, events, bridge)
|
||||||
return
|
session.onReconnect()
|
||||||
|
} else {
|
||||||
|
DebugLogF("Session #%d not exists", sessionID)
|
||||||
}
|
}
|
||||||
DebugLogF("Session #%d not exists", sessionID)
|
|
||||||
} else {
|
} else {
|
||||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||||
}
|
}
|
||||||
|
@ -156,25 +272,31 @@ func (app *application) socketReader(bridge webBridge) {
|
||||||
ErrorLog(`"session" key not found`)
|
ErrorLog(`"session" key not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.writeMessage("restartSession();")
|
if session == nil {
|
||||||
|
/* answer := ""
|
||||||
case "answer":
|
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
|
||||||
session.handleAnswer(obj)
|
if !bridge.writeMessage(answer) {
|
||||||
|
return
|
||||||
case "imageLoaded":
|
}
|
||||||
session.imageManager().imageLoaded(obj, session)
|
session.onStart()
|
||||||
|
go sessionEventHandler(session, events, bridge)
|
||||||
case "imageError":
|
bridge.writeMessage("restartSession();")
|
||||||
session.imageManager().imageLoadError(obj, session)
|
}
|
||||||
|
*/
|
||||||
|
bridge.writeMessage("reloadPage();")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
events <- obj
|
if !session.handleAnswer(command, obj) {
|
||||||
|
events <- obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) {
|
func sessionEventHandler(session Session, events chan DataObject, bridge bridge) {
|
||||||
for {
|
for {
|
||||||
data := <-events
|
data := <-events
|
||||||
|
|
||||||
|
@ -194,7 +316,9 @@ func sessionEventHandler(session Session, events chan DataObject, bridge webBrid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) {
|
func (app *application) startSession(params DataObject, events chan DataObject,
|
||||||
|
bridge bridge, response chan string) (Session, string) {
|
||||||
|
|
||||||
if app.createContentFunc == nil {
|
if app.createContentFunc == nil {
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
@ -205,7 +329,10 @@ func (app *application) startSession(params DataObject, events chan DataObject,
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
app.sessions[session.ID()] = session
|
app.sessions[session.ID()] = sessionInfo{
|
||||||
|
session: session,
|
||||||
|
response: response,
|
||||||
|
}
|
||||||
|
|
||||||
answer := allocStringBuilder()
|
answer := allocStringBuilder()
|
||||||
defer freeStringBuilder(answer)
|
defer freeStringBuilder(answer)
|
||||||
|
@ -229,7 +356,7 @@ var apps = []*application{}
|
||||||
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||||
app := new(application)
|
app := new(application)
|
||||||
app.params = params
|
app.params = params
|
||||||
app.sessions = map[int]Session{}
|
app.sessions = map[int]sessionInfo{}
|
||||||
app.createContentFunc = createContentFunc
|
app.createContentFunc = createContentFunc
|
||||||
apps = append(apps, app)
|
apps = append(apps, app)
|
||||||
|
|
||||||
|
|
21
appWasm.go
21
appWasm.go
|
@ -17,7 +17,7 @@ type wasmApp struct {
|
||||||
params AppParams
|
params AppParams
|
||||||
createContentFunc func(Session) SessionContent
|
createContentFunc func(Session) SessionContent
|
||||||
session Session
|
session Session
|
||||||
bridge webBridge
|
bridge bridge
|
||||||
close chan DataObject
|
close chan DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,12 @@ func (app *wasmApp) Finish() {
|
||||||
app.session.close()
|
app.session.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *wasmApp) Params() AppParams {
|
||||||
|
params := app.params
|
||||||
|
params.SocketAutoClose = 0
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
func debugLog(text string) {
|
func debugLog(text string) {
|
||||||
js.Global().Get("console").Call("log", text)
|
js.Global().Get("console").Call("log", text)
|
||||||
}
|
}
|
||||||
|
@ -44,17 +50,10 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
|
||||||
case "session-close":
|
case "session-close":
|
||||||
app.close <- obj
|
app.close <- obj
|
||||||
|
|
||||||
case "answer":
|
|
||||||
app.session.handleAnswer(obj)
|
|
||||||
|
|
||||||
case "imageLoaded":
|
|
||||||
app.session.imageManager().imageLoaded(obj, app.session)
|
|
||||||
|
|
||||||
case "imageError":
|
|
||||||
app.session.imageManager().imageLoadError(obj, app.session)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
app.session.handleEvent(command, obj)
|
if !app.session.handleAnswer(command, obj) {
|
||||||
|
app.session.handleEvent(command, obj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
async function sendMessage(message) {
|
||||||
|
const response = await fetch('/', {
|
||||||
|
method : 'POST',
|
||||||
|
body : message,
|
||||||
|
"Content-Type" : "text/plain",
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
if (text != "") {
|
||||||
|
window.eval(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
sendMessage( sessionInfo() );
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onfocus = function() {
|
||||||
|
windowFocus = true
|
||||||
|
sendMessage( "session-resume{}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSocket() {
|
||||||
|
}
|
468
app_scripts.js
468
app_scripts.js
File diff suppressed because it is too large
Load Diff
|
@ -1,51 +1,71 @@
|
||||||
var socket
|
let socket
|
||||||
var socketUrl
|
|
||||||
|
|
||||||
function sendMessage(message) {
|
function sendMessage(message) {
|
||||||
if (socket) {
|
if (!socket) {
|
||||||
socket.send(message)
|
createSocket(function() {
|
||||||
|
sendMessage( "reconnect{session=" + sessionID + "}" );
|
||||||
|
if (!windowFocus) {
|
||||||
|
windowFocus = true;
|
||||||
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
|
}
|
||||||
|
socket.send(message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
socket.send(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
function createSocket(onopen) {
|
||||||
socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
||||||
socketUrl += document.location.hostname
|
socketUrl += document.location.hostname
|
||||||
var port = document.location.port
|
const port = document.location.port
|
||||||
if (port) {
|
if (port) {
|
||||||
socketUrl += ":" + port
|
socketUrl += ":" + port
|
||||||
}
|
}
|
||||||
socketUrl += window.location.pathname + "ws"
|
socketUrl += window.location.pathname + "ws"
|
||||||
|
|
||||||
socket = new WebSocket(socketUrl);
|
socket = new WebSocket(socketUrl);
|
||||||
socket.onopen = socketOpen;
|
socket.onopen = onopen;
|
||||||
socket.onclose = socketClose;
|
socket.onclose = onSocketClose;
|
||||||
socket.onerror = socketError;
|
socket.onerror = onSocketError;
|
||||||
socket.onmessage = function(event) {
|
socket.onmessage = function(event) {
|
||||||
window.execScript ? window.execScript(event.data) : window.eval(event.data);
|
window.execScript ? window.execScript(event.data) : window.eval(event.data);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
function socketOpen() {
|
function closeSocket() {
|
||||||
sendMessage( sessionInfo() );
|
if (socket) {
|
||||||
|
socket.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketReopen() {
|
window.onload = createSocket(function() {
|
||||||
|
sendMessage( sessionInfo() );
|
||||||
|
});
|
||||||
|
|
||||||
|
window.onfocus = function() {
|
||||||
|
windowFocus = true
|
||||||
|
if (!socket) {
|
||||||
|
createSocket(function() {
|
||||||
|
sendMessage( "reconnect{session=" + sessionID + "}" );
|
||||||
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSocketReopen() {
|
||||||
sendMessage( "reconnect{session=" + sessionID + "}" );
|
sendMessage( "reconnect{session=" + sessionID + "}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketReconnect() {
|
function socketReconnect() {
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
socket = new WebSocket(socketUrl);
|
createSocket(onSocketReopen);
|
||||||
socket.onopen = socketReopen;
|
|
||||||
socket.onclose = socketClose;
|
|
||||||
socket.onerror = socketError;
|
|
||||||
socket.onmessage = function(event) {
|
|
||||||
window.execScript ? window.execScript(event.data) : window.eval(event.data);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketClose(event) {
|
function onSocketClose(event) {
|
||||||
console.log("socket closed")
|
console.log("socket closed")
|
||||||
socket = null;
|
socket = null;
|
||||||
if (!event.wasClean && windowFocus) {
|
if (!event.wasClean && windowFocus) {
|
||||||
|
@ -53,15 +73,6 @@ function socketClose(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketError(error) {
|
function onSocketError(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onfocus = function(event) {
|
|
||||||
windowFocus = true
|
|
||||||
if (!socket) {
|
|
||||||
socketReconnect()
|
|
||||||
} else {
|
|
||||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -57,9 +57,10 @@ button {
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
padding: 1px;
|
padding: 4px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul:focus {
|
ul:focus {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
|
||||||
window.onfocus = function(event) {
|
window.onfocus = function() {
|
||||||
windowFocus = true
|
windowFocus = true
|
||||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeSocket() {
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ var defaultThemeText string
|
||||||
// Application - app interface
|
// Application - app interface
|
||||||
type Application interface {
|
type Application interface {
|
||||||
Finish()
|
Finish()
|
||||||
|
Params() AppParams
|
||||||
removeSession(id int)
|
removeSession(id int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,23 +25,37 @@ type Application interface {
|
||||||
type AppParams struct {
|
type AppParams struct {
|
||||||
// Title - title of the app window/tab
|
// Title - title of the app window/tab
|
||||||
Title string
|
Title string
|
||||||
|
|
||||||
// TitleColor - background color of the app window/tab (applied only for Safari and Chrome for Android)
|
// TitleColor - background color of the app window/tab (applied only for Safari and Chrome for Android)
|
||||||
TitleColor Color
|
TitleColor Color
|
||||||
|
|
||||||
// Icon - the icon file name
|
// Icon - the icon file name
|
||||||
Icon string
|
Icon string
|
||||||
|
|
||||||
// CertFile - path of a certificate for the server must be provided
|
// CertFile - path of a certificate for the server must be provided
|
||||||
// if neither the Server's TLSConfig.Certificates nor TLSConfig.GetCertificate are populated.
|
// if neither the Server's TLSConfig.Certificates nor TLSConfig.GetCertificate are populated.
|
||||||
// If the certificate is signed by a certificate authority, the certFile should be the concatenation
|
// If the certificate is signed by a certificate authority, the certFile should be the concatenation
|
||||||
// of the server's certificate, any intermediates, and the CA's certificate.
|
// of the server's certificate, any intermediates, and the CA's certificate.
|
||||||
CertFile string
|
CertFile string
|
||||||
|
|
||||||
// KeyFile - path of a private key for the server must be provided
|
// KeyFile - path of a private key for the server must be provided
|
||||||
// if neither the Server's TLSConfig.Certificates nor TLSConfig.GetCertificate are populated.
|
// if neither the Server's TLSConfig.Certificates nor TLSConfig.GetCertificate are populated.
|
||||||
KeyFile string
|
KeyFile string
|
||||||
|
|
||||||
// Redirect80 - if true then the function of redirect from port 80 to 443 is created
|
// Redirect80 - if true then the function of redirect from port 80 to 443 is created
|
||||||
Redirect80 bool
|
Redirect80 bool
|
||||||
|
|
||||||
|
// NoSocket - if true then WebSockets will not be used and information exchange
|
||||||
|
// between the client and the server will be carried out only via http.
|
||||||
|
NoSocket bool
|
||||||
|
|
||||||
|
// SocketAutoClose - time in seconds after which the socket is automatically closed for an inactive session.
|
||||||
|
// The countdown begins after the OnPause event arrives.
|
||||||
|
// If the value of this property is less than or equal to 0 then the socket is not closed.
|
||||||
|
SocketAutoClose int
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) {
|
func getStartPage(buffer *strings.Builder, params AppParams) {
|
||||||
buffer.WriteString(`<head>
|
buffer.WriteString(`<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>`)
|
<title>`)
|
||||||
|
@ -67,11 +82,7 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
|
||||||
buffer.WriteString(appStyles)
|
buffer.WriteString(appStyles)
|
||||||
buffer.WriteString(`</style>
|
buffer.WriteString(`</style>
|
||||||
<style id="ruiAnimations"></style>
|
<style id="ruiAnimations"></style>
|
||||||
<script>
|
<script src="/script.js"></script>
|
||||||
`)
|
|
||||||
buffer.WriteString(defaultScripts)
|
|
||||||
buffer.WriteString(addScripts)
|
|
||||||
buffer.WriteString(`</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body id="body" onkeydown="keyDownEvent(this, event)">
|
<body id="body" onkeydown="keyDownEvent(this, event)">
|
||||||
<div class="ruiRoot" id="ruiRootView"></div>
|
<div class="ruiRoot" id="ruiRootView"></div>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NoRepeat is value of the Repeat property of an background image:
|
// NoRepeat is value of the Repeat property of an background image:
|
||||||
|
@ -61,6 +64,8 @@ const (
|
||||||
// BackgroundElement describes the background element.
|
// BackgroundElement describes the background element.
|
||||||
type BackgroundElement interface {
|
type BackgroundElement interface {
|
||||||
Properties
|
Properties
|
||||||
|
fmt.Stringer
|
||||||
|
stringWriter
|
||||||
cssStyle(session Session) string
|
cssStyle(session Session) string
|
||||||
Tag() string
|
Tag() string
|
||||||
Clone() BackgroundElement
|
Clone() BackgroundElement
|
||||||
|
@ -239,3 +244,20 @@ func (image *backgroundImage) cssStyle(session Session) string {
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
image.writeToBuffer(buffer, indent, image.Tag(), []string{
|
||||||
|
Source,
|
||||||
|
Width,
|
||||||
|
Height,
|
||||||
|
ImageHorizontalAlign,
|
||||||
|
ImageVerticalAlign,
|
||||||
|
backgroundFit,
|
||||||
|
Repeat,
|
||||||
|
Attachment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) String() string {
|
||||||
|
return runStringWriter(image)
|
||||||
|
}
|
||||||
|
|
|
@ -336,3 +336,16 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string {
|
||||||
|
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
||||||
|
Gradient,
|
||||||
|
CenterX,
|
||||||
|
CenterY,
|
||||||
|
Repeating,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundConicGradient) String() string {
|
||||||
|
return runStringWriter(gradient)
|
||||||
|
}
|
||||||
|
|
|
@ -224,6 +224,33 @@ func (point *BackgroundGradientPoint) color(session Session) (Color, bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (point *BackgroundGradientPoint) String() string {
|
||||||
|
result := "black"
|
||||||
|
if point.Color != nil {
|
||||||
|
switch color := point.Color.(type) {
|
||||||
|
case string:
|
||||||
|
result = color
|
||||||
|
|
||||||
|
case Color:
|
||||||
|
result = color.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if point.Pos != nil {
|
||||||
|
switch value := point.Pos.(type) {
|
||||||
|
case string:
|
||||||
|
result += " " + value
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
if value.Type != Auto {
|
||||||
|
result += " " + value.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool {
|
func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool {
|
||||||
|
|
||||||
value, ok := gradient.properties[Gradient]
|
value, ok := gradient.properties[Gradient]
|
||||||
|
@ -370,6 +397,18 @@ func (gradient *backgroundLinearGradient) cssStyle(session Session) string {
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
||||||
|
Gradient,
|
||||||
|
Repeating,
|
||||||
|
Direction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) String() string {
|
||||||
|
return runStringWriter(gradient)
|
||||||
|
}
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) Tag() string {
|
func (gradient *backgroundRadialGradient) Tag() string {
|
||||||
return "radial-gradient"
|
return "radial-gradient"
|
||||||
}
|
}
|
||||||
|
@ -610,3 +649,17 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
|
||||||
|
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
||||||
|
Gradient,
|
||||||
|
CenterX,
|
||||||
|
CenterY,
|
||||||
|
Repeating,
|
||||||
|
RadialGradientShape,
|
||||||
|
RadialGradientRadius,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) String() string {
|
||||||
|
return runStringWriter(gradient)
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ func newColorPicker(session Session) View {
|
||||||
func (picker *colorPickerData) init(session Session) {
|
func (picker *colorPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "ColorPicker"
|
picker.tag = "ColorPicker"
|
||||||
|
picker.hasHtmlDisabled = true
|
||||||
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
||||||
picker.properties[Padding] = Px(0)
|
picker.properties[Padding] = Px(0)
|
||||||
}
|
}
|
||||||
|
@ -153,13 +154,6 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *colorPickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
|
|
|
@ -188,10 +188,6 @@ func (customView *CustomViewData) htmlProperties(self View, buffer *strings.Buil
|
||||||
customView.superView.htmlProperties(customView.superView, buffer)
|
customView.superView.htmlProperties(customView.superView, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
customView.superView.htmlDisabledProperties(customView.superView, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
|
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
|
||||||
customView.superView.cssStyle(customView.superView, builder)
|
customView.superView.cssStyle(customView.superView, builder)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ func newDatePicker(session Session) View {
|
||||||
func (picker *datePickerData) init(session Session) {
|
func (picker *datePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "DatePicker"
|
picker.tag = "DatePicker"
|
||||||
|
picker.hasHtmlDisabled = true
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,13 +304,6 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
|
|
|
@ -39,6 +39,7 @@ func newDropDownList(session Session) View {
|
||||||
func (list *dropDownListData) init(session Session) {
|
func (list *dropDownListData) init(session Session) {
|
||||||
list.viewData.init(session)
|
list.viewData.init(session)
|
||||||
list.tag = "DropDownList"
|
list.tag = "DropDownList"
|
||||||
|
list.hasHtmlDisabled = true
|
||||||
list.items = []string{}
|
list.items = []string{}
|
||||||
list.disabledItems = []any{}
|
list.disabledItems = []any{}
|
||||||
list.dropDownListener = []func(DropDownList, int, int){}
|
list.dropDownListener = []func(DropDownList, int, int){}
|
||||||
|
@ -370,13 +371,6 @@ func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
|
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
list.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
if IsDisabled(list) {
|
|
||||||
buffer.WriteString(`disabled`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
|
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
|
||||||
for _, listener := range list.dropDownListener {
|
for _, listener := range list.dropDownListener {
|
||||||
listener(list, number, old)
|
listener(list, number, old)
|
||||||
|
|
35
editView.go
35
editView.go
|
@ -58,6 +58,7 @@ func newEditView(session Session) View {
|
||||||
|
|
||||||
func (edit *editViewData) init(session Session) {
|
func (edit *editViewData) init(session Session) {
|
||||||
edit.viewData.init(session)
|
edit.viewData.init(session)
|
||||||
|
edit.hasHtmlDisabled = true
|
||||||
edit.textChangeListeners = []func(EditView, string, string){}
|
edit.textChangeListeners = []func(EditView, string, string){}
|
||||||
edit.tag = "EditView"
|
edit.tag = "EditView"
|
||||||
}
|
}
|
||||||
|
@ -466,13 +467,6 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
edit.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
if GetEditViewType(edit) == MultiLineText {
|
if GetEditViewType(edit) == MultiLineText {
|
||||||
buffer.WriteString(GetText(edit))
|
buffer.WriteString(GetText(edit))
|
||||||
|
@ -517,19 +511,30 @@ func GetHint(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||||
view = ViewByID(view, subviewID[0])
|
view = ViewByID(view, subviewID[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session := view.Session()
|
||||||
|
text := ""
|
||||||
if view != nil {
|
if view != nil {
|
||||||
if text, ok := stringProperty(view, Hint, view.Session()); ok {
|
var ok bool
|
||||||
return text
|
text, ok = stringProperty(view, Hint, view.Session())
|
||||||
}
|
if !ok {
|
||||||
if value := valueFromStyle(view, Hint); value != nil {
|
if value := valueFromStyle(view, Hint); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok = value.(string); ok {
|
||||||
if text, ok = view.Session().resolveConstants(text); ok {
|
if text, ok = session.resolveConstants(text); !ok {
|
||||||
return text
|
text = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
if text != "" && !GetNotTranslate(view) {
|
||||||
|
text, _ = session.GetString(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMaxLength returns a maximal length of EditView. If a maximal length is not limited then 0 is returned
|
// GetMaxLength returns a maximal length of EditView. If a maximal length is not limited then 0 is returned
|
||||||
|
|
|
@ -83,6 +83,7 @@ func newFilePicker(session Session) View {
|
||||||
func (picker *filePickerData) init(session Session) {
|
func (picker *filePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "FilePicker"
|
picker.tag = "FilePicker"
|
||||||
|
picker.hasHtmlDisabled = true
|
||||||
picker.files = []FileInfo{}
|
picker.files = []FileInfo{}
|
||||||
picker.loader = map[int]func(FileInfo, []byte){}
|
picker.loader = map[int]func(FileInfo, []byte){}
|
||||||
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
||||||
|
@ -260,13 +261,6 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "fileSelected":
|
case "fileSelected":
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -2,4 +2,6 @@ module github.com/anoshenko/rui
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.0
|
require github.com/gorilla/websocket v1.5.1
|
||||||
|
|
||||||
|
require golang.org/x/net v0.17.0 // indirect
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -1,2 +1,4 @@
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
|
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||||
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
|
|
@ -5,6 +5,44 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CellVerticalAlign is the constant for the "cell-vertical-align" property tag.
|
||||||
|
// The "cell-vertical-align" int property sets the default vertical alignment
|
||||||
|
// of GridLayout children within the cell they are occupying. Valid values:
|
||||||
|
// * TopAlign (0) / "top"
|
||||||
|
// * BottomAlign (1) / "bottom"
|
||||||
|
// * CenterAlign (2) / "center", and
|
||||||
|
// * StretchAlign (2) / "stretch"
|
||||||
|
CellVerticalAlign = "cell-vertical-align"
|
||||||
|
|
||||||
|
// CellHorizontalAlign is the constant for the "cell-horizontal-align" property tag.
|
||||||
|
// The "cell-horizontal-align" int property sets the default horizontal alignment
|
||||||
|
// of GridLayout children within the occupied cell. Valid values:
|
||||||
|
// * LeftAlign (0) / "left"
|
||||||
|
// * RightAlign (1) / "right"
|
||||||
|
// * CenterAlign (2) / "center"
|
||||||
|
// * StretchAlign (3) / "stretch"
|
||||||
|
CellHorizontalAlign = "cell-horizontal-align"
|
||||||
|
|
||||||
|
// CellVerticalSelfAlign is the constant for the "cell-vertical-self-align" property tag.
|
||||||
|
// The "cell-vertical-align" int property sets the vertical alignment of GridLayout children
|
||||||
|
// within the cell they are occupying. The property is set for the child view of GridLayout. Valid values:
|
||||||
|
// * TopAlign (0) / "top"
|
||||||
|
// * BottomAlign (1) / "bottom"
|
||||||
|
// * CenterAlign (2) / "center", and
|
||||||
|
// * StretchAlign (2) / "stretch"
|
||||||
|
CellVerticalSelfAlign = "cell-vertical-self-align"
|
||||||
|
|
||||||
|
// CellHorizontalSelfAlign is the constant for the "cell-horizontal-self-align" property tag.
|
||||||
|
// The "cell-horizontal-self align" int property sets the horizontal alignment of GridLayout children
|
||||||
|
// within the occupied cell. The property is set for the child view of GridLayout. Valid values:
|
||||||
|
// * LeftAlign (0) / "left"
|
||||||
|
// * RightAlign (1) / "right"
|
||||||
|
// * CenterAlign (2) / "center"
|
||||||
|
// * StretchAlign (3) / "stretch"
|
||||||
|
CellHorizontalSelfAlign = "cell-horizontal-self-align"
|
||||||
|
)
|
||||||
|
|
||||||
// GridLayout - grid-container of View
|
// GridLayout - grid-container of View
|
||||||
type GridLayout interface {
|
type GridLayout interface {
|
||||||
ViewsContainer
|
ViewsContainer
|
||||||
|
|
5
image.go
5
image.go
|
@ -80,10 +80,11 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
|
||||||
manager.images[url] = image
|
manager.images[url] = image
|
||||||
|
|
||||||
session.callFunc("loadImage", url)
|
session.callFunc("loadImage", url)
|
||||||
|
session.sendResponse()
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *imageManager) imageLoaded(obj DataObject, session Session) {
|
func (manager *imageManager) imageLoaded(obj DataObject) {
|
||||||
if manager.images == nil {
|
if manager.images == nil {
|
||||||
manager.images = make(map[string]*imageData)
|
manager.images = make(map[string]*imageData)
|
||||||
return
|
return
|
||||||
|
@ -109,7 +110,7 @@ func (manager *imageManager) imageLoaded(obj DataObject, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *imageManager) imageLoadError(obj DataObject, session Session) {
|
func (manager *imageManager) imageLoadError(obj DataObject) {
|
||||||
if manager.images == nil {
|
if manager.images == nil {
|
||||||
manager.images = make(map[string]*imageData)
|
manager.images = make(map[string]*imageData)
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,8 +2,13 @@ package rui
|
||||||
|
|
||||||
// ListAdapter - the list data source
|
// ListAdapter - the list data source
|
||||||
type ListAdapter interface {
|
type ListAdapter interface {
|
||||||
|
// ListSize returns the number of elements in the list
|
||||||
ListSize() int
|
ListSize() int
|
||||||
|
|
||||||
|
// ListItem creates a View of a list item at the given index
|
||||||
ListItem(index int, session Session) View
|
ListItem(index int, session Session) View
|
||||||
|
|
||||||
|
// IsListItemEnabled returns the status (enabled/disabled) of a list item at the given index
|
||||||
IsListItemEnabled(index int) bool
|
IsListItemEnabled(index int) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,16 +7,22 @@ import (
|
||||||
const (
|
const (
|
||||||
// TopDownOrientation - subviews are arranged from top to bottom. Synonym of VerticalOrientation
|
// TopDownOrientation - subviews are arranged from top to bottom. Synonym of VerticalOrientation
|
||||||
TopDownOrientation = 0
|
TopDownOrientation = 0
|
||||||
|
|
||||||
// StartToEndOrientation - subviews are arranged from left to right. Synonym of HorizontalOrientation
|
// StartToEndOrientation - subviews are arranged from left to right. Synonym of HorizontalOrientation
|
||||||
StartToEndOrientation = 1
|
StartToEndOrientation = 1
|
||||||
|
|
||||||
// BottomUpOrientation - subviews are arranged from bottom to top
|
// BottomUpOrientation - subviews are arranged from bottom to top
|
||||||
BottomUpOrientation = 2
|
BottomUpOrientation = 2
|
||||||
|
|
||||||
// EndToStartOrientation - subviews are arranged from right to left
|
// EndToStartOrientation - subviews are arranged from right to left
|
||||||
EndToStartOrientation = 3
|
EndToStartOrientation = 3
|
||||||
|
|
||||||
// ListWrapOff - subviews are scrolled and "true" if a new row/column starts
|
// ListWrapOff - subviews are scrolled and "true" if a new row/column starts
|
||||||
ListWrapOff = 0
|
ListWrapOff = 0
|
||||||
|
|
||||||
// ListWrapOn - the new row/column starts at bottom/right
|
// ListWrapOn - the new row/column starts at bottom/right
|
||||||
ListWrapOn = 1
|
ListWrapOn = 1
|
||||||
|
|
||||||
// ListWrapReverse - the new row/column starts at top/left
|
// ListWrapReverse - the new row/column starts at top/left
|
||||||
ListWrapReverse = 2
|
ListWrapReverse = 2
|
||||||
)
|
)
|
||||||
|
|
37
listView.go
37
listView.go
|
@ -11,20 +11,25 @@ const (
|
||||||
// The "list-item-clicked" event occurs when the user clicks on an item in the list.
|
// The "list-item-clicked" event occurs when the user clicks on an item in the list.
|
||||||
// The main listener format: func(ListView, int), where the second argument is the item index.
|
// The main listener format: func(ListView, int), where the second argument is the item index.
|
||||||
ListItemClickedEvent = "list-item-clicked"
|
ListItemClickedEvent = "list-item-clicked"
|
||||||
|
|
||||||
// ListItemSelectedEvent is the constant for "list-item-selected" property tag.
|
// ListItemSelectedEvent is the constant for "list-item-selected" property tag.
|
||||||
// The "list-item-selected" event occurs when a list item becomes selected.
|
// The "list-item-selected" event occurs when a list item becomes selected.
|
||||||
// The main listener format: func(ListView, int), where the second argument is the item index.
|
// The main listener format: func(ListView, int), where the second argument is the item index.
|
||||||
ListItemSelectedEvent = "list-item-selected"
|
ListItemSelectedEvent = "list-item-selected"
|
||||||
|
|
||||||
// ListItemCheckedEvent is the constant for "list-item-checked" property tag.
|
// ListItemCheckedEvent is the constant for "list-item-checked" property tag.
|
||||||
// The "list-item-checked" event occurs when a list item checkbox becomes checked/unchecked.
|
// The "list-item-checked" event occurs when a list item checkbox becomes checked/unchecked.
|
||||||
// The main listener format: func(ListView, []int), where the second argument is the array of checked item indexes.
|
// The main listener format: func(ListView, []int), where the second argument is the array of checked item indexes.
|
||||||
ListItemCheckedEvent = "list-item-checked"
|
ListItemCheckedEvent = "list-item-checked"
|
||||||
|
|
||||||
// ListItemStyle is the constant for "list-item-style" property tag.
|
// ListItemStyle is the constant for "list-item-style" property tag.
|
||||||
// The "list-item-style" string property defines the style of an unselected item
|
// The "list-item-style" string property defines the style of an unselected item
|
||||||
ListItemStyle = "list-item-style"
|
ListItemStyle = "list-item-style"
|
||||||
|
|
||||||
// CurrentStyle is the constant for "current-style" property tag.
|
// CurrentStyle is the constant for "current-style" property tag.
|
||||||
// The "current-style" string property defines the style of the selected item when the ListView is focused.
|
// The "current-style" string property defines the style of the selected item when the ListView is focused.
|
||||||
CurrentStyle = "current-style"
|
CurrentStyle = "current-style"
|
||||||
|
|
||||||
// CurrentInactiveStyle is the constant for "current-inactive-style" property tag.
|
// CurrentInactiveStyle is the constant for "current-inactive-style" property tag.
|
||||||
// The "current-inactive-style" string property defines the style of the selected item when the ListView is unfocused.
|
// The "current-inactive-style" string property defines the style of the selected item when the ListView is unfocused.
|
||||||
CurrentInactiveStyle = "current-inactive-style"
|
CurrentInactiveStyle = "current-inactive-style"
|
||||||
|
@ -589,7 +594,7 @@ func (listView *listViewData) getItemFrames() []Frame {
|
||||||
return listView.itemFrame
|
return listView.itemFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) {
|
func (listView *listViewData) itemAlign(buffer *strings.Builder) {
|
||||||
values := enumProperties[ItemHorizontalAlign].cssValues
|
values := enumProperties[ItemHorizontalAlign].cssValues
|
||||||
if hAlign := GetListItemHorizontalAlign(listView); hAlign >= 0 && hAlign < len(values) {
|
if hAlign := GetListItemHorizontalAlign(listView); hAlign >= 0 && hAlign < len(values) {
|
||||||
buffer.WriteString(" justify-items: ")
|
buffer.WriteString(" justify-items: ")
|
||||||
|
@ -605,7 +610,7 @@ func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) itemSize(self View, buffer *strings.Builder) {
|
func (listView *listViewData) itemSize(buffer *strings.Builder) {
|
||||||
if itemWidth := GetListItemWidth(listView); itemWidth.Type != Auto {
|
if itemWidth := GetListItemWidth(listView); itemWidth.Type != Auto {
|
||||||
buffer.WriteString(` min-width: `)
|
buffer.WriteString(` min-width: `)
|
||||||
buffer.WriteString(itemWidth.cssString("", listView.Session()))
|
buffer.WriteString(itemWidth.cssString("", listView.Session()))
|
||||||
|
@ -619,14 +624,14 @@ func (listView *listViewData) itemSize(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) getDivs(self View, checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
|
func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
|
||||||
session := listView.Session()
|
session := listView.Session()
|
||||||
|
|
||||||
contentBuilder := allocStringBuilder()
|
contentBuilder := allocStringBuilder()
|
||||||
defer freeStringBuilder(contentBuilder)
|
defer freeStringBuilder(contentBuilder)
|
||||||
|
|
||||||
contentBuilder.WriteString(`<div style="display: grid;`)
|
contentBuilder.WriteString(`<div style="display: grid;`)
|
||||||
listView.itemAlign(self, contentBuilder)
|
listView.itemAlign(contentBuilder)
|
||||||
|
|
||||||
onDivBuilder := allocStringBuilder()
|
onDivBuilder := allocStringBuilder()
|
||||||
defer freeStringBuilder(onDivBuilder)
|
defer freeStringBuilder(onDivBuilder)
|
||||||
|
@ -681,7 +686,7 @@ func (listView *listViewData) getDivs(self View, checkbox, hCheckboxAlign, vChec
|
||||||
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
|
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) checkboxItemDiv(self View, checkbox, hCheckboxAlign, vCheckboxAlign int) string {
|
func (listView *listViewData) checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign int) string {
|
||||||
itemStyleBuilder := allocStringBuilder()
|
itemStyleBuilder := allocStringBuilder()
|
||||||
defer freeStringBuilder(itemStyleBuilder)
|
defer freeStringBuilder(itemStyleBuilder)
|
||||||
|
|
||||||
|
@ -760,15 +765,15 @@ func (listView *listViewData) currentInactiveStyle() string {
|
||||||
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
|
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builder, checkbox int) {
|
func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox int) {
|
||||||
count := listView.adapter.ListSize()
|
count := listView.adapter.ListSize()
|
||||||
listViewID := listView.htmlID()
|
listViewID := listView.htmlID()
|
||||||
|
|
||||||
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
|
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
|
||||||
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
|
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
|
||||||
|
|
||||||
itemDiv := listView.checkboxItemDiv(self, checkbox, hCheckboxAlign, vCheckboxAlign)
|
itemDiv := listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
onDiv, offDiv, contentDiv := listView.getDivs(self, checkbox, hCheckboxAlign, vCheckboxAlign)
|
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
|
|
||||||
current := GetCurrent(listView)
|
current := GetCurrent(listView)
|
||||||
checkedItems := GetListViewCheckedItems(listView)
|
checkedItems := GetListViewCheckedItems(listView)
|
||||||
|
@ -784,7 +789,7 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
|
||||||
buffer.WriteString(listView.currentInactiveStyle())
|
buffer.WriteString(listView.currentInactiveStyle())
|
||||||
}
|
}
|
||||||
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
|
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
|
||||||
listView.itemSize(self, buffer)
|
listView.itemSize(buffer)
|
||||||
if !listView.adapter.IsListItemEnabled(i) {
|
if !listView.adapter.IsListItemEnabled(i) {
|
||||||
buffer.WriteString(`" data-disabled="1`)
|
buffer.WriteString(`" data-disabled="1`)
|
||||||
}
|
}
|
||||||
|
@ -815,7 +820,7 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Builder) {
|
func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) {
|
||||||
count := listView.adapter.ListSize()
|
count := listView.adapter.ListSize()
|
||||||
listViewID := listView.htmlID()
|
listViewID := listView.htmlID()
|
||||||
|
|
||||||
|
@ -824,8 +829,8 @@ func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Bu
|
||||||
|
|
||||||
itemStyleBuilder.WriteString(`data-left="0" data-top="0" data-width="0" data-height="0" style="max-width: 100%; max-height: 100%; display: grid;`)
|
itemStyleBuilder.WriteString(`data-left="0" data-top="0" data-width="0" data-height="0" style="max-width: 100%; max-height: 100%; display: grid;`)
|
||||||
|
|
||||||
listView.itemAlign(self, itemStyleBuilder)
|
listView.itemAlign(itemStyleBuilder)
|
||||||
listView.itemSize(self, itemStyleBuilder)
|
listView.itemSize(itemStyleBuilder)
|
||||||
|
|
||||||
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
|
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
|
||||||
itemStyle := itemStyleBuilder.String()
|
itemStyle := itemStyleBuilder.String()
|
||||||
|
@ -865,12 +870,12 @@ func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
|
||||||
checkbox := GetListViewCheckbox(listView)
|
checkbox := GetListViewCheckbox(listView)
|
||||||
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
|
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
|
||||||
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
|
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
|
||||||
onDiv, offDiv, contentDiv := listView.getDivs(listView, checkbox, hCheckboxAlign, vCheckboxAlign)
|
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
buffer.WriteString(listView.checkboxItemDiv(listView, checkbox, hCheckboxAlign, vCheckboxAlign))
|
buffer.WriteString(listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign))
|
||||||
if checked {
|
if checked {
|
||||||
buffer.WriteString(onDiv)
|
buffer.WriteString(onDiv)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1061,9 +1066,9 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
||||||
checkbox := GetListViewCheckbox(listView)
|
checkbox := GetListViewCheckbox(listView)
|
||||||
if checkbox == NoneCheckbox {
|
if checkbox == NoneCheckbox {
|
||||||
listView.noneCheckboxSubviews(self, buffer)
|
listView.noneCheckboxSubviews(buffer)
|
||||||
} else {
|
} else {
|
||||||
listView.checkboxSubviews(self, buffer, checkbox)
|
listView.checkboxSubviews(buffer, checkbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(`</div>`)
|
buffer.WriteString(`</div>`)
|
||||||
|
|
|
@ -13,15 +13,18 @@ const (
|
||||||
// to control audio/video playback, including volume, seeking, and pause/resume playback.
|
// to control audio/video playback, including volume, seeking, and pause/resume playback.
|
||||||
// Its default value is false.
|
// Its default value is false.
|
||||||
Controls = "controls"
|
Controls = "controls"
|
||||||
|
|
||||||
// Loop is the constant for the "loop" property tag.
|
// Loop is the constant for the "loop" property tag.
|
||||||
// If the "loop" bool property is "true", the audio/video player will automatically seek back
|
// If the "loop" bool property is "true", the audio/video player will automatically seek back
|
||||||
// to the start upon reaching the end of the audio/video.
|
// to the start upon reaching the end of the audio/video.
|
||||||
// Its default value is false.
|
// Its default value is false.
|
||||||
Loop = "loop"
|
Loop = "loop"
|
||||||
|
|
||||||
// Muted is the constant for the "muted" property tag.
|
// Muted is the constant for the "muted" property tag.
|
||||||
// The "muted" bool property indicates whether the audio/video will be initially silenced.
|
// The "muted" bool property indicates whether the audio/video will be initially silenced.
|
||||||
// Its default value is false.
|
// Its default value is false.
|
||||||
Muted = "muted"
|
Muted = "muted"
|
||||||
|
|
||||||
// Preload is the constant for the "preload" property tag.
|
// Preload is the constant for the "preload" property tag.
|
||||||
// The "preload" int property is intended to provide a hint to the browser about what
|
// The "preload" int property is intended to provide a hint to the browser about what
|
||||||
// the author thinks will lead to the best user experience. It may have one of the following values:
|
// the author thinks will lead to the best user experience. It may have one of the following values:
|
||||||
|
@ -32,72 +35,94 @@ const (
|
||||||
// AbortEvent is the constant for the "abort-event" property tag.
|
// AbortEvent is the constant for the "abort-event" property tag.
|
||||||
// The "abort-event" event fired when the resource was not fully loaded, but not as the result of an error.
|
// The "abort-event" event fired when the resource was not fully loaded, but not as the result of an error.
|
||||||
AbortEvent = "abort-event"
|
AbortEvent = "abort-event"
|
||||||
|
|
||||||
// CanPlayEvent is the constant for the "can-play-event" property tag.
|
// CanPlayEvent is the constant for the "can-play-event" property tag.
|
||||||
// The "can-play-event" event occurs when the browser can play the media, but estimates that not enough data has been
|
// The "can-play-event" event occurs when the browser can play the media, but estimates that not enough data has been
|
||||||
// loaded to play the media up to its end without having to stop for further buffering of content.
|
// loaded to play the media up to its end without having to stop for further buffering of content.
|
||||||
CanPlayEvent = "can-play-event"
|
CanPlayEvent = "can-play-event"
|
||||||
|
|
||||||
// CanPlayThroughEvent is the constant for the "can-play-through-event" property tag.
|
// CanPlayThroughEvent is the constant for the "can-play-through-event" property tag.
|
||||||
// The "can-play-through-event" event occurs when the browser estimates it can play the media up
|
// The "can-play-through-event" event occurs when the browser estimates it can play the media up
|
||||||
// to its end without stopping for content buffering.
|
// to its end without stopping for content buffering.
|
||||||
CanPlayThroughEvent = "can-play-through-event"
|
CanPlayThroughEvent = "can-play-through-event"
|
||||||
|
|
||||||
// CompleteEvent is the constant for the "complete-event" property tag.
|
// CompleteEvent is the constant for the "complete-event" property tag.
|
||||||
// The "complete-event" event occurs when the rendering of an OfflineAudioContext is terminated.
|
// The "complete-event" event occurs when the rendering of an OfflineAudioContext is terminated.
|
||||||
CompleteEvent = "complete-event"
|
CompleteEvent = "complete-event"
|
||||||
|
|
||||||
// DurationChangedEvent is the constant for the "duration-changed-event" property tag.
|
// DurationChangedEvent is the constant for the "duration-changed-event" property tag.
|
||||||
// The "duration-changed-event" event occurs when the duration attribute has been updated.
|
// The "duration-changed-event" event occurs when the duration attribute has been updated.
|
||||||
DurationChangedEvent = "duration-changed-event"
|
DurationChangedEvent = "duration-changed-event"
|
||||||
|
|
||||||
// EmptiedEvent is the constant for the "emptied-event" property tag.
|
// EmptiedEvent is the constant for the "emptied-event" property tag.
|
||||||
// The "emptied-event" event occurs when the media has become empty; for example, this event is sent if the media has already been loaded
|
// The "emptied-event" event occurs when the media has become empty; for example, this event is sent if the media has already been loaded
|
||||||
// (or partially loaded), and the HTMLMediaElement.load method is called to reload it.
|
// (or partially loaded), and the HTMLMediaElement.load method is called to reload it.
|
||||||
EmptiedEvent = "emptied-event"
|
EmptiedEvent = "emptied-event"
|
||||||
|
|
||||||
// EndedEvent is the constant for the "ended-event" property tag.
|
// EndedEvent is the constant for the "ended-event" property tag.
|
||||||
// The "ended-event" event occurs when the playback has stopped because the end of the media was reached.
|
// The "ended-event" event occurs when the playback has stopped because the end of the media was reached.
|
||||||
EndedEvent = "ended-event"
|
EndedEvent = "ended-event"
|
||||||
|
|
||||||
// LoadedDataEvent is the constant for the "loaded-data-event" property tag.
|
// LoadedDataEvent is the constant for the "loaded-data-event" property tag.
|
||||||
// The "loaded-data-event" event occurs when the first frame of the media has finished loading.
|
// The "loaded-data-event" event occurs when the first frame of the media has finished loading.
|
||||||
LoadedDataEvent = "loaded-data-event"
|
LoadedDataEvent = "loaded-data-event"
|
||||||
|
|
||||||
// LoadedMetadataEvent is the constant for the "loaded-metadata-event" property tag.
|
// LoadedMetadataEvent is the constant for the "loaded-metadata-event" property tag.
|
||||||
// The "loaded-metadata-event" event occurs when the metadata has been loaded.
|
// The "loaded-metadata-event" event occurs when the metadata has been loaded.
|
||||||
LoadedMetadataEvent = "loaded-metadata-event"
|
LoadedMetadataEvent = "loaded-metadata-event"
|
||||||
|
|
||||||
// LoadStartEvent is the constant for the "load-start-event" property tag.
|
// LoadStartEvent is the constant for the "load-start-event" property tag.
|
||||||
// The "load-start-event" event is fired when the browser has started to load a resource.
|
// The "load-start-event" event is fired when the browser has started to load a resource.
|
||||||
LoadStartEvent = "load-start-event"
|
LoadStartEvent = "load-start-event"
|
||||||
|
|
||||||
// PauseEvent is the constant for the "pause-event" property tag.
|
// PauseEvent is the constant for the "pause-event" property tag.
|
||||||
// The "pause-event" event occurs when the playback has been paused.
|
// The "pause-event" event occurs when the playback has been paused.
|
||||||
PauseEvent = "pause-event"
|
PauseEvent = "pause-event"
|
||||||
|
|
||||||
// PlayEvent is the constant for the "play-event" property tag.
|
// PlayEvent is the constant for the "play-event" property tag.
|
||||||
// The "play-event" event occurs when the playback has begun.
|
// The "play-event" event occurs when the playback has begun.
|
||||||
PlayEvent = "play-event"
|
PlayEvent = "play-event"
|
||||||
|
|
||||||
// PlayingEvent is the constant for the "playing-event" property tag.
|
// PlayingEvent is the constant for the "playing-event" property tag.
|
||||||
// The "playing-event" event occurs when the playback is ready to start after having been paused or delayed due to lack of data.
|
// The "playing-event" event occurs when the playback is ready to start after having been paused or delayed due to lack of data.
|
||||||
PlayingEvent = "playing-event"
|
PlayingEvent = "playing-event"
|
||||||
|
|
||||||
// ProgressEvent is the constant for the "progress-event" property tag.
|
// ProgressEvent is the constant for the "progress-event" property tag.
|
||||||
// The "progress-event" event is fired periodically as the browser loads a resource.
|
// The "progress-event" event is fired periodically as the browser loads a resource.
|
||||||
ProgressEvent = "progress-event"
|
ProgressEvent = "progress-event"
|
||||||
|
|
||||||
// RateChangeEvent is the constant for the "rate-change-event" property tag.
|
// RateChangeEvent is the constant for the "rate-change-event" property tag.
|
||||||
// The "rate-change-event" event occurs when the playback rate has changed.
|
// The "rate-change-event" event occurs when the playback rate has changed.
|
||||||
RateChangedEvent = "rate-changed-event"
|
RateChangedEvent = "rate-changed-event"
|
||||||
|
|
||||||
// SeekedEvent is the constant for the "seeked-event" property tag.
|
// SeekedEvent is the constant for the "seeked-event" property tag.
|
||||||
// The "seeked-event" event occurs when a seek operation completed.
|
// The "seeked-event" event occurs when a seek operation completed.
|
||||||
SeekedEvent = "seeked-event"
|
SeekedEvent = "seeked-event"
|
||||||
|
|
||||||
// SeekingEvent is the constant for the "seeking-event" property tag.
|
// SeekingEvent is the constant for the "seeking-event" property tag.
|
||||||
// The "seeking-event" event occurs when a seek operation began.
|
// The "seeking-event" event occurs when a seek operation began.
|
||||||
SeekingEvent = "seeking-event"
|
SeekingEvent = "seeking-event"
|
||||||
|
|
||||||
// StalledEvent is the constant for the "stalled-event" property tag.
|
// StalledEvent is the constant for the "stalled-event" property tag.
|
||||||
// The "stalled-event" event occurs when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming.
|
// The "stalled-event" event occurs when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming.
|
||||||
StalledEvent = "stalled-event"
|
StalledEvent = "stalled-event"
|
||||||
|
|
||||||
// SuspendEvent is the constant for the "suspend-event" property tag.
|
// SuspendEvent is the constant for the "suspend-event" property tag.
|
||||||
// The "suspend-event" event occurs when the media data loading has been suspended.
|
// The "suspend-event" event occurs when the media data loading has been suspended.
|
||||||
SuspendEvent = "suspend-event"
|
SuspendEvent = "suspend-event"
|
||||||
|
|
||||||
// TimeUpdateEvent is the constant for the "time-update-event" property tag.
|
// TimeUpdateEvent is the constant for the "time-update-event" property tag.
|
||||||
// The "time-update-event" event occurs when the time indicated by the currentTime attribute has been updated.
|
// The "time-update-event" event occurs when the time indicated by the currentTime attribute has been updated.
|
||||||
TimeUpdateEvent = "time-update-event"
|
TimeUpdateEvent = "time-update-event"
|
||||||
|
|
||||||
// VolumeChangedEvent is the constant for the "volume-change-event" property tag.
|
// VolumeChangedEvent is the constant for the "volume-change-event" property tag.
|
||||||
// The "volume-change-event" event occurs when the volume has changed.
|
// The "volume-change-event" event occurs when the volume has changed.
|
||||||
VolumeChangedEvent = "volume-changed-event"
|
VolumeChangedEvent = "volume-changed-event"
|
||||||
|
|
||||||
// WaitingEvent is the constant for the "waiting-event" property tag.
|
// WaitingEvent is the constant for the "waiting-event" property tag.
|
||||||
// The "waiting-event" event occurs when the playback has stopped because of a temporary lack of data
|
// The "waiting-event" event occurs when the playback has stopped because of a temporary lack of data
|
||||||
WaitingEvent = "waiting-event"
|
WaitingEvent = "waiting-event"
|
||||||
|
|
||||||
// PlayerErrorEvent is the constant for the "player-error-event" property tag.
|
// PlayerErrorEvent is the constant for the "player-error-event" property tag.
|
||||||
// The "player-error-event" event is fired when the resource could not be loaded due to an error
|
// The "player-error-event" event is fired when the resource could not be loaded due to an error
|
||||||
// (for example, a network connectivity problem).
|
// (for example, a network connectivity problem).
|
||||||
|
@ -105,51 +130,68 @@ const (
|
||||||
|
|
||||||
// PreloadNone - value of the view "preload" property: indicates that the audio/video should not be preloaded.
|
// PreloadNone - value of the view "preload" property: indicates that the audio/video should not be preloaded.
|
||||||
PreloadNone = 0
|
PreloadNone = 0
|
||||||
|
|
||||||
// PreloadMetadata - value of the view "preload" property: indicates that only audio/video metadata (e.g. length) is fetched.
|
// PreloadMetadata - value of the view "preload" property: indicates that only audio/video metadata (e.g. length) is fetched.
|
||||||
PreloadMetadata = 1
|
PreloadMetadata = 1
|
||||||
|
|
||||||
// PreloadAuto - value of the view "preload" property: indicates that the whole audio file can be downloaded,
|
// PreloadAuto - value of the view "preload" property: indicates that the whole audio file can be downloaded,
|
||||||
// even if the user is not expected to use it.
|
// even if the user is not expected to use it.
|
||||||
PreloadAuto = 2
|
PreloadAuto = 2
|
||||||
|
|
||||||
// PlayerErrorUnknown - MediaPlayer error code: An unknown error.
|
// PlayerErrorUnknown - MediaPlayer error code: An unknown error.
|
||||||
PlayerErrorUnknown = 0
|
PlayerErrorUnknown = 0
|
||||||
|
|
||||||
// PlayerErrorAborted - MediaPlayer error code: The fetching of the associated resource was aborted by the user's request.
|
// PlayerErrorAborted - MediaPlayer error code: The fetching of the associated resource was aborted by the user's request.
|
||||||
PlayerErrorAborted = 1
|
PlayerErrorAborted = 1
|
||||||
|
|
||||||
// PlayerErrorNetwork - MediaPlayer error code: Some kind of network error occurred which prevented the media
|
// PlayerErrorNetwork - MediaPlayer error code: Some kind of network error occurred which prevented the media
|
||||||
// from being successfully fetched, despite having previously been available.
|
// from being successfully fetched, despite having previously been available.
|
||||||
PlayerErrorNetwork = 2
|
PlayerErrorNetwork = 2
|
||||||
|
|
||||||
// PlayerErrorDecode - MediaPlayer error code: Despite having previously been determined to be usable,
|
// PlayerErrorDecode - MediaPlayer error code: Despite having previously been determined to be usable,
|
||||||
// an error occurred while trying to decode the media resource, resulting in an error.
|
// an error occurred while trying to decode the media resource, resulting in an error.
|
||||||
PlayerErrorDecode = 3
|
PlayerErrorDecode = 3
|
||||||
|
|
||||||
// PlayerErrorSourceNotSupported - MediaPlayer error code: The associated resource or media provider object has been found to be unsuitable.
|
// PlayerErrorSourceNotSupported - MediaPlayer error code: The associated resource or media provider object has been found to be unsuitable.
|
||||||
PlayerErrorSourceNotSupported = 4
|
PlayerErrorSourceNotSupported = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaPlayer interface {
|
type MediaPlayer interface {
|
||||||
View
|
View
|
||||||
|
|
||||||
// Play attempts to begin playback of the media.
|
// Play attempts to begin playback of the media.
|
||||||
Play()
|
Play()
|
||||||
|
|
||||||
// Pause will pause playback of the media, if the media is already in a paused state this method will have no effect.
|
// Pause will pause playback of the media, if the media is already in a paused state this method will have no effect.
|
||||||
Pause()
|
Pause()
|
||||||
|
|
||||||
// SetCurrentTime sets the current playback time in seconds.
|
// SetCurrentTime sets the current playback time in seconds.
|
||||||
SetCurrentTime(seconds float64)
|
SetCurrentTime(seconds float64)
|
||||||
|
|
||||||
// CurrentTime returns the current playback time in seconds.
|
// CurrentTime returns the current playback time in seconds.
|
||||||
CurrentTime() float64
|
CurrentTime() float64
|
||||||
|
|
||||||
// Duration returns the value indicating the total duration of the media in seconds.
|
// Duration returns the value indicating the total duration of the media in seconds.
|
||||||
// If no media data is available, the returned value is NaN.
|
// If no media data is available, the returned value is NaN.
|
||||||
Duration() float64
|
Duration() float64
|
||||||
|
|
||||||
// SetPlaybackRate sets the rate at which the media is being played back. This is used to implement user controls
|
// SetPlaybackRate sets the rate at which the media is being played back. This is used to implement user controls
|
||||||
// for fast forward, slow motion, and so forth. The normal playback rate is multiplied by this value to obtain
|
// for fast forward, slow motion, and so forth. The normal playback rate is multiplied by this value to obtain
|
||||||
// the current rate, so a value of 1.0 indicates normal speed.
|
// the current rate, so a value of 1.0 indicates normal speed.
|
||||||
SetPlaybackRate(rate float64)
|
SetPlaybackRate(rate float64)
|
||||||
|
|
||||||
// PlaybackRate returns the rate at which the media is being played back.
|
// PlaybackRate returns the rate at which the media is being played back.
|
||||||
PlaybackRate() float64
|
PlaybackRate() float64
|
||||||
|
|
||||||
// SetVolume sets the audio volume, from 0.0 (silent) to 1.0 (loudest).
|
// SetVolume sets the audio volume, from 0.0 (silent) to 1.0 (loudest).
|
||||||
SetVolume(volume float64)
|
SetVolume(volume float64)
|
||||||
|
|
||||||
// Volume returns the audio volume, from 0.0 (silent) to 1.0 (loudest).
|
// Volume returns the audio volume, from 0.0 (silent) to 1.0 (loudest).
|
||||||
Volume() float64
|
Volume() float64
|
||||||
|
|
||||||
// IsEnded function tells whether the media element is ended.
|
// IsEnded function tells whether the media element is ended.
|
||||||
IsEnded() bool
|
IsEnded() bool
|
||||||
|
|
||||||
// IsPaused function tells whether the media element is paused.
|
// IsPaused function tells whether the media element is paused.
|
||||||
IsPaused() bool
|
IsPaused() bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,24 +82,32 @@ const (
|
||||||
|
|
||||||
// PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state
|
// PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state
|
||||||
PrimaryMouseButton = 0
|
PrimaryMouseButton = 0
|
||||||
|
|
||||||
// AuxiliaryMouseButton is a number of the auxiliary pressed button, usually the wheel button
|
// AuxiliaryMouseButton is a number of the auxiliary pressed button, usually the wheel button
|
||||||
// or the middle button (if present)
|
// or the middle button (if present)
|
||||||
AuxiliaryMouseButton = 1
|
AuxiliaryMouseButton = 1
|
||||||
|
|
||||||
// SecondaryMouseButton is a number of the secondary pressed button, usually the right button
|
// SecondaryMouseButton is a number of the secondary pressed button, usually the right button
|
||||||
SecondaryMouseButton = 2
|
SecondaryMouseButton = 2
|
||||||
|
|
||||||
// MouseButton4 is a number of the fourth button, typically the Browser Back button
|
// MouseButton4 is a number of the fourth button, typically the Browser Back button
|
||||||
MouseButton4 = 3
|
MouseButton4 = 3
|
||||||
|
|
||||||
// MouseButton5 is a number of the fifth button, typically the Browser Forward button
|
// MouseButton5 is a number of the fifth button, typically the Browser Forward button
|
||||||
MouseButton5 = 4
|
MouseButton5 = 4
|
||||||
|
|
||||||
// PrimaryMouseMask is the mask of the primary button (usually the left button)
|
// PrimaryMouseMask is the mask of the primary button (usually the left button)
|
||||||
PrimaryMouseMask = 1
|
PrimaryMouseMask = 1
|
||||||
|
|
||||||
// SecondaryMouseMask is the mask of the secondary button (usually the right button)
|
// SecondaryMouseMask is the mask of the secondary button (usually the right button)
|
||||||
SecondaryMouseMask = 2
|
SecondaryMouseMask = 2
|
||||||
|
|
||||||
// AuxiliaryMouseMask is the mask of the auxiliary button (usually the mouse wheel button or middle button)
|
// AuxiliaryMouseMask is the mask of the auxiliary button (usually the mouse wheel button or middle button)
|
||||||
AuxiliaryMouseMask = 4
|
AuxiliaryMouseMask = 4
|
||||||
|
|
||||||
// MouseMask4 is the mask of the 4th button (typically the "Browser Back" button)
|
// MouseMask4 is the mask of the 4th button (typically the "Browser Back" button)
|
||||||
MouseMask4 = 8
|
MouseMask4 = 8
|
||||||
|
|
||||||
//MouseMask5 is the mask of the 5th button (typically the "Browser Forward" button)
|
//MouseMask5 is the mask of the 5th button (typically the "Browser Forward" button)
|
||||||
MouseMask5 = 16
|
MouseMask5 = 16
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,17 +7,37 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// NumberChangedEvent is the constant for the "" property tag.
|
||||||
|
// The "number-changed" property sets listener(s) that track the change in the entered value.
|
||||||
NumberChangedEvent = "number-changed"
|
NumberChangedEvent = "number-changed"
|
||||||
NumberPickerType = "number-picker-type"
|
|
||||||
NumberPickerMin = "number-picker-min"
|
// NumberPickerType is the constant for the "number-picker-type" property tag.
|
||||||
NumberPickerMax = "number-picker-max"
|
// The "number-picker-type" int property sets the mode of NumberPicker. It can take the following values:
|
||||||
NumberPickerStep = "number-picker-step"
|
// * NumberEditor (0) - NumberPicker is presented by editor. Default value;
|
||||||
NumberPickerValue = "number-picker-value"
|
// * NumberSlider (1) - NumberPicker is presented by slider. |
|
||||||
|
NumberPickerType = "number-picker-type"
|
||||||
|
|
||||||
|
// NumberPickerMin is the constant for the "number-picker-min" property tag.
|
||||||
|
// The "number-picker-min" int property sets the minimum value of NumberPicker. The default value is 0.
|
||||||
|
NumberPickerMin = "number-picker-min"
|
||||||
|
|
||||||
|
// NumberPickerMax is the constant for the "number-picker-max" property tag.
|
||||||
|
// The "number-picker-max" int property sets the maximum value of NumberPicker. The default value is 1.
|
||||||
|
NumberPickerMax = "number-picker-max"
|
||||||
|
|
||||||
|
// NumberPickerStep is the constant for the "number-picker-step" property tag.
|
||||||
|
// The "number-picker-step" int property sets the value change step of NumberPicker
|
||||||
|
NumberPickerStep = "number-picker-step"
|
||||||
|
|
||||||
|
// NumberPickerValue is the constant for the "number-picker-value" property tag.
|
||||||
|
// The "number-picker-value" int property sets the current value of NumberPicker. The default value is 0.
|
||||||
|
NumberPickerValue = "number-picker-value"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// NumberEditor - type of NumberPicker. NumberPicker is presented by editor
|
// NumberEditor - type of NumberPicker. NumberPicker is presented by editor
|
||||||
NumberEditor = 0
|
NumberEditor = 0
|
||||||
|
|
||||||
// NumberSlider - type of NumberPicker. NumberPicker is presented by slider
|
// NumberSlider - type of NumberPicker. NumberPicker is presented by slider
|
||||||
NumberSlider = 1
|
NumberSlider = 1
|
||||||
)
|
)
|
||||||
|
@ -47,6 +67,7 @@ func newNumberPicker(session Session) View {
|
||||||
func (picker *numberPickerData) init(session Session) {
|
func (picker *numberPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "NumberPicker"
|
picker.tag = "NumberPicker"
|
||||||
|
picker.hasHtmlDisabled = true
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,13 +253,6 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde
|
||||||
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *numberPickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *numberPickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
|
|
|
@ -11,15 +11,19 @@ type Properties interface {
|
||||||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||||||
Get(tag string) any
|
Get(tag string) any
|
||||||
getRaw(tag string) any
|
getRaw(tag string) any
|
||||||
|
|
||||||
// Set sets the value (second argument) of the property with name defined by the first argument.
|
// Set sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
Set(tag string, value any) bool
|
Set(tag string, value any) bool
|
||||||
setRaw(tag string, value any)
|
setRaw(tag string, value any)
|
||||||
|
|
||||||
// Remove removes the property with name defined by the argument
|
// Remove removes the property with name defined by the argument
|
||||||
Remove(tag string)
|
Remove(tag string)
|
||||||
|
|
||||||
// Clear removes all properties
|
// Clear removes all properties
|
||||||
Clear()
|
Clear()
|
||||||
|
|
||||||
// AllTags returns an array of the set properties
|
// AllTags returns an array of the set properties
|
||||||
AllTags() []string
|
AllTags() []string
|
||||||
}
|
}
|
||||||
|
@ -68,6 +72,28 @@ func (properties *propertyList) AllTags() []string {
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
|
||||||
|
indent string, objectTag string, tags []string) {
|
||||||
|
|
||||||
|
buffer.WriteString(objectTag)
|
||||||
|
buffer.WriteString(" {\n")
|
||||||
|
|
||||||
|
indent2 := indent + "\t"
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if value, ok := properties.properties[tag]; ok {
|
||||||
|
buffer.WriteString(indent2)
|
||||||
|
buffer.WriteString(tag)
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent2)
|
||||||
|
buffer.WriteString(",\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(indent)
|
||||||
|
buffer.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
func parseProperties(properties Properties, object DataObject) {
|
func parseProperties(properties Properties, object DataObject) {
|
||||||
count := object.PropertyCount()
|
count := object.PropertyCount()
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
|
|
|
@ -666,7 +666,8 @@ const (
|
||||||
|
|
||||||
// Resize is the constant for the "resize" property tag.
|
// Resize is the constant for the "resize" property tag.
|
||||||
// The "resize" int property sets whether an element is resizable, and if so, in which directions.
|
// The "resize" int property sets whether an element is resizable, and if so, in which directions.
|
||||||
// Valid values are "none" (0), "both" (1), horizontal (2), and "vertical" (3)
|
// Valid values are "none" / NoneResize (0), "both" / BothResize (1),
|
||||||
|
// "horizontal" / HorizontalResize (2), and "vertical" / VerticalResize (3)
|
||||||
Resize = "resize"
|
Resize = "resize"
|
||||||
|
|
||||||
// UserSelect is the constant for the "user-select" property tag.
|
// UserSelect is the constant for the "user-select" property tag.
|
||||||
|
|
|
@ -331,6 +331,16 @@ var enumProperties = map[string]struct {
|
||||||
"justify-items",
|
"justify-items",
|
||||||
[]string{"start", "end", "center", "stretch"},
|
[]string{"start", "end", "center", "stretch"},
|
||||||
},
|
},
|
||||||
|
CellVerticalSelfAlign: {
|
||||||
|
[]string{"top", "bottom", "center", "stretch"},
|
||||||
|
"align-self",
|
||||||
|
[]string{"start", "end", "center", "stretch"},
|
||||||
|
},
|
||||||
|
CellHorizontalSelfAlign: {
|
||||||
|
[]string{"left", "right", "center", "stretch"},
|
||||||
|
"justify-self",
|
||||||
|
[]string{"start", "end", "center", "stretch"},
|
||||||
|
},
|
||||||
GridAutoFlow: {
|
GridAutoFlow: {
|
||||||
[]string{"row", "column", "row-dense", "column-dense"},
|
[]string{"row", "column", "row-dense", "column-dense"},
|
||||||
GridAutoFlow,
|
GridAutoFlow,
|
||||||
|
|
|
@ -11,22 +11,23 @@ const (
|
||||||
// The "side" int property determines which side of the container is used to resize.
|
// The "side" int property determines which side of the container is used to resize.
|
||||||
// The value of property is or-combination of TopSide (1), RightSide (2), BottomSide (4), and LeftSide (8)
|
// The value of property is or-combination of TopSide (1), RightSide (2), BottomSide (4), and LeftSide (8)
|
||||||
Side = "side"
|
Side = "side"
|
||||||
|
|
||||||
// ResizeBorderWidth is the constant for the "resize-border-width" property tag.
|
// ResizeBorderWidth is the constant for the "resize-border-width" property tag.
|
||||||
// The "ResizeBorderWidth" SizeUnit property determines the width of the resizing border
|
// The "ResizeBorderWidth" SizeUnit property determines the width of the resizing border
|
||||||
ResizeBorderWidth = "resize-border-width"
|
ResizeBorderWidth = "resize-border-width"
|
||||||
// CellVerticalAlign is the constant for the "cell-vertical-align" property tag.
|
|
||||||
CellVerticalAlign = "cell-vertical-align"
|
|
||||||
// CellHorizontalAlign is the constant for the "cell-horizontal-align" property tag.
|
|
||||||
CellHorizontalAlign = "cell-horizontal-align"
|
|
||||||
|
|
||||||
// TopSide is value of the "side" property: the top side is used to resize
|
// TopSide is value of the "side" property: the top side is used to resize
|
||||||
TopSide = 1
|
TopSide = 1
|
||||||
|
|
||||||
// RightSide is value of the "side" property: the right side is used to resize
|
// RightSide is value of the "side" property: the right side is used to resize
|
||||||
RightSide = 2
|
RightSide = 2
|
||||||
|
|
||||||
// BottomSide is value of the "side" property: the bottom side is used to resize
|
// BottomSide is value of the "side" property: the bottom side is used to resize
|
||||||
BottomSide = 4
|
BottomSide = 4
|
||||||
|
|
||||||
// LeftSide is value of the "side" property: the left side is used to resize
|
// LeftSide is value of the "side" property: the left side is used to resize
|
||||||
LeftSide = 8
|
LeftSide = 8
|
||||||
|
|
||||||
// AllSides is value of the "side" property: all sides is used to resize
|
// AllSides is value of the "side" property: all sides is used to resize
|
||||||
AllSides = TopSide | RightSide | BottomSide | LeftSide
|
AllSides = TopSide | RightSide | BottomSide | LeftSide
|
||||||
)
|
)
|
||||||
|
|
121
session.go
121
session.go
|
@ -7,7 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type webBridge interface {
|
type bridge interface {
|
||||||
startUpdateScript(htmlID string) bool
|
startUpdateScript(htmlID string) bool
|
||||||
finishUpdateScript(htmlID string)
|
finishUpdateScript(htmlID string)
|
||||||
callFunc(funcName string, args ...any) bool
|
callFunc(funcName string, args ...any) bool
|
||||||
|
@ -16,10 +16,9 @@ type webBridge interface {
|
||||||
updateCSSProperty(htmlID, property, value string)
|
updateCSSProperty(htmlID, property, value string)
|
||||||
updateProperty(htmlID, property string, value any)
|
updateProperty(htmlID, property string, value any)
|
||||||
removeProperty(htmlID, property string)
|
removeProperty(htmlID, property string)
|
||||||
readMessage() (string, bool)
|
sendResponse()
|
||||||
writeMessage(text string) bool
|
setAnimationCSS(css string)
|
||||||
addAnimationCSS(css string)
|
appendAnimationCSS(css string)
|
||||||
clearAnimation()
|
|
||||||
canvasStart(htmlID string)
|
canvasStart(htmlID string)
|
||||||
callCanvasFunc(funcName string, args ...any)
|
callCanvasFunc(funcName string, args ...any)
|
||||||
callCanvasVarFunc(v any, funcName string, args ...any)
|
callCanvasVarFunc(v any, funcName string, args ...any)
|
||||||
|
@ -111,6 +110,14 @@ type Session interface {
|
||||||
// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
|
// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
|
||||||
SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session))
|
SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session))
|
||||||
|
|
||||||
|
// StartTimer starts a timer on the client side.
|
||||||
|
// The first argument specifies the timer period in milliseconds.
|
||||||
|
// The second argument specifies a function that will be called on each timer event.
|
||||||
|
// The result is the id of the timer, which is used to stop the timer
|
||||||
|
StartTimer(ms int, timerFunc func(Session)) int
|
||||||
|
// StopTimer the timer with the given id
|
||||||
|
StopTimer(timerID int)
|
||||||
|
|
||||||
getCurrentTheme() Theme
|
getCurrentTheme() Theme
|
||||||
registerAnimation(props []AnimatedProperty) string
|
registerAnimation(props []AnimatedProperty) string
|
||||||
|
|
||||||
|
@ -124,7 +131,7 @@ type Session interface {
|
||||||
nextViewID() string
|
nextViewID() string
|
||||||
styleProperty(styleTag, property string) any
|
styleProperty(styleTag, property string) any
|
||||||
|
|
||||||
setBridge(events chan DataObject, bridge webBridge)
|
setBridge(events chan DataObject, bridge bridge)
|
||||||
writeInitScript(writer *strings.Builder)
|
writeInitScript(writer *strings.Builder)
|
||||||
callFunc(funcName string, args ...any)
|
callFunc(funcName string, args ...any)
|
||||||
updateInnerHTML(htmlID, html string)
|
updateInnerHTML(htmlID, html string)
|
||||||
|
@ -134,6 +141,7 @@ type Session interface {
|
||||||
removeProperty(htmlID, property string)
|
removeProperty(htmlID, property string)
|
||||||
startUpdateScript(htmlID string) bool
|
startUpdateScript(htmlID string) bool
|
||||||
finishUpdateScript(htmlID string)
|
finishUpdateScript(htmlID string)
|
||||||
|
sendResponse()
|
||||||
addAnimationCSS(css string)
|
addAnimationCSS(css string)
|
||||||
clearAnimation()
|
clearAnimation()
|
||||||
canvasStart(htmlID string)
|
canvasStart(htmlID string)
|
||||||
|
@ -145,7 +153,8 @@ type Session interface {
|
||||||
canvasFinish()
|
canvasFinish()
|
||||||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||||
htmlPropertyValue(htmlID, name string) string
|
htmlPropertyValue(htmlID, name string) string
|
||||||
handleAnswer(data DataObject)
|
addToEventsQueue(data DataObject)
|
||||||
|
handleAnswer(command string, data DataObject) bool
|
||||||
handleRootSize(data DataObject)
|
handleRootSize(data DataObject)
|
||||||
handleResize(data DataObject)
|
handleResize(data DataObject)
|
||||||
handleEvent(command string, data DataObject)
|
handleEvent(command string, data DataObject)
|
||||||
|
@ -189,13 +198,16 @@ type sessionData struct {
|
||||||
ignoreUpdates bool
|
ignoreUpdates bool
|
||||||
popups *popupManager
|
popups *popupManager
|
||||||
images *imageManager
|
images *imageManager
|
||||||
bridge webBridge
|
bridge bridge
|
||||||
events chan DataObject
|
events chan DataObject
|
||||||
animationCounter int
|
animationCounter int
|
||||||
animationCSS string
|
animationCSS string
|
||||||
updateScripts map[string]*strings.Builder
|
updateScripts map[string]*strings.Builder
|
||||||
clientStorage map[string]string
|
clientStorage map[string]string
|
||||||
hotkeys map[string]func(Session)
|
hotkeys map[string]func(Session)
|
||||||
|
timers map[int]func(Session)
|
||||||
|
nextTimerID int
|
||||||
|
pauseTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
||||||
|
@ -214,6 +226,8 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
||||||
session.updateScripts = map[string]*strings.Builder{}
|
session.updateScripts = map[string]*strings.Builder{}
|
||||||
session.clientStorage = map[string]string{}
|
session.clientStorage = map[string]string{}
|
||||||
session.hotkeys = map[string]func(Session){}
|
session.hotkeys = map[string]func(Session){}
|
||||||
|
session.timers = map[int]func(Session){}
|
||||||
|
session.nextTimerID = 1
|
||||||
|
|
||||||
if customTheme != "" {
|
if customTheme != "" {
|
||||||
if theme, ok := CreateThemeFromText(customTheme); ok {
|
if theme, ok := CreateThemeFromText(customTheme); ok {
|
||||||
|
@ -237,7 +251,7 @@ func (session *sessionData) ID() int {
|
||||||
return session.sessionID
|
return session.sessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) {
|
func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
|
||||||
session.events = events
|
session.events = events
|
||||||
session.bridge = bridge
|
session.bridge = bridge
|
||||||
}
|
}
|
||||||
|
@ -330,23 +344,19 @@ func (session *sessionData) updateTooltipConstants() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) reload() {
|
func (session *sessionData) reload() {
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
|
css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
|
||||||
css = strings.ReplaceAll(css, "\n", `\n`)
|
session.bridge.callFunc("setStyles", css)
|
||||||
css = strings.ReplaceAll(css, "\t", `\t`)
|
|
||||||
buffer.WriteString(`document.querySelector('style').textContent = "`)
|
|
||||||
buffer.WriteString(css)
|
|
||||||
buffer.WriteString("\";\n")
|
|
||||||
|
|
||||||
if session.rootView != nil {
|
if session.rootView != nil {
|
||||||
buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
viewHTML(session.rootView, buffer)
|
viewHTML(session.rootView, buffer)
|
||||||
buffer.WriteString("';\nscanElementsSize();")
|
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
||||||
|
session.bridge.callFunc("scanElementsSize")
|
||||||
}
|
}
|
||||||
|
|
||||||
session.bridge.writeMessage(buffer.String())
|
|
||||||
session.updateTooltipConstants()
|
session.updateTooltipConstants()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,15 +457,21 @@ func (session *sessionData) finishUpdateScript(htmlID string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) sendResponse() {
|
||||||
|
if session.bridge != nil {
|
||||||
|
session.bridge.sendResponse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (session *sessionData) addAnimationCSS(css string) {
|
func (session *sessionData) addAnimationCSS(css string) {
|
||||||
if session.bridge != nil {
|
if session.bridge != nil {
|
||||||
session.bridge.addAnimationCSS(css)
|
session.bridge.appendAnimationCSS(css)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) clearAnimation() {
|
func (session *sessionData) clearAnimation() {
|
||||||
if session.bridge != nil {
|
if session.bridge != nil {
|
||||||
session.bridge.clearAnimation()
|
session.bridge.setAnimationCSS("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,8 +536,27 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) handleAnswer(data DataObject) {
|
func (session *sessionData) handleAnswer(command string, data DataObject) bool {
|
||||||
session.bridge.answerReceived(data)
|
switch command {
|
||||||
|
case "answer":
|
||||||
|
if session.bridge != nil {
|
||||||
|
session.bridge.answerReceived(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "imageLoaded":
|
||||||
|
session.imageManager().imageLoaded(data)
|
||||||
|
|
||||||
|
case "imageError":
|
||||||
|
session.imageManager().imageLoadError(data)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.bridge != nil {
|
||||||
|
session.bridge.sendResponse()
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) handleRootSize(data DataObject) {
|
func (session *sessionData) handleRootSize(data DataObject) {
|
||||||
|
@ -642,6 +677,22 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||||
case "session-resume":
|
case "session-resume":
|
||||||
session.onResume()
|
session.onResume()
|
||||||
|
|
||||||
|
case "timer":
|
||||||
|
if text, ok := data.PropertyValue("timerID"); ok {
|
||||||
|
timerID, err := strconv.Atoi(text)
|
||||||
|
if err == nil {
|
||||||
|
if fn, ok := session.timers[timerID]; ok {
|
||||||
|
fn(session)
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Timer (id = ` + text + `) not exists`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorLog(`"timerID" property not found`)
|
||||||
|
}
|
||||||
|
|
||||||
case "root-size":
|
case "root-size":
|
||||||
session.handleRootSize(data)
|
session.handleRootSize(data)
|
||||||
|
|
||||||
|
@ -672,6 +723,8 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||||
ErrorLog(`"id" property not found. Event: ` + command)
|
ErrorLog(`"id" property not found. Event: ` + command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.bridge.sendResponse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) hotKey(event KeyEvent) {
|
func (session *sessionData) hotKey(event KeyEvent) {
|
||||||
|
@ -769,3 +822,25 @@ func (session *sessionData) RemoveAllClientItems() {
|
||||||
session.clientStorage = map[string]string{}
|
session.clientStorage = map[string]string{}
|
||||||
session.bridge.callFunc("localStorageClear")
|
session.bridge.callFunc("localStorageClear")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) addToEventsQueue(data DataObject) {
|
||||||
|
session.events <- data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) StartTimer(ms int, timerFunc func(Session)) int {
|
||||||
|
timerID := 0
|
||||||
|
if session.bridge != nil {
|
||||||
|
timerID = session.nextTimerID
|
||||||
|
session.nextTimerID++
|
||||||
|
session.timers[timerID] = timerFunc
|
||||||
|
session.bridge.callFunc("startTimer", ms, timerID)
|
||||||
|
}
|
||||||
|
return timerID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) StopTimer(timerID int) {
|
||||||
|
if session.bridge != nil {
|
||||||
|
session.bridge.callFunc("stopTimer", timerID)
|
||||||
|
delete(session.timers, timerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// SessionStartListener is the listener interface of a session start event
|
// SessionStartListener is the listener interface of a session start event
|
||||||
type SessionStartListener interface {
|
type SessionStartListener interface {
|
||||||
OnStart(session Session)
|
OnStart(session Session)
|
||||||
|
@ -50,13 +52,25 @@ func (session *sessionData) onFinish() {
|
||||||
|
|
||||||
func (session *sessionData) onPause() {
|
func (session *sessionData) onPause() {
|
||||||
if session.content != nil {
|
if session.content != nil {
|
||||||
|
session.pauseTime = time.Now().Unix()
|
||||||
if listener, ok := session.content.(SessionPauseListener); ok {
|
if listener, ok := session.content.(SessionPauseListener); ok {
|
||||||
listener.OnPause(session)
|
listener.OnPause(session)
|
||||||
}
|
}
|
||||||
|
if timeout := session.app.Params().SocketAutoClose; timeout > 0 {
|
||||||
|
go session.autoClose(session.pauseTime, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *sessionData) autoClose(start int64, timeout int) {
|
||||||
|
time.Sleep(time.Second * time.Duration(timeout))
|
||||||
|
if session.pauseTime == start {
|
||||||
|
session.bridge.callFunc("closeSocket")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) onResume() {
|
func (session *sessionData) onResume() {
|
||||||
|
session.pauseTime = 0
|
||||||
if session.content != nil {
|
if session.content != nil {
|
||||||
if listener, ok := session.content.(SessionResumeListener); ok {
|
if listener, ok := session.content.(SessionResumeListener); ok {
|
||||||
listener.OnResume(session)
|
listener.OnResume(session)
|
||||||
|
|
|
@ -22,10 +22,33 @@ const (
|
||||||
// StackLayout - list-container of View
|
// StackLayout - list-container of View
|
||||||
type StackLayout interface {
|
type StackLayout interface {
|
||||||
ViewsContainer
|
ViewsContainer
|
||||||
|
|
||||||
|
// Peek returns the current (visible) View. If StackLayout is empty then it returns nil.
|
||||||
Peek() View
|
Peek() View
|
||||||
|
|
||||||
|
// RemovePeek removes the current View and returns it. If StackLayout is empty then it doesn't do anything and returns nil.
|
||||||
|
RemovePeek() View
|
||||||
|
|
||||||
|
// MoveToFront makes the given View current. Returns true if successful, false otherwise.
|
||||||
MoveToFront(view View) bool
|
MoveToFront(view View) bool
|
||||||
|
|
||||||
|
// MoveToFrontByID makes the View current by viewID. Returns true if successful, false otherwise.
|
||||||
MoveToFrontByID(viewID string) bool
|
MoveToFrontByID(viewID string) bool
|
||||||
|
|
||||||
|
// Push adds a new View to the container and makes it current.
|
||||||
|
// It is similar to Append, but the addition is done using an animation effect.
|
||||||
|
// The animation type is specified by the second argument and can take the following values:
|
||||||
|
// * DefaultAnimation (0) - Default animation. For the Push function it is EndToStartAnimation, for Pop - StartToEndAnimation;
|
||||||
|
// * StartToEndAnimation (1) - Animation from beginning to end. The beginning and the end are determined by the direction of the text output;
|
||||||
|
// * EndToStartAnimation (2) - End-to-Beginning animation;
|
||||||
|
// * TopDownAnimation (3) - Top-down animation;
|
||||||
|
// * BottomUpAnimation (4) - Bottom up animation.
|
||||||
|
// The third argument `onPushFinished` is the function to be called when the animation ends. It may be nil.
|
||||||
Push(view View, animation int, onPushFinished func())
|
Push(view View, animation int, onPushFinished func())
|
||||||
|
|
||||||
|
// Pop removes the current View from the container using animation.
|
||||||
|
// The second argument `onPopFinished`` is the function to be called when the animation ends. It may be nil.
|
||||||
|
// The function will return false if the StackLayout is empty and true if the current item has been removed.
|
||||||
Pop(animation int, onPopFinished func(View)) bool
|
Pop(animation int, onPopFinished func(View)) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +300,10 @@ func (layout *stackLayoutData) RemoveView(index int) View {
|
||||||
return layout.viewsContainerData.RemoveView(index)
|
return layout.viewsContainerData.RemoveView(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (layout *stackLayoutData) RemovePeek() View {
|
||||||
|
return layout.RemoveView(len(layout.views) - 1)
|
||||||
|
}
|
||||||
|
|
||||||
func (layout *stackLayoutData) Push(view View, animation int, onPushFinished func()) {
|
func (layout *stackLayoutData) Push(view View, animation int, onPushFinished func()) {
|
||||||
if view == nil {
|
if view == nil {
|
||||||
ErrorLog("StackLayout.Push(nil, ....) is forbidden")
|
ErrorLog("StackLayout.Push(nil, ....) is forbidden")
|
||||||
|
|
|
@ -40,6 +40,7 @@ func newTimePicker(session Session) View {
|
||||||
func (picker *timePickerData) init(session Session) {
|
func (picker *timePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "TimePicker"
|
picker.tag = "TimePicker"
|
||||||
|
picker.hasHtmlDisabled = true
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,13 +292,6 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` disabled`)
|
|
||||||
}
|
|
||||||
picker.viewData.htmlDisabledProperties(self, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *timePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *timePickerData) handleCommand(self View, command string, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
|
|
|
@ -8,9 +8,11 @@ const (
|
||||||
// VideoWidth is the constant for the "video-width" property tag of VideoPlayer.
|
// VideoWidth is the constant for the "video-width" property tag of VideoPlayer.
|
||||||
// The "video-width" float property defines the width of the video's display area in pixels.
|
// The "video-width" float property defines the width of the video's display area in pixels.
|
||||||
VideoWidth = "video-width"
|
VideoWidth = "video-width"
|
||||||
|
|
||||||
// VideoHeight is the constant for the "video-height" property tag of VideoPlayer.
|
// VideoHeight is the constant for the "video-height" property tag of VideoPlayer.
|
||||||
// The "video-height" float property defines the height of the video's display area in pixels.
|
// The "video-height" float property defines the height of the video's display area in pixels.
|
||||||
VideoHeight = "video-height"
|
VideoHeight = "video-height"
|
||||||
|
|
||||||
// Poster is the constant for the "poster" property tag of VideoPlayer.
|
// Poster is the constant for the "poster" property tag of VideoPlayer.
|
||||||
// The "poster" property defines an URL for an image to be shown while the video is downloading.
|
// The "poster" property defines an URL for an image to be shown while the video is downloading.
|
||||||
// If this attribute isn't specified, nothing is displayed until the first frame is available,
|
// If this attribute isn't specified, nothing is displayed until the first frame is available,
|
||||||
|
|
87
view.go
87
view.go
|
@ -35,24 +35,33 @@ type View interface {
|
||||||
|
|
||||||
// Session returns the current Session interface
|
// Session returns the current Session interface
|
||||||
Session() Session
|
Session() Session
|
||||||
|
|
||||||
// Parent returns the parent view
|
// Parent returns the parent view
|
||||||
Parent() View
|
Parent() View
|
||||||
|
|
||||||
// Tag returns the tag of View interface
|
// Tag returns the tag of View interface
|
||||||
Tag() string
|
Tag() string
|
||||||
|
|
||||||
// ID returns the id of the view
|
// ID returns the id of the view
|
||||||
ID() string
|
ID() string
|
||||||
|
|
||||||
// Focusable returns true if the view receives the focus
|
// Focusable returns true if the view receives the focus
|
||||||
Focusable() bool
|
Focusable() bool
|
||||||
|
|
||||||
// Frame returns the location and size of the view in pixels
|
// Frame returns the location and size of the view in pixels
|
||||||
Frame() Frame
|
Frame() Frame
|
||||||
|
|
||||||
// Scroll returns the location size of the scrollable view in pixels
|
// Scroll returns the location size of the scrollable view in pixels
|
||||||
Scroll() Frame
|
Scroll() Frame
|
||||||
|
|
||||||
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
SetAnimated(tag string, value any, animation Animation) bool
|
SetAnimated(tag string, value any, animation Animation) bool
|
||||||
|
|
||||||
// SetChangeListener set the function to track the change of the View property
|
// SetChangeListener set the function to track the change of the View property
|
||||||
SetChangeListener(tag string, listener func(View, string))
|
SetChangeListener(tag string, listener func(View, string))
|
||||||
|
|
||||||
// HasFocus returns 'true' if the view has focus
|
// HasFocus returns 'true' if the view has focus
|
||||||
HasFocus() bool
|
HasFocus() bool
|
||||||
|
|
||||||
|
@ -65,7 +74,6 @@ type View interface {
|
||||||
setParentID(parentID string)
|
setParentID(parentID string)
|
||||||
htmlSubviews(self View, buffer *strings.Builder)
|
htmlSubviews(self View, buffer *strings.Builder)
|
||||||
htmlProperties(self View, buffer *strings.Builder)
|
htmlProperties(self View, buffer *strings.Builder)
|
||||||
htmlDisabledProperties(self View, buffer *strings.Builder)
|
|
||||||
cssStyle(self View, builder cssBuilder)
|
cssStyle(self View, builder cssBuilder)
|
||||||
addToCSSStyle(addCSS map[string]string)
|
addToCSSStyle(addCSS map[string]string)
|
||||||
|
|
||||||
|
@ -93,6 +101,7 @@ type viewData struct {
|
||||||
noResizeEvent bool
|
noResizeEvent bool
|
||||||
created bool
|
created bool
|
||||||
hasFocus bool
|
hasFocus bool
|
||||||
|
hasHtmlDisabled bool
|
||||||
//animation map[string]AnimationEndListener
|
//animation map[string]AnimationEndListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +144,7 @@ func (view *viewData) init(session Session) {
|
||||||
view.singleTransition = map[string]Animation{}
|
view.singleTransition = map[string]Animation{}
|
||||||
view.noResizeEvent = false
|
view.noResizeEvent = false
|
||||||
view.created = false
|
view.created = false
|
||||||
|
view.hasHtmlDisabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) Session() Session {
|
func (view *viewData) Session() Session {
|
||||||
|
@ -302,7 +312,6 @@ func (view *viewData) propertyChangedEvent(tag string) {
|
||||||
if listener, ok := view.changeListener[tag]; ok {
|
if listener, ok := view.changeListener[tag]; ok {
|
||||||
listener(view, tag)
|
listener(view, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) Set(tag string, value any) bool {
|
func (view *viewData) Set(tag string, value any) bool {
|
||||||
|
@ -404,7 +413,35 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Disabled:
|
case Disabled:
|
||||||
updateInnerHTML(view.parentHTMLID(), session)
|
tabIndex := GetTabIndex(view, htmlID)
|
||||||
|
enabledClass := view.htmlClass(false)
|
||||||
|
disabledClass := view.htmlClass(true)
|
||||||
|
session.startUpdateScript(htmlID)
|
||||||
|
if IsDisabled(view) {
|
||||||
|
session.updateProperty(htmlID, "data-disabled", "1")
|
||||||
|
if view.hasHtmlDisabled {
|
||||||
|
session.updateProperty(htmlID, "disabled", true)
|
||||||
|
}
|
||||||
|
if tabIndex >= 0 {
|
||||||
|
session.updateProperty(htmlID, "tabindex", -1)
|
||||||
|
}
|
||||||
|
if enabledClass != disabledClass {
|
||||||
|
session.updateProperty(htmlID, "class", disabledClass)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.updateProperty(htmlID, "data-disabled", "0")
|
||||||
|
if view.hasHtmlDisabled {
|
||||||
|
session.removeProperty(htmlID, "disabled")
|
||||||
|
}
|
||||||
|
if tabIndex >= 0 {
|
||||||
|
session.updateProperty(htmlID, "tabindex", tabIndex)
|
||||||
|
}
|
||||||
|
if enabledClass != disabledClass {
|
||||||
|
session.updateProperty(htmlID, "class", enabledClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.finishUpdateScript(htmlID)
|
||||||
|
updateInnerHTML(htmlID, session)
|
||||||
return
|
return
|
||||||
|
|
||||||
case Visibility:
|
case Visibility:
|
||||||
|
@ -613,6 +650,8 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
case ZIndex, Order, TabSize:
|
case ZIndex, Order, TabSize:
|
||||||
if i, ok := intProperty(view, tag, session, 0); ok {
|
if i, ok := intProperty(view, tag, session, 0); ok {
|
||||||
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
|
session.updateCSSProperty(htmlID, tag, strconv.Itoa(i))
|
||||||
|
} else {
|
||||||
|
session.updateCSSProperty(htmlID, tag, "")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -660,8 +699,11 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cssTag, ok := sizeProperties[tag]; ok {
|
if cssTag, ok := sizeProperties[tag]; ok {
|
||||||
size, _ := sizeProperty(view, tag, session)
|
if size, ok := sizeProperty(view, tag, session); ok {
|
||||||
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session))
|
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session))
|
||||||
|
} else {
|
||||||
|
session.updateCSSProperty(htmlID, cssTag, "")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,8 +724,11 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if valuesData, ok := enumProperties[tag]; ok && valuesData.cssTag != "" {
|
if valuesData, ok := enumProperties[tag]; ok && valuesData.cssTag != "" {
|
||||||
n, _ := enumProperty(view, tag, session, 0)
|
if n, ok := enumProperty(view, tag, session, 0); ok {
|
||||||
session.updateCSSProperty(htmlID, valuesData.cssTag, valuesData.cssValues[n])
|
session.updateCSSProperty(htmlID, valuesData.cssTag, valuesData.cssValues[n])
|
||||||
|
} else {
|
||||||
|
session.updateCSSProperty(htmlID, valuesData.cssTag, "")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,6 +736,8 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
if tag == floatTag {
|
if tag == floatTag {
|
||||||
if f, ok := floatTextProperty(view, floatTag, session, 0); ok {
|
if f, ok := floatTextProperty(view, floatTag, session, 0); ok {
|
||||||
session.updateCSSProperty(htmlID, floatTag, f)
|
session.updateCSSProperty(htmlID, floatTag, f)
|
||||||
|
} else {
|
||||||
|
session.updateCSSProperty(htmlID, floatTag, "")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -759,20 +806,22 @@ func (view *viewData) cssStyle(self View, builder cssBuilder) {
|
||||||
|
|
||||||
func (view *viewData) htmlProperties(self View, buffer *strings.Builder) {
|
func (view *viewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
view.created = true
|
view.created = true
|
||||||
|
|
||||||
|
if IsDisabled(self) {
|
||||||
|
buffer.WriteString(` data-disabled="1"`)
|
||||||
|
if view.hasHtmlDisabled {
|
||||||
|
buffer.WriteString(` disabled`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(` data-disabled="0"`)
|
||||||
|
}
|
||||||
|
|
||||||
if view.frame.Left != 0 || view.frame.Top != 0 || view.frame.Width != 0 || view.frame.Height != 0 {
|
if view.frame.Left != 0 || view.frame.Top != 0 || view.frame.Width != 0 || view.frame.Height != 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(` data-left="%g" data-top="%g" data-width="%g" data-height="%g"`,
|
buffer.WriteString(fmt.Sprintf(` data-left="%g" data-top="%g" data-width="%g" data-height="%g"`,
|
||||||
view.frame.Left, view.frame.Top, view.frame.Width, view.frame.Height))
|
view.frame.Left, view.frame.Top, view.frame.Width, view.frame.Height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) htmlDisabledProperties(self View, buffer *strings.Builder) {
|
|
||||||
if IsDisabled(self) {
|
|
||||||
buffer.WriteString(` data-disabled="1"`)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(` data-disabled="0"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func viewHTML(view View, buffer *strings.Builder) {
|
func viewHTML(view View, buffer *strings.Builder) {
|
||||||
viewHTMLTag := view.htmlTag()
|
viewHTMLTag := view.htmlTag()
|
||||||
buffer.WriteRune('<')
|
buffer.WriteRune('<')
|
||||||
|
@ -800,8 +849,6 @@ func viewHTML(view View, buffer *strings.Builder) {
|
||||||
|
|
||||||
buffer.WriteRune(' ')
|
buffer.WriteRune(' ')
|
||||||
view.htmlProperties(view, buffer)
|
view.htmlProperties(view, buffer)
|
||||||
buffer.WriteRune(' ')
|
|
||||||
view.htmlDisabledProperties(view, buffer)
|
|
||||||
|
|
||||||
if view.isNoResizeEvent() {
|
if view.isNoResizeEvent() {
|
||||||
buffer.WriteString(` data-noresize="1" `)
|
buffer.WriteString(` data-noresize="1" `)
|
||||||
|
@ -810,12 +857,10 @@ func viewHTML(view View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !disabled {
|
if !disabled {
|
||||||
if value, ok := intProperty(view, TabIndex, view.Session(), -1); ok {
|
if tabIndex := GetTabIndex(view); tabIndex >= 0 {
|
||||||
buffer.WriteString(`tabindex="`)
|
buffer.WriteString(`tabindex="`)
|
||||||
buffer.WriteString(strconv.Itoa(value))
|
buffer.WriteString(strconv.Itoa(tabIndex))
|
||||||
buffer.WriteString(`" `)
|
buffer.WriteString(`" `)
|
||||||
} else if view.Focusable() {
|
|
||||||
buffer.WriteString(`tabindex="0" `)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
viewStyle.go
49
viewStyle.go
|
@ -13,8 +13,10 @@ type ViewStyle interface {
|
||||||
|
|
||||||
// Transition returns the transition animation of the property. Returns nil is there is no transition animation.
|
// Transition returns the transition animation of the property. Returns nil is there is no transition animation.
|
||||||
Transition(tag string) Animation
|
Transition(tag string) Animation
|
||||||
|
|
||||||
// Transitions returns the map of transition animations. The result is always non-nil.
|
// Transitions returns the map of transition animations. The result is always non-nil.
|
||||||
Transitions() map[string]Animation
|
Transitions() map[string]Animation
|
||||||
|
|
||||||
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
||||||
// removes the transition animation of the property if "animation" argument is nil.
|
// removes the transition animation of the property if "animation" argument is nil.
|
||||||
// The "tag" argument is the property name.
|
// The "tag" argument is the property name.
|
||||||
|
@ -573,6 +575,9 @@ func supportedPropertyValue(value any) bool {
|
||||||
case []ViewShadow:
|
case []ViewShadow:
|
||||||
case []View:
|
case []View:
|
||||||
case []any:
|
case []any:
|
||||||
|
case []BackgroundElement:
|
||||||
|
case []BackgroundGradientPoint:
|
||||||
|
case []BackgroundGradientAngle:
|
||||||
case map[string]Animation:
|
case map[string]Animation:
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
@ -692,6 +697,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
for _, shadow := range value {
|
for _, shadow := range value {
|
||||||
buffer.WriteString(indent2)
|
buffer.WriteString(indent2)
|
||||||
shadow.writeString(buffer, indent)
|
shadow.writeString(buffer, indent)
|
||||||
|
buffer.WriteRune(',')
|
||||||
}
|
}
|
||||||
buffer.WriteRune('\n')
|
buffer.WriteRune('\n')
|
||||||
buffer.WriteString(indent)
|
buffer.WriteString(indent)
|
||||||
|
@ -701,7 +707,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
case []View:
|
case []View:
|
||||||
switch len(value) {
|
switch len(value) {
|
||||||
case 0:
|
case 0:
|
||||||
buffer.WriteString("[]\n")
|
buffer.WriteString("[]")
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
writeViewStyle(value[0].Tag(), value[0], buffer, indent)
|
writeViewStyle(value[0].Tag(), value[0], buffer, indent)
|
||||||
|
@ -740,6 +746,47 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
buffer.WriteString(" ]")
|
buffer.WriteString(" ]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case []BackgroundElement:
|
||||||
|
switch len(value) {
|
||||||
|
case 0:
|
||||||
|
buffer.WriteString("[]\n")
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
value[0].writeString(buffer, indent)
|
||||||
|
|
||||||
|
default:
|
||||||
|
buffer.WriteString("[\n")
|
||||||
|
indent2 := indent + "\t"
|
||||||
|
for _, element := range value {
|
||||||
|
buffer.WriteString(indent2)
|
||||||
|
element.writeString(buffer, indent2)
|
||||||
|
buffer.WriteString(",\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(indent)
|
||||||
|
buffer.WriteRune(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
case []BackgroundGradientPoint:
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
for i, point := range value {
|
||||||
|
if i > 0 {
|
||||||
|
buffer.WriteString(",")
|
||||||
|
}
|
||||||
|
buffer.WriteString(point.String())
|
||||||
|
}
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
|
||||||
|
case []BackgroundGradientAngle:
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
for i, point := range value {
|
||||||
|
if i > 0 {
|
||||||
|
buffer.WriteString(",")
|
||||||
|
}
|
||||||
|
buffer.WriteString(point.String())
|
||||||
|
}
|
||||||
|
buffer.WriteRune('"')
|
||||||
|
|
||||||
case map[string]Animation:
|
case map[string]Animation:
|
||||||
switch count := len(value); count {
|
switch count := len(value); count {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
|
@ -32,42 +32,52 @@ func (style *viewStyle) setRange(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) setBackground(value any) bool {
|
func (style *viewStyle) setBackground(value any) bool {
|
||||||
|
background := []BackgroundElement{}
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case BackgroundElement:
|
case BackgroundElement:
|
||||||
style.properties[Background] = []BackgroundElement{value}
|
background = []BackgroundElement{value}
|
||||||
return true
|
|
||||||
|
|
||||||
case []BackgroundElement:
|
case []BackgroundElement:
|
||||||
style.properties[Background] = value
|
background = value
|
||||||
return true
|
|
||||||
|
case []DataValue:
|
||||||
|
for _, el := range value {
|
||||||
|
if el.IsObject() {
|
||||||
|
if element := createBackground(el.Object()); element != nil {
|
||||||
|
background = append(background, element)
|
||||||
|
}
|
||||||
|
} else if obj := ParseDataText(el.Value()); obj != nil {
|
||||||
|
if element := createBackground(obj); element != nil {
|
||||||
|
background = append(background, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if element := createBackground(value); element != nil {
|
if element := createBackground(value); element != nil {
|
||||||
style.properties[Background] = []BackgroundElement{element}
|
background = []BackgroundElement{element}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case []DataObject:
|
case []DataObject:
|
||||||
for _, obj := range value {
|
for _, obj := range value {
|
||||||
background := []BackgroundElement{}
|
|
||||||
if element := createBackground(obj); element != nil {
|
if element := createBackground(obj); element != nil {
|
||||||
background = append(background, element)
|
background = append(background, element)
|
||||||
}
|
}
|
||||||
if len(background) > 0 {
|
|
||||||
style.properties[Background] = background
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if obj := ParseDataText(value); obj != nil {
|
if obj := ParseDataText(value); obj != nil {
|
||||||
if element := createBackground(obj); element != nil {
|
if element := createBackground(obj); element != nil {
|
||||||
style.properties[Background] = []BackgroundElement{element}
|
background = []BackgroundElement{element}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(background) > 0 {
|
||||||
|
style.properties[Background] = background
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,12 +11,16 @@ type ParentView interface {
|
||||||
type ViewsContainer interface {
|
type ViewsContainer interface {
|
||||||
View
|
View
|
||||||
ParentView
|
ParentView
|
||||||
|
|
||||||
// Append appends a view to the end of the list of a view children
|
// Append appends a view to the end of the list of a view children
|
||||||
Append(view View)
|
Append(view View)
|
||||||
|
|
||||||
// Insert inserts a view to the "index" position in the list of a view children
|
// Insert inserts a view to the "index" position in the list of a view children
|
||||||
Insert(view View, index int)
|
Insert(view View, index int)
|
||||||
|
|
||||||
// Remove removes a view from the list of a view children and return it
|
// Remove removes a view from the list of a view children and return it
|
||||||
RemoveView(index int) View
|
RemoveView(index int) View
|
||||||
|
|
||||||
// ViewIndex returns the index of view, -1 overwise
|
// ViewIndex returns the index of view, -1 overwise
|
||||||
ViewIndex(view View) int
|
ViewIndex(view View) int
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ type wasmBridge struct {
|
||||||
closeEvent chan DataObject
|
closeEvent chan DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWasmBridge(close chan DataObject) webBridge {
|
func createWasmBridge(close chan DataObject) bridge {
|
||||||
bridge := new(wasmBridge)
|
bridge := new(wasmBridge)
|
||||||
bridge.answerID = 1
|
bridge.answerID = 1
|
||||||
bridge.answer = make(map[int]chan DataObject)
|
bridge.answer = make(map[int]chan DataObject)
|
||||||
|
@ -102,10 +102,6 @@ func (bridge *wasmBridge) close() {
|
||||||
bridge.closeEvent <- NewDataObject("close")
|
bridge.closeEvent <- NewDataObject("close")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) readMessage() (string, bool) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *wasmBridge) writeMessage(script string) bool {
|
func (bridge *wasmBridge) writeMessage(script string) bool {
|
||||||
if ProtocolInDebugLog {
|
if ProtocolInDebugLog {
|
||||||
DebugLog("Run script:")
|
DebugLog("Run script:")
|
||||||
|
@ -118,21 +114,24 @@ func (bridge *wasmBridge) writeMessage(script string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) addAnimationCSS(css string) {
|
func (bridge *wasmBridge) prepareCSS(css string) string {
|
||||||
css = strings.ReplaceAll(css, `\t`, "\t")
|
css = strings.ReplaceAll(css, `\t`, "\t")
|
||||||
css = strings.ReplaceAll(css, `\n`, "\n")
|
css = strings.ReplaceAll(css, `\n`, "\n")
|
||||||
css = strings.ReplaceAll(css, `\'`, "'")
|
css = strings.ReplaceAll(css, `\'`, "'")
|
||||||
css = strings.ReplaceAll(css, `\"`, "\"")
|
css = strings.ReplaceAll(css, `\"`, "\"")
|
||||||
css = strings.ReplaceAll(css, `\\`, "\\")
|
css = strings.ReplaceAll(css, `\\`, "\\")
|
||||||
|
return css
|
||||||
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
|
||||||
content := styles.Get("textContent").String()
|
|
||||||
styles.Set("textContent", content+"\n"+css)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) clearAnimation() {
|
func (bridge *wasmBridge) appendAnimationCSS(css string) {
|
||||||
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
||||||
styles.Set("textContent", "")
|
content := styles.Get("textContent").String()
|
||||||
|
styles.Set("textContent", content+"\n"+bridge.prepareCSS(css))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *wasmBridge) setAnimationCSS(css string) {
|
||||||
|
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
||||||
|
styles.Set("textContent", bridge.prepareCSS(css))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
||||||
|
@ -276,3 +275,6 @@ func (bridge *wasmBridge) answerReceived(answer DataObject) {
|
||||||
func (bridge *wasmBridge) remoteAddr() string {
|
func (bridge *wasmBridge) remoteAddr() string {
|
||||||
return "localhost"
|
return "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *wasmBridge) sendResponse() {
|
||||||
|
}
|
||||||
|
|
314
webBridge.go
314
webBridge.go
|
@ -12,16 +12,30 @@ import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type webBridge struct {
|
||||||
|
answer map[int]chan DataObject
|
||||||
|
answerID int
|
||||||
|
answerMutex sync.Mutex
|
||||||
|
writeMutex sync.Mutex
|
||||||
|
closed bool
|
||||||
|
canvasBuffer strings.Builder
|
||||||
|
canvasVarNumber int
|
||||||
|
updateScripts map[string]*strings.Builder
|
||||||
|
writeMessage func(string) bool
|
||||||
|
callFuncImmediately func(funcName string, args ...any) bool
|
||||||
|
}
|
||||||
|
|
||||||
type wsBridge struct {
|
type wsBridge struct {
|
||||||
conn *websocket.Conn
|
webBridge
|
||||||
answer map[int]chan DataObject
|
conn *websocket.Conn
|
||||||
answerID int
|
}
|
||||||
answerMutex sync.Mutex
|
|
||||||
closed bool
|
type httpBridge struct {
|
||||||
buffer strings.Builder
|
webBridge
|
||||||
canvasBuffer strings.Builder
|
responseBuffer strings.Builder
|
||||||
canvasVarNumber int
|
response chan string
|
||||||
updateScripts map[string]*strings.Builder
|
remoteAddress string
|
||||||
|
//conn *websocket.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
type canvasVar struct {
|
type canvasVar struct {
|
||||||
|
@ -33,7 +47,7 @@ var upgrader = websocket.Upgrader{
|
||||||
WriteBufferSize: 8096,
|
WriteBufferSize: 8096,
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
|
func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge {
|
||||||
conn, err := upgrader.Upgrade(w, req, nil)
|
conn, err := upgrader.Upgrade(w, req, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorLog(err.Error())
|
ErrorLog(err.Error())
|
||||||
|
@ -41,41 +55,84 @@ func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge := new(wsBridge)
|
bridge := new(wsBridge)
|
||||||
bridge.answerID = 1
|
bridge.initBridge()
|
||||||
bridge.answer = make(map[int]chan DataObject)
|
|
||||||
bridge.conn = conn
|
bridge.conn = conn
|
||||||
bridge.closed = false
|
bridge.writeMessage = func(script string) bool {
|
||||||
bridge.updateScripts = map[string]*strings.Builder{}
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog("🖥️ <- " + script)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bridge.conn == nil {
|
||||||
|
ErrorLog("No connection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.writeMutex.Lock()
|
||||||
|
err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script))
|
||||||
|
bridge.writeMutex.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
bridge.callFuncImmediately = bridge.callFunc
|
||||||
return bridge
|
return bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) close() {
|
func createHttpBridge(req *http.Request) *httpBridge {
|
||||||
bridge.closed = true
|
bridge := new(httpBridge)
|
||||||
bridge.conn.Close()
|
bridge.initBridge()
|
||||||
|
bridge.response = make(chan string, 10)
|
||||||
|
bridge.writeMessage = func(script string) bool {
|
||||||
|
if script != "" {
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bridge.responseBuffer.Len() > 0 {
|
||||||
|
bridge.responseBuffer.WriteRune('\n')
|
||||||
|
}
|
||||||
|
bridge.responseBuffer.WriteString(script)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
bridge.callFuncImmediately = bridge.callImmediately
|
||||||
|
bridge.remoteAddress = req.RemoteAddr
|
||||||
|
return bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) startUpdateScript(htmlID string) bool {
|
func (bridge *webBridge) initBridge() {
|
||||||
|
bridge.answerID = 1
|
||||||
|
bridge.answer = make(map[int]chan DataObject)
|
||||||
|
bridge.closed = false
|
||||||
|
bridge.updateScripts = map[string]*strings.Builder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *webBridge) startUpdateScript(htmlID string) bool {
|
||||||
if _, ok := bridge.updateScripts[htmlID]; ok {
|
if _, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
bridge.updateScripts[htmlID] = buffer
|
bridge.updateScripts[htmlID] = buffer
|
||||||
buffer.WriteString("var element = document.getElementById('")
|
buffer.WriteString("{\nlet element = document.getElementById('")
|
||||||
buffer.WriteString(htmlID)
|
buffer.WriteString(htmlID)
|
||||||
buffer.WriteString("');\nif (element) {\n")
|
buffer.WriteString("');\nif (element) {\n")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) finishUpdateScript(htmlID string) {
|
func (bridge *webBridge) finishUpdateScript(htmlID string) {
|
||||||
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
buffer.WriteString("scanElementsSize();\n}\n")
|
buffer.WriteString("scanElementsSize();\n}\n}\n")
|
||||||
bridge.writeMessage(buffer.String())
|
bridge.writeMessage(buffer.String())
|
||||||
|
|
||||||
freeStringBuilder(buffer)
|
freeStringBuilder(buffer)
|
||||||
delete(bridge.updateScripts, htmlID)
|
delete(bridge.updateScripts, htmlID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) argToString(arg any) (string, bool) {
|
func (bridge *webBridge) argToString(arg any) (string, bool) {
|
||||||
switch arg := arg.(type) {
|
switch arg := arg.(type) {
|
||||||
case string:
|
case string:
|
||||||
arg = strings.ReplaceAll(arg, "\\", `\\`)
|
arg = strings.ReplaceAll(arg, "\\", `\\`)
|
||||||
|
@ -150,43 +207,44 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) callFunc(funcName string, args ...any) bool {
|
func (bridge *webBridge) callFuncScript(funcName string, args ...any) (string, bool) {
|
||||||
bridge.buffer.Reset()
|
buffer := allocStringBuilder()
|
||||||
bridge.buffer.WriteString(funcName)
|
defer freeStringBuilder(buffer)
|
||||||
bridge.buffer.WriteRune('(')
|
|
||||||
|
buffer.WriteString(funcName)
|
||||||
|
buffer.WriteRune('(')
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
argText, ok := bridge.argToString(arg)
|
argText, ok := bridge.argToString(arg)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
bridge.buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
bridge.buffer.WriteString(argText)
|
buffer.WriteString(argText)
|
||||||
}
|
}
|
||||||
bridge.buffer.WriteString(");")
|
buffer.WriteString(");")
|
||||||
|
|
||||||
funcText := bridge.buffer.String()
|
return buffer.String(), true
|
||||||
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) {
|
func (bridge *webBridge) callFunc(funcName string, args ...any) bool {
|
||||||
|
if funcText, ok := bridge.callFuncScript(funcName, args...); ok {
|
||||||
|
return bridge.writeMessage(funcText)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *webBridge) updateInnerHTML(htmlID, html string) {
|
||||||
bridge.callFunc("updateInnerHTML", htmlID, html)
|
bridge.callFunc("updateInnerHTML", htmlID, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) {
|
func (bridge *webBridge) appendToInnerHTML(htmlID, html string) {
|
||||||
bridge.callFunc("appendToInnerHTML", htmlID, html)
|
bridge.callFunc("appendToInnerHTML", htmlID, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) {
|
func (bridge *webBridge) updateCSSProperty(htmlID, property, value string) {
|
||||||
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
buffer.WriteString(`element.style['`)
|
buffer.WriteString(`element.style['`)
|
||||||
buffer.WriteString(property)
|
buffer.WriteString(property)
|
||||||
|
@ -198,7 +256,7 @@ func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) updateProperty(htmlID, property string, value any) {
|
func (bridge *webBridge) updateProperty(htmlID, property string, value any) {
|
||||||
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
if val, ok := bridge.argToString(value); ok {
|
if val, ok := bridge.argToString(value); ok {
|
||||||
buffer.WriteString(`element.setAttribute('`)
|
buffer.WriteString(`element.setAttribute('`)
|
||||||
|
@ -212,7 +270,7 @@ func (bridge *wsBridge) updateProperty(htmlID, property string, value any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) removeProperty(htmlID, property string) {
|
func (bridge *webBridge) removeProperty(htmlID, property string) {
|
||||||
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
buffer.WriteString(`if (element.hasAttribute('`)
|
buffer.WriteString(`if (element.hasAttribute('`)
|
||||||
buffer.WriteString(property)
|
buffer.WriteString(property)
|
||||||
|
@ -224,28 +282,34 @@ func (bridge *wsBridge) removeProperty(htmlID, property string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) addAnimationCSS(css string) {
|
func (bridge *webBridge) appendAnimationCSS(css string) {
|
||||||
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
|
//bridge.callFunc("appendAnimationCSS", css)
|
||||||
if (styles) {
|
bridge.writeMessage(`{
|
||||||
styles.textContent += '` + css + `';
|
let styles = document.getElementById('ruiAnimations');
|
||||||
|
if (styles) {
|
||||||
|
styles.textContent += '` + css + `';
|
||||||
|
}
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) clearAnimation() {
|
func (bridge *webBridge) setAnimationCSS(css string) {
|
||||||
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
|
//bridge.callFunc("setAnimationCSS", css)
|
||||||
if (styles) {
|
bridge.writeMessage(`{
|
||||||
styles.textContent = '';
|
let styles = document.getElementById('ruiAnimations');
|
||||||
|
if (styles) {
|
||||||
|
styles.textContent = '` + css + `';
|
||||||
|
}
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) canvasStart(htmlID string) {
|
func (bridge *webBridge) canvasStart(htmlID string) {
|
||||||
bridge.canvasBuffer.Reset()
|
bridge.canvasBuffer.Reset()
|
||||||
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
|
bridge.canvasBuffer.WriteString("{\nconst ctx = getCanvasContext('")
|
||||||
bridge.canvasBuffer.WriteString(htmlID)
|
bridge.canvasBuffer.WriteString(htmlID)
|
||||||
bridge.canvasBuffer.WriteString(`');`)
|
bridge.canvasBuffer.WriteString(`');`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) {
|
func (bridge *webBridge) callCanvasFunc(funcName string, args ...any) {
|
||||||
bridge.canvasBuffer.WriteString("\nctx.")
|
bridge.canvasBuffer.WriteString("\nctx.")
|
||||||
bridge.canvasBuffer.WriteString(funcName)
|
bridge.canvasBuffer.WriteString(funcName)
|
||||||
bridge.canvasBuffer.WriteRune('(')
|
bridge.canvasBuffer.WriteRune('(')
|
||||||
|
@ -259,7 +323,7 @@ func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) {
|
||||||
bridge.canvasBuffer.WriteString(");")
|
bridge.canvasBuffer.WriteString(");")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) updateCanvasProperty(property string, value any) {
|
func (bridge *webBridge) updateCanvasProperty(property string, value any) {
|
||||||
bridge.canvasBuffer.WriteString("\nctx.")
|
bridge.canvasBuffer.WriteString("\nctx.")
|
||||||
bridge.canvasBuffer.WriteString(property)
|
bridge.canvasBuffer.WriteString(property)
|
||||||
bridge.canvasBuffer.WriteString(" = ")
|
bridge.canvasBuffer.WriteString(" = ")
|
||||||
|
@ -268,10 +332,10 @@ func (bridge *wsBridge) updateCanvasProperty(property string, value any) {
|
||||||
bridge.canvasBuffer.WriteString(";")
|
bridge.canvasBuffer.WriteString(";")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any {
|
func (bridge *webBridge) createCanvasVar(funcName string, args ...any) any {
|
||||||
bridge.canvasVarNumber++
|
bridge.canvasVarNumber++
|
||||||
result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)}
|
result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)}
|
||||||
bridge.canvasBuffer.WriteString("\nvar ")
|
bridge.canvasBuffer.WriteString("\nlet ")
|
||||||
bridge.canvasBuffer.WriteString(result.name)
|
bridge.canvasBuffer.WriteString(result.name)
|
||||||
bridge.canvasBuffer.WriteString(" = ctx.")
|
bridge.canvasBuffer.WriteString(" = ctx.")
|
||||||
bridge.canvasBuffer.WriteString(funcName)
|
bridge.canvasBuffer.WriteString(funcName)
|
||||||
|
@ -287,7 +351,7 @@ func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
|
func (bridge *webBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
|
||||||
varName, ok := v.(canvasVar)
|
varName, ok := v.(canvasVar)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -307,7 +371,7 @@ func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
|
||||||
bridge.canvasBuffer.WriteString(");")
|
bridge.canvasBuffer.WriteString(");")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
|
func (bridge *webBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
|
||||||
|
|
||||||
bridge.canvasBuffer.WriteString("\nimg = images.get('")
|
bridge.canvasBuffer.WriteString("\nimg = images.get('")
|
||||||
bridge.canvasBuffer.WriteString(url)
|
bridge.canvasBuffer.WriteString(url)
|
||||||
|
@ -328,51 +392,12 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam
|
||||||
bridge.canvasBuffer.WriteString(");\n}")
|
bridge.canvasBuffer.WriteString(");\n}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) canvasFinish() {
|
func (bridge *webBridge) canvasFinish() {
|
||||||
bridge.canvasBuffer.WriteString("\n")
|
bridge.canvasBuffer.WriteString("\n}\n")
|
||||||
script := bridge.canvasBuffer.String()
|
bridge.writeMessage(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) {
|
func (bridge *webBridge) remoteValue(funcName string, args ...any) (DataObject, 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()
|
bridge.answerMutex.Lock()
|
||||||
answerID := bridge.answerID
|
answerID := bridge.answerID
|
||||||
bridge.answerID++
|
bridge.answerID++
|
||||||
|
@ -381,36 +406,36 @@ func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||||||
answer := make(chan DataObject)
|
answer := make(chan DataObject)
|
||||||
bridge.answer[answerID] = answer
|
bridge.answer[answerID] = answer
|
||||||
|
|
||||||
if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) {
|
funcArgs := append([]any{answerID}, args...)
|
||||||
data := <-answer
|
var result DataObject = nil
|
||||||
result.Width = dataFloatProperty(data, "width")
|
ok := bridge.callFuncImmediately(funcName, funcArgs...)
|
||||||
|
if ok {
|
||||||
|
result = <-answer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(answer)
|
||||||
delete(bridge.answer, answerID)
|
delete(bridge.answer, answerID)
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
|
||||||
|
result := TextMetrics{}
|
||||||
|
if data, ok := bridge.remoteValue("canvasTextMetrics", htmlID, font, text); ok {
|
||||||
|
result.Width = dataFloatProperty(data, "width")
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string {
|
func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
|
||||||
bridge.answerMutex.Lock()
|
if data, ok := bridge.remoteValue("getPropertyValue", htmlID, name); ok {
|
||||||
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 {
|
if value, ok := data.PropertyValue("value"); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(bridge.answer, answerID)
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) answerReceived(answer DataObject) {
|
func (bridge *webBridge) answerReceived(answer DataObject) {
|
||||||
if text, ok := answer.PropertyValue("answerID"); ok {
|
if text, ok := answer.PropertyValue("answerID"); ok {
|
||||||
if id, err := strconv.Atoi(text); err == nil {
|
if id, err := strconv.Atoi(text); err == nil {
|
||||||
if chanel, ok := bridge.answer[id]; ok {
|
if chanel, ok := bridge.answer[id]; ok {
|
||||||
|
@ -427,6 +452,55 @@ func (bridge *wsBridge) answerReceived(answer DataObject) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *wsBridge) close() {
|
||||||
|
bridge.closed = true
|
||||||
|
defer bridge.conn.Close()
|
||||||
|
bridge.conn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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) sendResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
func (bridge *wsBridge) remoteAddr() string {
|
func (bridge *wsBridge) remoteAddr() string {
|
||||||
return bridge.conn.RemoteAddr().String()
|
return bridge.conn.RemoteAddr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bridge *httpBridge) close() {
|
||||||
|
bridge.closed = true
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *httpBridge) callImmediately(funcName string, args ...any) bool {
|
||||||
|
if funcText, ok := bridge.callFuncScript(funcName, args...); ok {
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog("Run func: " + funcText)
|
||||||
|
}
|
||||||
|
bridge.response <- funcText
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *httpBridge) sendResponse() {
|
||||||
|
bridge.writeMutex.Lock()
|
||||||
|
text := bridge.responseBuffer.String()
|
||||||
|
bridge.responseBuffer.Reset()
|
||||||
|
bridge.writeMutex.Unlock()
|
||||||
|
bridge.response <- text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *httpBridge) remoteAddr() string {
|
||||||
|
return bridge.remoteAddress
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue