mirror of https://github.com/anoshenko/rui.git
Compare commits
No commits in common. "918fbf347368b141c0d5f54e3c689bfaab07b038" and "8268d6ba3cb464cce209a245d1e39386265fcde0" have entirely different histories.
918fbf3473
...
8268d6ba3c
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,14 +1,3 @@
|
||||||
# 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,10 +2465,9 @@ 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" and "cell-vertical-self-align" properties
|
### "cell-vertical-align" property
|
||||||
|
|
||||||
The "cell-vertical-align" int property (constant CellVerticalAlign) sets the default vertical alignment of children
|
The "cell-vertical-align" property (constant CellVerticalAlign) of type int sets the vertical alignment of children within the cell they are occupying. Valid values:
|
||||||
within the cell they are occupying. Valid values:
|
|
||||||
|
|
||||||
| Value | Constant | Name | Alignment |
|
| Value | Constant | Name | Alignment |
|
||||||
|:-----:|--------------|-----------|---------------------|
|
|:-----:|--------------|-----------|---------------------|
|
||||||
|
@ -2479,13 +2478,9 @@ within the cell they are occupying. Valid values:
|
||||||
|
|
||||||
The default value is StretchAlign (3)
|
The default value is StretchAlign (3)
|
||||||
|
|
||||||
The "cell-vertical-self-align" int property (constant CellVerticalAlign) sets the vertical alignment of children
|
### "cell-horizontal-align" property
|
||||||
within the cell they are occupying. This property should be set not for the grid, but for the children.
|
|
||||||
|
|
||||||
### "cell-horizontal-align" and "cell-horizontal-self-align" properties
|
The "cell-horizontal-align" property (constant CellHorizontalAlign) of type int sets the horizontal alignment of children within the occupied cell. Valid values:
|
||||||
|
|
||||||
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 |
|
||||||
|:-----:|--------------|-----------|--------------------|
|
|:-----:|--------------|-----------|--------------------|
|
||||||
|
@ -2496,9 +2491,6 @@ of children within the occupied cell. Valid values:
|
||||||
|
|
||||||
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,9 +20,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -31,16 +28,11 @@ 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]sessionInfo
|
sessions map[int]Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) getStartPage() string {
|
func (app *application) getStartPage() string {
|
||||||
|
@ -48,26 +40,14 @@ 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)
|
getStartPage(buffer, app.params, socketScripts)
|
||||||
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.session.close()
|
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)
|
||||||
|
@ -89,12 +69,7 @@ func (app *application) nextSessionID() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) removeSession(id int) {
|
func (app *application) removeSession(id int) {
|
||||||
if info, ok := app.sessions[id]; ok {
|
delete(app.sessions, id)
|
||||||
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) {
|
||||||
|
@ -104,11 +79,6 @@ 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 "/":
|
||||||
|
@ -116,20 +86,10 @@ 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] == '/' {
|
||||||
|
@ -144,87 +104,7 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSessionIDCookie(w http.ResponseWriter, sessionID int) {
|
func (app *application) socketReader(bridge webBridge) {
|
||||||
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)
|
||||||
|
|
||||||
|
@ -236,7 +116,7 @@ func (app *application) socketReader(bridge *wsBridge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ProtocolInDebugLog {
|
if ProtocolInDebugLog {
|
||||||
DebugLog("🖥️ -> " + message)
|
DebugLog(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj := ParseDataText(message); obj != nil {
|
if obj := ParseDataText(message); obj != nil {
|
||||||
|
@ -244,7 +124,7 @@ func (app *application) socketReader(bridge *wsBridge) {
|
||||||
switch command {
|
switch command {
|
||||||
case "startSession":
|
case "startSession":
|
||||||
answer := ""
|
answer := ""
|
||||||
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
|
if session, answer = app.startSession(obj, events, bridge); session != nil {
|
||||||
if !bridge.writeMessage(answer) {
|
if !bridge.writeMessage(answer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -253,18 +133,22 @@ func (app *application) socketReader(bridge *wsBridge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 info, ok := app.sessions[sessionID]; ok {
|
if session = app.sessions[sessionID]; session != nil {
|
||||||
session = info.session
|
|
||||||
session.setBridge(events, bridge)
|
session.setBridge(events, bridge)
|
||||||
|
answer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(answer)
|
||||||
|
|
||||||
go sessionEventHandler(session, events, bridge)
|
session.writeInitScript(answer)
|
||||||
|
if !bridge.writeMessage(answer.String()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
session.onReconnect()
|
session.onReconnect()
|
||||||
} else {
|
go sessionEventHandler(session, events, bridge)
|
||||||
DebugLogF("Session #%d not exists", sessionID)
|
return
|
||||||
}
|
}
|
||||||
|
DebugLogF("Session #%d not exists", sessionID)
|
||||||
} else {
|
} else {
|
||||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||||
}
|
}
|
||||||
|
@ -272,31 +156,25 @@ func (app *application) socketReader(bridge *wsBridge) {
|
||||||
ErrorLog(`"session" key not found`)
|
ErrorLog(`"session" key not found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if session == nil {
|
bridge.writeMessage("restartSession();")
|
||||||
/* answer := ""
|
|
||||||
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
|
case "answer":
|
||||||
if !bridge.writeMessage(answer) {
|
session.handleAnswer(obj)
|
||||||
return
|
|
||||||
}
|
case "imageLoaded":
|
||||||
session.onStart()
|
session.imageManager().imageLoaded(obj, session)
|
||||||
go sessionEventHandler(session, events, bridge)
|
|
||||||
bridge.writeMessage("restartSession();")
|
case "imageError":
|
||||||
}
|
session.imageManager().imageLoadError(obj, session)
|
||||||
*/
|
|
||||||
bridge.writeMessage("reloadPage();")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if !session.handleAnswer(command, obj) {
|
events <- obj
|
||||||
events <- obj
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sessionEventHandler(session Session, events chan DataObject, bridge bridge) {
|
func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) {
|
||||||
for {
|
for {
|
||||||
data := <-events
|
data := <-events
|
||||||
|
|
||||||
|
@ -316,9 +194,7 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *application) startSession(params DataObject, events chan DataObject,
|
func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) {
|
||||||
bridge bridge, response chan string) (Session, string) {
|
|
||||||
|
|
||||||
if app.createContentFunc == nil {
|
if app.createContentFunc == nil {
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
@ -329,10 +205,7 @@ func (app *application) startSession(params DataObject, events chan DataObject,
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
app.sessions[session.ID()] = sessionInfo{
|
app.sessions[session.ID()] = session
|
||||||
session: session,
|
|
||||||
response: response,
|
|
||||||
}
|
|
||||||
|
|
||||||
answer := allocStringBuilder()
|
answer := allocStringBuilder()
|
||||||
defer freeStringBuilder(answer)
|
defer freeStringBuilder(answer)
|
||||||
|
@ -356,7 +229,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]sessionInfo{}
|
app.sessions = map[int]Session{}
|
||||||
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 bridge
|
bridge webBridge
|
||||||
close chan DataObject
|
close chan DataObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,12 +25,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -50,10 +44,17 @@ 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:
|
||||||
if !app.session.handleAnswer(command, obj) {
|
app.session.handleEvent(command, obj)
|
||||||
app.session.handleEvent(command, obj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
app_post.js
25
app_post.js
|
@ -1,25 +0,0 @@
|
||||||
|
|
||||||
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,71 +1,51 @@
|
||||||
let socket
|
var socket
|
||||||
|
var socketUrl
|
||||||
|
|
||||||
function sendMessage(message) {
|
function sendMessage(message) {
|
||||||
if (!socket) {
|
if (socket) {
|
||||||
createSocket(function() {
|
socket.send(message)
|
||||||
sendMessage( "reconnect{session=" + sessionID + "}" );
|
|
||||||
if (!windowFocus) {
|
|
||||||
windowFocus = true;
|
|
||||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
|
||||||
}
|
|
||||||
socket.send(message);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
socket.send(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSocket(onopen) {
|
window.onload = function() {
|
||||||
let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
||||||
socketUrl += document.location.hostname
|
socketUrl += document.location.hostname
|
||||||
const port = document.location.port
|
var 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 = onopen;
|
socket.onopen = socketOpen;
|
||||||
socket.onclose = onSocketClose;
|
socket.onclose = socketClose;
|
||||||
socket.onerror = onSocketError;
|
socket.onerror = socketError;
|
||||||
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 closeSocket() {
|
function socketOpen() {
|
||||||
if (socket) {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = createSocket(function() {
|
|
||||||
sendMessage( sessionInfo() );
|
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() {
|
function socketReopen() {
|
||||||
sendMessage( "reconnect{session=" + sessionID + "}" );
|
sendMessage( "reconnect{session=" + sessionID + "}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketReconnect() {
|
function socketReconnect() {
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
createSocket(onSocketReopen);
|
socket = new WebSocket(socketUrl);
|
||||||
|
socket.onopen = socketReopen;
|
||||||
|
socket.onclose = socketClose;
|
||||||
|
socket.onerror = socketError;
|
||||||
|
socket.onmessage = function(event) {
|
||||||
|
window.execScript ? window.execScript(event.data) : window.eval(event.data);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSocketClose(event) {
|
function socketClose(event) {
|
||||||
console.log("socket closed")
|
console.log("socket closed")
|
||||||
socket = null;
|
socket = null;
|
||||||
if (!event.wasClean && windowFocus) {
|
if (!event.wasClean && windowFocus) {
|
||||||
|
@ -73,6 +53,15 @@ function onSocketClose(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSocketError(error) {
|
function socketError(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.onfocus = function(event) {
|
||||||
|
windowFocus = true
|
||||||
|
if (!socket) {
|
||||||
|
socketReconnect()
|
||||||
|
} else {
|
||||||
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -57,10 +57,9 @@ button {
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
padding: 4px;
|
padding: 1px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
resize: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul:focus {
|
ul:focus {
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
|
|
||||||
window.onfocus = function() {
|
window.onfocus = function(event) {
|
||||||
windowFocus = true
|
windowFocus = true
|
||||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeSocket() {
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,37 +24,23 @@ 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) {
|
func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) {
|
||||||
buffer.WriteString(`<head>
|
buffer.WriteString(`<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>`)
|
<title>`)
|
||||||
|
@ -82,7 +67,11 @@ func getStartPage(buffer *strings.Builder, params AppParams) {
|
||||||
buffer.WriteString(appStyles)
|
buffer.WriteString(appStyles)
|
||||||
buffer.WriteString(`</style>
|
buffer.WriteString(`</style>
|
||||||
<style id="ruiAnimations"></style>
|
<style id="ruiAnimations"></style>
|
||||||
<script src="/script.js"></script>
|
<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,9 +1,6 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"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:
|
||||||
|
@ -64,8 +61,6 @@ 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
|
||||||
|
@ -244,20 +239,3 @@ 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,16 +336,3 @@ 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,33 +224,6 @@ 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]
|
||||||
|
@ -397,18 +370,6 @@ 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"
|
||||||
}
|
}
|
||||||
|
@ -649,17 +610,3 @@ 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,7 +34,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
@ -154,6 +153,13 @@ 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,6 +188,10 @@ 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,7 +40,6 @@ 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){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +303,13 @@ 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,7 +39,6 @@ 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){}
|
||||||
|
@ -371,6 +370,13 @@ 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,7 +58,6 @@ 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"
|
||||||
}
|
}
|
||||||
|
@ -467,6 +466,13 @@ 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))
|
||||||
|
@ -511,30 +517,19 @@ 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 {
|
||||||
var ok bool
|
if text, ok := stringProperty(view, Hint, view.Session()); ok {
|
||||||
text, ok = stringProperty(view, Hint, view.Session())
|
return text
|
||||||
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 = session.resolveConstants(text); !ok {
|
if text, ok = view.Session().resolveConstants(text); ok {
|
||||||
text = ""
|
return 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,7 +83,6 @@ 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){}
|
||||||
|
@ -261,6 +260,13 @@ 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,6 +2,4 @@ module github.com/anoshenko/rui
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.1
|
require github.com/gorilla/websocket v1.5.0
|
||||||
|
|
||||||
require golang.org/x/net v0.17.0 // indirect
|
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -1,4 +1,2 @@
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
||||||
|
|
|
@ -5,44 +5,6 @@ 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
|
||||||
|
|
|
@ -42,7 +42,7 @@ Example for echo:
|
||||||
func NewHandler(urlPrefix string, createContentFunc func(Session) SessionContent, params AppParams) *httpHandler {
|
func NewHandler(urlPrefix string, createContentFunc func(Session) SessionContent, params AppParams) *httpHandler {
|
||||||
app := new(application)
|
app := new(application)
|
||||||
app.params = params
|
app.params = params
|
||||||
app.sessions = map[int]sessionInfo{}
|
app.sessions = map[int]Session{}
|
||||||
app.createContentFunc = createContentFunc
|
app.createContentFunc = createContentFunc
|
||||||
apps = append(apps, app)
|
apps = append(apps, app)
|
||||||
|
|
||||||
|
|
5
image.go
5
image.go
|
@ -80,11 +80,10 @@ 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) {
|
func (manager *imageManager) imageLoaded(obj DataObject, session Session) {
|
||||||
if manager.images == nil {
|
if manager.images == nil {
|
||||||
manager.images = make(map[string]*imageData)
|
manager.images = make(map[string]*imageData)
|
||||||
return
|
return
|
||||||
|
@ -110,7 +109,7 @@ func (manager *imageManager) imageLoaded(obj DataObject) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *imageManager) imageLoadError(obj DataObject) {
|
func (manager *imageManager) imageLoadError(obj DataObject, session Session) {
|
||||||
if manager.images == nil {
|
if manager.images == nil {
|
||||||
manager.images = make(map[string]*imageData)
|
manager.images = make(map[string]*imageData)
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,13 +2,8 @@ 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,22 +7,16 @@ 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,25 +11,20 @@ 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"
|
||||||
|
@ -594,7 +589,7 @@ func (listView *listViewData) getItemFrames() []Frame {
|
||||||
return listView.itemFrame
|
return listView.itemFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) itemAlign(buffer *strings.Builder) {
|
func (listView *listViewData) itemAlign(self View, 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: ")
|
||||||
|
@ -610,7 +605,7 @@ func (listView *listViewData) itemAlign(buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) itemSize(buffer *strings.Builder) {
|
func (listView *listViewData) itemSize(self View, 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()))
|
||||||
|
@ -624,14 +619,14 @@ func (listView *listViewData) itemSize(buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
|
func (listView *listViewData) getDivs(self View, 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(contentBuilder)
|
listView.itemAlign(self, contentBuilder)
|
||||||
|
|
||||||
onDivBuilder := allocStringBuilder()
|
onDivBuilder := allocStringBuilder()
|
||||||
defer freeStringBuilder(onDivBuilder)
|
defer freeStringBuilder(onDivBuilder)
|
||||||
|
@ -686,7 +681,7 @@ func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign i
|
||||||
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
|
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign int) string {
|
func (listView *listViewData) checkboxItemDiv(self View, checkbox, hCheckboxAlign, vCheckboxAlign int) string {
|
||||||
itemStyleBuilder := allocStringBuilder()
|
itemStyleBuilder := allocStringBuilder()
|
||||||
defer freeStringBuilder(itemStyleBuilder)
|
defer freeStringBuilder(itemStyleBuilder)
|
||||||
|
|
||||||
|
@ -765,15 +760,15 @@ func (listView *listViewData) currentInactiveStyle() string {
|
||||||
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
|
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox int) {
|
func (listView *listViewData) checkboxSubviews(self View, 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(checkbox, hCheckboxAlign, vCheckboxAlign)
|
itemDiv := listView.checkboxItemDiv(self, checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
|
onDiv, offDiv, contentDiv := listView.getDivs(self, checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
|
|
||||||
current := GetCurrent(listView)
|
current := GetCurrent(listView)
|
||||||
checkedItems := GetListViewCheckedItems(listView)
|
checkedItems := GetListViewCheckedItems(listView)
|
||||||
|
@ -789,7 +784,7 @@ func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox
|
||||||
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(buffer)
|
listView.itemSize(self, buffer)
|
||||||
if !listView.adapter.IsListItemEnabled(i) {
|
if !listView.adapter.IsListItemEnabled(i) {
|
||||||
buffer.WriteString(`" data-disabled="1`)
|
buffer.WriteString(`" data-disabled="1`)
|
||||||
}
|
}
|
||||||
|
@ -820,7 +815,7 @@ func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) {
|
func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Builder) {
|
||||||
count := listView.adapter.ListSize()
|
count := listView.adapter.ListSize()
|
||||||
listViewID := listView.htmlID()
|
listViewID := listView.htmlID()
|
||||||
|
|
||||||
|
@ -829,8 +824,8 @@ func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) {
|
||||||
|
|
||||||
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(itemStyleBuilder)
|
listView.itemAlign(self, itemStyleBuilder)
|
||||||
listView.itemSize(itemStyleBuilder)
|
listView.itemSize(self, itemStyleBuilder)
|
||||||
|
|
||||||
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
|
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
|
||||||
itemStyle := itemStyleBuilder.String()
|
itemStyle := itemStyleBuilder.String()
|
||||||
|
@ -870,12 +865,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(checkbox, hCheckboxAlign, vCheckboxAlign)
|
onDiv, offDiv, contentDiv := listView.getDivs(listView, checkbox, hCheckboxAlign, vCheckboxAlign)
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
buffer.WriteString(listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign))
|
buffer.WriteString(listView.checkboxItemDiv(listView, checkbox, hCheckboxAlign, vCheckboxAlign))
|
||||||
if checked {
|
if checked {
|
||||||
buffer.WriteString(onDiv)
|
buffer.WriteString(onDiv)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1066,9 +1061,9 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
||||||
checkbox := GetListViewCheckbox(listView)
|
checkbox := GetListViewCheckbox(listView)
|
||||||
if checkbox == NoneCheckbox {
|
if checkbox == NoneCheckbox {
|
||||||
listView.noneCheckboxSubviews(buffer)
|
listView.noneCheckboxSubviews(self, buffer)
|
||||||
} else {
|
} else {
|
||||||
listView.checkboxSubviews(buffer, checkbox)
|
listView.checkboxSubviews(self, buffer, checkbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(`</div>`)
|
buffer.WriteString(`</div>`)
|
||||||
|
|
|
@ -13,18 +13,15 @@ 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:
|
||||||
|
@ -35,94 +32,72 @@ 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).
|
||||||
|
@ -130,68 +105,51 @@ 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,32 +82,24 @@ 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,37 +7,17 @@ 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"
|
||||||
// NumberPickerType is the constant for the "number-picker-type" property tag.
|
NumberPickerMin = "number-picker-min"
|
||||||
// The "number-picker-type" int property sets the mode of NumberPicker. It can take the following values:
|
NumberPickerMax = "number-picker-max"
|
||||||
// * NumberEditor (0) - NumberPicker is presented by editor. Default value;
|
NumberPickerStep = "number-picker-step"
|
||||||
// * NumberSlider (1) - NumberPicker is presented by slider. |
|
NumberPickerValue = "number-picker-value"
|
||||||
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
|
||||||
)
|
)
|
||||||
|
@ -67,7 +47,6 @@ 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){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +232,13 @@ 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,19 +11,15 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -72,28 +68,6 @@ 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,8 +666,7 @@ 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" / NoneResize (0), "both" / BothResize (1),
|
// Valid values are "none" (0), "both" (1), horizontal (2), and "vertical" (3)
|
||||||
// "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,16 +331,6 @@ 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,23 +11,22 @@ 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 bridge interface {
|
type webBridge 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,9 +16,10 @@ type bridge 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)
|
||||||
sendResponse()
|
readMessage() (string, bool)
|
||||||
setAnimationCSS(css string)
|
writeMessage(text string) bool
|
||||||
appendAnimationCSS(css string)
|
addAnimationCSS(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)
|
||||||
|
@ -110,14 +111,6 @@ 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
|
||||||
|
|
||||||
|
@ -131,7 +124,7 @@ type Session interface {
|
||||||
nextViewID() string
|
nextViewID() string
|
||||||
styleProperty(styleTag, property string) any
|
styleProperty(styleTag, property string) any
|
||||||
|
|
||||||
setBridge(events chan DataObject, bridge bridge)
|
setBridge(events chan DataObject, bridge webBridge)
|
||||||
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)
|
||||||
|
@ -141,7 +134,6 @@ 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)
|
||||||
|
@ -153,8 +145,7 @@ 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
|
||||||
addToEventsQueue(data DataObject)
|
handleAnswer(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)
|
||||||
|
@ -198,16 +189,13 @@ type sessionData struct {
|
||||||
ignoreUpdates bool
|
ignoreUpdates bool
|
||||||
popups *popupManager
|
popups *popupManager
|
||||||
images *imageManager
|
images *imageManager
|
||||||
bridge bridge
|
bridge webBridge
|
||||||
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 {
|
||||||
|
@ -226,8 +214,6 @@ 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 {
|
||||||
|
@ -251,7 +237,7 @@ func (session *sessionData) ID() int {
|
||||||
return session.sessionID
|
return session.sessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
|
func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) {
|
||||||
session.events = events
|
session.events = events
|
||||||
session.bridge = bridge
|
session.bridge = bridge
|
||||||
}
|
}
|
||||||
|
@ -344,19 +330,23 @@ 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
|
||||||
session.bridge.callFunc("setStyles", css)
|
css = strings.ReplaceAll(css, "\n", `\n`)
|
||||||
|
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 := allocStringBuilder()
|
buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
viewHTML(session.rootView, buffer)
|
viewHTML(session.rootView, buffer)
|
||||||
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
buffer.WriteString("';\nscanElementsSize();")
|
||||||
session.bridge.callFunc("scanElementsSize")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.bridge.writeMessage(buffer.String())
|
||||||
session.updateTooltipConstants()
|
session.updateTooltipConstants()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,21 +447,15 @@ 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.appendAnimationCSS(css)
|
session.bridge.addAnimationCSS(css)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) clearAnimation() {
|
func (session *sessionData) clearAnimation() {
|
||||||
if session.bridge != nil {
|
if session.bridge != nil {
|
||||||
session.bridge.setAnimationCSS("")
|
session.bridge.clearAnimation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,27 +520,8 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) handleAnswer(command string, data DataObject) bool {
|
func (session *sessionData) handleAnswer(data DataObject) {
|
||||||
switch command {
|
session.bridge.answerReceived(data)
|
||||||
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) {
|
||||||
|
@ -677,22 +642,6 @@ 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)
|
||||||
|
|
||||||
|
@ -723,8 +672,6 @@ 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) {
|
||||||
|
@ -822,25 +769,3 @@ 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,7 +1,5 @@
|
||||||
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)
|
||||||
|
@ -52,25 +50,13 @@ 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,33 +22,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,10 +277,6 @@ 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,7 +40,6 @@ 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){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,6 +291,13 @@ 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,11 +8,9 @@ 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,33 +35,24 @@ 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
|
||||||
|
|
||||||
|
@ -74,6 +65,7 @@ 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)
|
||||||
|
|
||||||
|
@ -101,7 +93,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +135,6 @@ 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 {
|
||||||
|
@ -312,6 +302,7 @@ 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 {
|
||||||
|
@ -413,35 +404,7 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Disabled:
|
case Disabled:
|
||||||
tabIndex := GetTabIndex(view, htmlID)
|
updateInnerHTML(view.parentHTMLID(), session)
|
||||||
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:
|
||||||
|
@ -650,8 +613,6 @@ 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
|
||||||
|
|
||||||
|
@ -699,11 +660,8 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cssTag, ok := sizeProperties[tag]; ok {
|
if cssTag, ok := sizeProperties[tag]; ok {
|
||||||
if size, ok := sizeProperty(view, tag, session); ok {
|
size, _ := sizeProperty(view, tag, session)
|
||||||
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session))
|
session.updateCSSProperty(htmlID, cssTag, size.cssString("", session))
|
||||||
} else {
|
|
||||||
session.updateCSSProperty(htmlID, cssTag, "")
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,11 +682,8 @@ func viewPropertyChanged(view *viewData, tag string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if valuesData, ok := enumProperties[tag]; ok && valuesData.cssTag != "" {
|
if valuesData, ok := enumProperties[tag]; ok && valuesData.cssTag != "" {
|
||||||
if n, ok := enumProperty(view, tag, session, 0); ok {
|
n, _ := enumProperty(view, tag, session, 0)
|
||||||
session.updateCSSProperty(htmlID, valuesData.cssTag, valuesData.cssValues[n])
|
session.updateCSSProperty(htmlID, valuesData.cssTag, valuesData.cssValues[n])
|
||||||
} else {
|
|
||||||
session.updateCSSProperty(htmlID, valuesData.cssTag, "")
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,8 +691,6 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -806,22 +759,20 @@ 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('<')
|
||||||
|
@ -849,6 +800,8 @@ 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" `)
|
||||||
|
@ -857,10 +810,12 @@ func viewHTML(view View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !disabled {
|
if !disabled {
|
||||||
if tabIndex := GetTabIndex(view); tabIndex >= 0 {
|
if value, ok := intProperty(view, TabIndex, view.Session(), -1); ok {
|
||||||
buffer.WriteString(`tabindex="`)
|
buffer.WriteString(`tabindex="`)
|
||||||
buffer.WriteString(strconv.Itoa(tabIndex))
|
buffer.WriteString(strconv.Itoa(value))
|
||||||
buffer.WriteString(`" `)
|
buffer.WriteString(`" `)
|
||||||
|
} else if view.Focusable() {
|
||||||
|
buffer.WriteString(`tabindex="0" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
49
viewStyle.go
49
viewStyle.go
|
@ -13,10 +13,8 @@ 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.
|
||||||
|
@ -575,9 +573,6 @@ 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
|
||||||
|
@ -697,7 +692,6 @@ 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)
|
||||||
|
@ -707,7 +701,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("[]")
|
buffer.WriteString("[]\n")
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
writeViewStyle(value[0].Tag(), value[0], buffer, indent)
|
writeViewStyle(value[0].Tag(), value[0], buffer, indent)
|
||||||
|
@ -746,47 +740,6 @@ 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,52 +32,42 @@ 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:
|
||||||
background = []BackgroundElement{value}
|
style.properties[Background] = []BackgroundElement{value}
|
||||||
|
return true
|
||||||
|
|
||||||
case []BackgroundElement:
|
case []BackgroundElement:
|
||||||
background = value
|
style.properties[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 {
|
||||||
background = []BackgroundElement{element}
|
style.properties[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 {
|
||||||
background = []BackgroundElement{element}
|
style.properties[Background] = []BackgroundElement{element}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(background) > 0 {
|
|
||||||
style.properties[Background] = background
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,12 @@ 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) bridge {
|
func createWasmBridge(close chan DataObject) webBridge {
|
||||||
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,6 +102,10 @@ 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:")
|
||||||
|
@ -114,24 +118,21 @@ func (bridge *wasmBridge) writeMessage(script string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) prepareCSS(css string) string {
|
func (bridge *wasmBridge) addAnimationCSS(css 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *wasmBridge) appendAnimationCSS(css string) {
|
|
||||||
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
||||||
content := styles.Get("textContent").String()
|
content := styles.Get("textContent").String()
|
||||||
styles.Set("textContent", content+"\n"+bridge.prepareCSS(css))
|
styles.Set("textContent", content+"\n"+css)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) setAnimationCSS(css string) {
|
func (bridge *wasmBridge) clearAnimation() {
|
||||||
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
|
||||||
styles.Set("textContent", bridge.prepareCSS(css))
|
styles.Set("textContent", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
func (bridge *wasmBridge) canvasStart(htmlID string) {
|
||||||
|
@ -275,6 +276,3 @@ func (bridge *wasmBridge) answerReceived(answer DataObject) {
|
||||||
func (bridge *wasmBridge) remoteAddr() string {
|
func (bridge *wasmBridge) remoteAddr() string {
|
||||||
return "localhost"
|
return "localhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *wasmBridge) sendResponse() {
|
|
||||||
}
|
|
||||||
|
|
316
webBridge.go
316
webBridge.go
|
@ -12,30 +12,16 @@ 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 {
|
||||||
webBridge
|
conn *websocket.Conn
|
||||||
conn *websocket.Conn
|
answer map[int]chan DataObject
|
||||||
}
|
answerID int
|
||||||
|
answerMutex sync.Mutex
|
||||||
type httpBridge struct {
|
closed bool
|
||||||
webBridge
|
buffer strings.Builder
|
||||||
responseBuffer strings.Builder
|
canvasBuffer strings.Builder
|
||||||
response chan string
|
canvasVarNumber int
|
||||||
remoteAddress string
|
updateScripts map[string]*strings.Builder
|
||||||
//conn *websocket.Conn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type canvasVar struct {
|
type canvasVar struct {
|
||||||
|
@ -47,7 +33,7 @@ var upgrader = websocket.Upgrader{
|
||||||
WriteBufferSize: 8096,
|
WriteBufferSize: 8096,
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge {
|
func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
|
||||||
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())
|
||||||
|
@ -55,84 +41,41 @@ func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge := new(wsBridge)
|
bridge := new(wsBridge)
|
||||||
bridge.initBridge()
|
|
||||||
bridge.conn = conn
|
|
||||||
bridge.writeMessage = func(script string) bool {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func createHttpBridge(req *http.Request) *httpBridge {
|
|
||||||
bridge := new(httpBridge)
|
|
||||||
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 *webBridge) initBridge() {
|
|
||||||
bridge.answerID = 1
|
bridge.answerID = 1
|
||||||
bridge.answer = make(map[int]chan DataObject)
|
bridge.answer = make(map[int]chan DataObject)
|
||||||
|
bridge.conn = conn
|
||||||
bridge.closed = false
|
bridge.closed = false
|
||||||
bridge.updateScripts = map[string]*strings.Builder{}
|
bridge.updateScripts = map[string]*strings.Builder{}
|
||||||
|
return bridge
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) startUpdateScript(htmlID string) bool {
|
func (bridge *wsBridge) close() {
|
||||||
|
bridge.closed = true
|
||||||
|
bridge.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *wsBridge) 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("{\nlet element = document.getElementById('")
|
buffer.WriteString("var 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 *webBridge) finishUpdateScript(htmlID string) {
|
func (bridge *wsBridge) finishUpdateScript(htmlID string) {
|
||||||
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
if buffer, ok := bridge.updateScripts[htmlID]; ok {
|
||||||
buffer.WriteString("scanElementsSize();\n}\n}\n")
|
buffer.WriteString("scanElementsSize();\n}\n")
|
||||||
bridge.writeMessage(buffer.String())
|
bridge.writeMessage(buffer.String())
|
||||||
|
|
||||||
freeStringBuilder(buffer)
|
freeStringBuilder(buffer)
|
||||||
delete(bridge.updateScripts, htmlID)
|
delete(bridge.updateScripts, htmlID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) argToString(arg any) (string, bool) {
|
func (bridge *wsBridge) 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, "\\", `\\`)
|
||||||
|
@ -207,44 +150,43 @@ func (bridge *webBridge) argToString(arg any) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) callFuncScript(funcName string, args ...any) (string, bool) {
|
func (bridge *wsBridge) callFunc(funcName string, args ...any) bool {
|
||||||
buffer := allocStringBuilder()
|
bridge.buffer.Reset()
|
||||||
defer freeStringBuilder(buffer)
|
bridge.buffer.WriteString(funcName)
|
||||||
|
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 {
|
||||||
buffer.WriteString(", ")
|
bridge.buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(argText)
|
bridge.buffer.WriteString(argText)
|
||||||
}
|
}
|
||||||
buffer.WriteString(");")
|
bridge.buffer.WriteString(");")
|
||||||
|
|
||||||
return buffer.String(), true
|
funcText := bridge.buffer.String()
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog("Run func: " + funcText)
|
||||||
|
}
|
||||||
|
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) callFunc(funcName string, args ...any) bool {
|
func (bridge *wsBridge) updateInnerHTML(htmlID, html string) {
|
||||||
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 *webBridge) appendToInnerHTML(htmlID, html string) {
|
func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) {
|
||||||
bridge.callFunc("appendToInnerHTML", htmlID, html)
|
bridge.callFunc("appendToInnerHTML", htmlID, html)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) updateCSSProperty(htmlID, property, value string) {
|
func (bridge *wsBridge) 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)
|
||||||
|
@ -256,7 +198,7 @@ func (bridge *webBridge) updateCSSProperty(htmlID, property, value string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) updateProperty(htmlID, property string, value any) {
|
func (bridge *wsBridge) 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('`)
|
||||||
|
@ -270,7 +212,7 @@ func (bridge *webBridge) updateProperty(htmlID, property string, value any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) removeProperty(htmlID, property string) {
|
func (bridge *wsBridge) 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)
|
||||||
|
@ -282,34 +224,28 @@ func (bridge *webBridge) removeProperty(htmlID, property string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) appendAnimationCSS(css string) {
|
func (bridge *wsBridge) addAnimationCSS(css string) {
|
||||||
//bridge.callFunc("appendAnimationCSS", css)
|
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
|
||||||
bridge.writeMessage(`{
|
if (styles) {
|
||||||
let styles = document.getElementById('ruiAnimations');
|
styles.textContent += '` + css + `';
|
||||||
if (styles) {
|
|
||||||
styles.textContent += '` + css + `';
|
|
||||||
}
|
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) setAnimationCSS(css string) {
|
func (bridge *wsBridge) clearAnimation() {
|
||||||
//bridge.callFunc("setAnimationCSS", css)
|
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
|
||||||
bridge.writeMessage(`{
|
if (styles) {
|
||||||
let styles = document.getElementById('ruiAnimations');
|
styles.textContent = '';
|
||||||
if (styles) {
|
|
||||||
styles.textContent = '` + css + `';
|
|
||||||
}
|
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) canvasStart(htmlID string) {
|
func (bridge *wsBridge) canvasStart(htmlID string) {
|
||||||
bridge.canvasBuffer.Reset()
|
bridge.canvasBuffer.Reset()
|
||||||
bridge.canvasBuffer.WriteString("{\nconst ctx = getCanvasContext('")
|
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
|
||||||
bridge.canvasBuffer.WriteString(htmlID)
|
bridge.canvasBuffer.WriteString(htmlID)
|
||||||
bridge.canvasBuffer.WriteString(`');`)
|
bridge.canvasBuffer.WriteString(`');`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) callCanvasFunc(funcName string, args ...any) {
|
func (bridge *wsBridge) 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('(')
|
||||||
|
@ -323,7 +259,7 @@ func (bridge *webBridge) callCanvasFunc(funcName string, args ...any) {
|
||||||
bridge.canvasBuffer.WriteString(");")
|
bridge.canvasBuffer.WriteString(");")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) updateCanvasProperty(property string, value any) {
|
func (bridge *wsBridge) 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(" = ")
|
||||||
|
@ -332,10 +268,10 @@ func (bridge *webBridge) updateCanvasProperty(property string, value any) {
|
||||||
bridge.canvasBuffer.WriteString(";")
|
bridge.canvasBuffer.WriteString(";")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) createCanvasVar(funcName string, args ...any) any {
|
func (bridge *wsBridge) 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("\nlet ")
|
bridge.canvasBuffer.WriteString("\nvar ")
|
||||||
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)
|
||||||
|
@ -351,7 +287,7 @@ func (bridge *webBridge) createCanvasVar(funcName string, args ...any) any {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
|
func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
|
||||||
varName, ok := v.(canvasVar)
|
varName, ok := v.(canvasVar)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -371,7 +307,7 @@ func (bridge *webBridge) callCanvasVarFunc(v any, funcName string, args ...any)
|
||||||
bridge.canvasBuffer.WriteString(");")
|
bridge.canvasBuffer.WriteString(");")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
|
func (bridge *wsBridge) 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)
|
||||||
|
@ -392,12 +328,51 @@ func (bridge *webBridge) callCanvasImageFunc(url string, property string, funcNa
|
||||||
bridge.canvasBuffer.WriteString(");\n}")
|
bridge.canvasBuffer.WriteString(");\n}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) canvasFinish() {
|
func (bridge *wsBridge) canvasFinish() {
|
||||||
bridge.canvasBuffer.WriteString("\n}\n")
|
bridge.canvasBuffer.WriteString("\n")
|
||||||
bridge.writeMessage(bridge.canvasBuffer.String())
|
script := bridge.canvasBuffer.String()
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog("Run script:")
|
||||||
|
DebugLog(script)
|
||||||
|
}
|
||||||
|
if bridge.conn == nil {
|
||||||
|
ErrorLog("No connection")
|
||||||
|
} else if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) remoteValue(funcName string, args ...any) (DataObject, bool) {
|
func (bridge *wsBridge) readMessage() (string, bool) {
|
||||||
|
_, p, err := bridge.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if !bridge.closed {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(p), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *wsBridge) writeMessage(script string) bool {
|
||||||
|
if ProtocolInDebugLog {
|
||||||
|
DebugLog("Run script:")
|
||||||
|
DebugLog(script)
|
||||||
|
}
|
||||||
|
if bridge.conn == nil {
|
||||||
|
ErrorLog("No connection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
|
||||||
|
result := TextMetrics{}
|
||||||
|
|
||||||
bridge.answerMutex.Lock()
|
bridge.answerMutex.Lock()
|
||||||
answerID := bridge.answerID
|
answerID := bridge.answerID
|
||||||
bridge.answerID++
|
bridge.answerID++
|
||||||
|
@ -406,36 +381,36 @@ func (bridge *webBridge) remoteValue(funcName string, args ...any) (DataObject,
|
||||||
answer := make(chan DataObject)
|
answer := make(chan DataObject)
|
||||||
bridge.answer[answerID] = answer
|
bridge.answer[answerID] = answer
|
||||||
|
|
||||||
funcArgs := append([]any{answerID}, args...)
|
if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) {
|
||||||
var result DataObject = nil
|
data := <-answer
|
||||||
ok := bridge.callFuncImmediately(funcName, funcArgs...)
|
|
||||||
if ok {
|
|
||||||
result = <-answer
|
|
||||||
}
|
|
||||||
|
|
||||||
close(answer)
|
|
||||||
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")
|
result.Width = dataFloatProperty(data, "width")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(bridge.answer, answerID)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
|
func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string {
|
||||||
if data, ok := bridge.remoteValue("getPropertyValue", htmlID, name); ok {
|
bridge.answerMutex.Lock()
|
||||||
|
answerID := bridge.answerID
|
||||||
|
bridge.answerID++
|
||||||
|
bridge.answerMutex.Unlock()
|
||||||
|
|
||||||
|
answer := make(chan DataObject)
|
||||||
|
bridge.answer[answerID] = answer
|
||||||
|
|
||||||
|
if bridge.callFunc("getPropertyValue", answerID, htmlID, name) {
|
||||||
|
data := <-answer
|
||||||
if value, ok := data.PropertyValue("value"); ok {
|
if value, ok := data.PropertyValue("value"); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(bridge.answer, answerID)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *webBridge) answerReceived(answer DataObject) {
|
func (bridge *wsBridge) 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 {
|
||||||
|
@ -452,55 +427,6 @@ func (bridge *webBridge) 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