mirror of https://github.com/anoshenko/rui.git
1001 lines
26 KiB
Go
1001 lines
26 KiB
Go
package rui
|
||
|
||
import (
|
||
"fmt"
|
||
"iter"
|
||
"maps"
|
||
"net/url"
|
||
"slices"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
type bridge interface {
|
||
startUpdateScript(htmlID string) bool
|
||
finishUpdateScript(htmlID string)
|
||
callFunc(funcName string, args ...any) bool
|
||
updateInnerHTML(htmlID, html string)
|
||
appendToInnerHTML(htmlID, html string)
|
||
updateCSSProperty(htmlID, property, value string)
|
||
updateProperty(htmlID, property string, value any)
|
||
removeProperty(htmlID, property string)
|
||
sendResponse()
|
||
setAnimationCSS(css string)
|
||
appendAnimationCSS(css string)
|
||
canvasStart(htmlID string)
|
||
callCanvasFunc(funcName string, args ...any)
|
||
callCanvasVarFunc(v any, funcName string, args ...any)
|
||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||
createCanvasVar(funcName string, args ...any) any
|
||
createPath2D(arg string) any
|
||
updateCanvasProperty(property string, value any)
|
||
canvasFinish()
|
||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||
htmlPropertyValue(htmlID, name string) string
|
||
answerReceived(answer DataObject)
|
||
close()
|
||
remoteAddr() string
|
||
}
|
||
|
||
// SessionContent is the interface of a session content
|
||
type SessionContent interface {
|
||
|
||
// CreateRootView will be called by the library to create a root view of the application
|
||
CreateRootView(session Session) View
|
||
}
|
||
|
||
// Session provide interface to session parameters assess
|
||
type Session interface {
|
||
// App return the current application interface
|
||
App() Application
|
||
|
||
// ID return the id of the session
|
||
ID() int
|
||
|
||
// DarkTheme returns "true" if the dark theme is used
|
||
DarkTheme() bool
|
||
|
||
// Mobile returns "true" if current session is displayed on a touch screen device
|
||
TouchScreen() bool
|
||
|
||
// PixelRatio returns the ratio of the resolution in physical pixels to the resolution
|
||
// in logical pixels for the current display device.
|
||
PixelRatio() float64
|
||
|
||
// TextDirection returns the default text direction (LeftToRightDirection (1) or RightToLeftDirection (2))
|
||
TextDirection() int
|
||
|
||
// Constant returns the constant with "tag" name or "" if it is not exists
|
||
Constant(tag string) (string, bool)
|
||
|
||
// Color returns the color with "tag" name or 0 if it is not exists
|
||
Color(tag string) (Color, bool)
|
||
|
||
// ImageConstant returns the image constant with "tag" name or "" if it is not exists
|
||
ImageConstant(tag string) (string, bool)
|
||
|
||
// SetCustomTheme set the custom theme
|
||
SetCustomTheme(name string) bool
|
||
|
||
// UserAgent returns the "user-agent" text of the client browser
|
||
UserAgent() string
|
||
|
||
// RemoteAddr returns the client address.
|
||
RemoteAddr() string
|
||
|
||
// Language returns the current session language
|
||
Language() string
|
||
|
||
// SetLanguage set the current session language
|
||
SetLanguage(lang string)
|
||
|
||
// GetString returns the text for the current language
|
||
GetString(tag string) (string, bool)
|
||
|
||
// Content returns the SessionContent of session
|
||
Content() SessionContent
|
||
setContent(content SessionContent) bool
|
||
|
||
// SetTitle sets the text of the browser title/tab
|
||
SetTitle(title string)
|
||
|
||
// SetTitleColor sets the color of the browser navigation bar. Supported only in Safari and Chrome for android
|
||
SetTitleColor(color Color)
|
||
|
||
// RootView returns the root view of the session
|
||
RootView() View
|
||
|
||
// Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument.
|
||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||
Get(viewID string, tag PropertyName) any
|
||
|
||
// Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument.
|
||
// 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
|
||
Set(viewID string, tag PropertyName, value any) bool
|
||
|
||
// PopupShowAnimation returns a list of displayed popups or nil if there are no displayed popups.
|
||
Popups() []Popup
|
||
|
||
// PopupDefault returns default values for the Popup property
|
||
PopupDefault(tag PropertyName) any
|
||
|
||
// PopupDefaultsSeq returns an iterator over all default values for Popup properties
|
||
PopupDefaultsSeq() iter.Seq2[PropertyName, any]
|
||
|
||
// SetPopupDefaults sets default values for Popup properties
|
||
SetPopupDefaults(params Params)
|
||
|
||
// DownloadFile downloads (saves) on the client side the file located at the specified path on the server.
|
||
DownloadFile(path string)
|
||
|
||
//DownloadFileData downloads (saves) on the client side a file with a specified name and specified content.
|
||
DownloadFileData(filename string, data []byte)
|
||
|
||
// OpenURL opens the url in the new browser tab
|
||
OpenURL(url string)
|
||
|
||
// ClientItem reads value by key from the client-side storage
|
||
ClientItem(key string) (string, bool)
|
||
|
||
// SetClientItem stores a key-value pair in the client-side storage
|
||
SetClientItem(key, value string)
|
||
|
||
// RemoveClientItem removes a key-value pair in the client-side storage
|
||
RemoveClientItem(key string)
|
||
|
||
// RemoveAllClientItems removes all key-value pair from the client-side storage
|
||
RemoveAllClientItems()
|
||
|
||
// SetHotKey sets the function that will be called when the given hotkey is pressed.
|
||
// Invoke SetHotKey(..., ..., nil) for remove hotkey function.
|
||
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
|
||
registerAnimation(props []AnimatedProperty) string
|
||
|
||
resolveConstants(value string) (string, bool)
|
||
checkboxOffImage(accentColor Color) string
|
||
checkboxOnImage(accentColor Color) string
|
||
radiobuttonOffImage() string
|
||
radiobuttonOnImage(accentColor Color) string
|
||
|
||
viewByHTMLID(id string) View
|
||
nextViewID() string
|
||
styleProperty(styleTag string, propertyTag PropertyName) any
|
||
|
||
setBridge(events chan DataObject, bridge bridge)
|
||
writeInitScript(writer *strings.Builder)
|
||
callFunc(funcName string, args ...any)
|
||
updateInnerHTML(htmlID, html string)
|
||
appendToInnerHTML(htmlID, html string)
|
||
updateCSSProperty(htmlID, property, value string)
|
||
updateProperty(htmlID, property string, value any)
|
||
removeProperty(htmlID, property string)
|
||
startUpdateScript(htmlID string) bool
|
||
finishUpdateScript(htmlID string)
|
||
sendResponse()
|
||
addAnimationCSS(css string)
|
||
removeAnimation(keyframe string)
|
||
htmlPropertyValue(htmlID, name string) string
|
||
|
||
canvasStart(htmlID string) bool
|
||
callCanvasFunc(funcName string, args ...any)
|
||
createCanvasVar(funcName string, args ...any) any
|
||
createPath(arg string) any
|
||
callCanvasVarFunc(v any, funcName string, args ...any)
|
||
callCanvasImageFunc(url string, property string, funcName string, args ...any)
|
||
updateCanvasProperty(property string, value any)
|
||
canvasFinish()
|
||
canvasTextMetrics(htmlID, font, text string) TextMetrics
|
||
|
||
addToEventsQueue(data DataObject)
|
||
handleAnswer(command string, data DataObject) bool
|
||
handleRootSize(data DataObject)
|
||
handleResize(data DataObject)
|
||
handleEvent(command string, data DataObject)
|
||
close()
|
||
|
||
onStart()
|
||
onFinish()
|
||
onPause()
|
||
onResume()
|
||
onDisconnect()
|
||
onReconnect()
|
||
|
||
ignoreViewUpdates() bool
|
||
setIgnoreViewUpdates(ignore bool)
|
||
|
||
popupManager() *popupManager
|
||
imageManager() *imageManager
|
||
}
|
||
|
||
type sessionData struct {
|
||
customTheme Theme
|
||
currentTheme Theme
|
||
darkTheme bool
|
||
touchScreen bool
|
||
screenWidth int
|
||
screenHeight int
|
||
textDirection int
|
||
pixelRatio float64
|
||
userAgent string
|
||
language string
|
||
languages []string
|
||
checkboxOff string
|
||
checkboxOn string
|
||
radiobuttonOff string
|
||
radiobuttonOn string
|
||
app Application
|
||
sessionID int
|
||
viewCounter int
|
||
content SessionContent
|
||
rootView View
|
||
ignoreUpdates bool
|
||
popups *popupManager
|
||
images *imageManager
|
||
bridge bridge
|
||
events chan DataObject
|
||
animationCounter int
|
||
animationCSS string
|
||
updateScripts map[string]*strings.Builder
|
||
clientStorage map[string]string
|
||
hotkeys map[string]func(Session)
|
||
timers map[int]func(Session)
|
||
nextTimerID int
|
||
pauseTime int64
|
||
popupDefaults Params
|
||
}
|
||
|
||
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
||
session := new(sessionData)
|
||
session.app = app
|
||
session.sessionID = id
|
||
session.darkTheme = false
|
||
session.touchScreen = false
|
||
session.pixelRatio = 1
|
||
session.textDirection = LeftToRightDirection
|
||
session.languages = []string{}
|
||
session.viewCounter = 0
|
||
session.ignoreUpdates = false
|
||
session.animationCounter = 0
|
||
session.animationCSS = ""
|
||
session.updateScripts = map[string]*strings.Builder{}
|
||
session.clientStorage = map[string]string{}
|
||
session.hotkeys = map[string]func(Session){}
|
||
session.timers = map[int]func(Session){}
|
||
session.nextTimerID = 1
|
||
|
||
if customTheme != "" {
|
||
if theme, ok := CreateThemeFromText(customTheme); ok {
|
||
session.customTheme = theme
|
||
session.currentTheme = nil
|
||
}
|
||
}
|
||
|
||
if params != nil {
|
||
session.handleSessionInfo(params)
|
||
}
|
||
|
||
return session
|
||
}
|
||
|
||
func (session *sessionData) App() Application {
|
||
return session.app
|
||
}
|
||
|
||
func (session *sessionData) ID() int {
|
||
return session.sessionID
|
||
}
|
||
|
||
func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
|
||
session.events = events
|
||
session.bridge = bridge
|
||
}
|
||
|
||
func (session *sessionData) close() {
|
||
if session.events != nil {
|
||
obj, _ := ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
|
||
session.events <- obj
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) styleProperty(styleTag string, propertyTag PropertyName) any {
|
||
if style := session.getCurrentTheme().style(styleTag); style != nil {
|
||
return style.getRaw(propertyTag)
|
||
}
|
||
//errorLogF(`property "%v" not found`, propertyTag)
|
||
return nil
|
||
}
|
||
|
||
func (session *sessionData) nextViewID() string {
|
||
session.viewCounter++
|
||
return fmt.Sprintf("id%06d", session.viewCounter)
|
||
}
|
||
|
||
func (session *sessionData) viewByHTMLID(id string) View {
|
||
if session.rootView == nil {
|
||
return nil
|
||
}
|
||
popupManager := session.popupManager()
|
||
for _, popup := range popupManager.popups {
|
||
if view := popup.viewByHTMLID(id); view != nil {
|
||
return view
|
||
}
|
||
}
|
||
return viewByHTMLID(id, session.rootView)
|
||
}
|
||
|
||
func (session *sessionData) Content() SessionContent {
|
||
return session.content
|
||
}
|
||
|
||
func (session *sessionData) setContent(content SessionContent) bool {
|
||
if content != nil {
|
||
session.content = content
|
||
session.rootView = content.CreateRootView(session)
|
||
if session.rootView != nil {
|
||
session.rootView.setParentID("ruiRootView")
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (session *sessionData) RootView() View {
|
||
return session.rootView
|
||
}
|
||
|
||
func (session *sessionData) writeInitScript(writer *strings.Builder) {
|
||
if css := session.getCurrentTheme().cssText(session); css != "" {
|
||
css = strings.ReplaceAll(css, "\n", `\n`)
|
||
css = strings.ReplaceAll(css, "\t", `\t`)
|
||
writer.WriteString(`document.querySelector('style').textContent += "`)
|
||
writer.WriteString(css)
|
||
writer.WriteString("\";\n")
|
||
}
|
||
|
||
if session.rootView != nil {
|
||
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
||
buffer := allocStringBuilder()
|
||
defer freeStringBuilder(buffer)
|
||
viewHTML(session.rootView, buffer, "")
|
||
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
|
||
writer.WriteString(text)
|
||
writer.WriteString("';\nscanElementsSize();")
|
||
}
|
||
|
||
session.updateTooltipConstants()
|
||
}
|
||
|
||
func (session *sessionData) updateTooltipConstants() {
|
||
if color, ok := session.Color("ruiTooltipBackground"); ok {
|
||
session.bridge.callFunc("setCssVar", "--tooltip-background", color.cssString())
|
||
}
|
||
if color, ok := session.Color("ruiTooltipTextColor"); ok {
|
||
session.bridge.callFunc("setCssVar", "--tooltip-text-color", color.cssString())
|
||
}
|
||
if color, ok := session.Color("ruiTooltipShadowColor"); ok {
|
||
session.bridge.callFunc("setCssVar", "--tooltip-shadow-color", color.cssString())
|
||
}
|
||
|
||
}
|
||
|
||
func (session *sessionData) reload() {
|
||
|
||
css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
|
||
session.bridge.callFunc("setStyles", css)
|
||
|
||
if session.rootView != nil {
|
||
buffer := allocStringBuilder()
|
||
defer freeStringBuilder(buffer)
|
||
|
||
viewHTML(session.rootView, buffer, "")
|
||
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
||
session.bridge.callFunc("scanElementsSize")
|
||
}
|
||
|
||
session.updateTooltipConstants()
|
||
}
|
||
|
||
func (session *sessionData) ignoreViewUpdates() bool {
|
||
return session.bridge == nil || session.ignoreUpdates
|
||
}
|
||
|
||
func (session *sessionData) setIgnoreViewUpdates(ignore bool) {
|
||
session.ignoreUpdates = ignore
|
||
}
|
||
|
||
func (session *sessionData) Get(viewID string, tag PropertyName) any {
|
||
if view := ViewByID(session.RootView(), viewID); view != nil {
|
||
return view.Get(tag)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (session *sessionData) Set(viewID string, tag PropertyName, value any) bool {
|
||
if view := ViewByID(session.RootView(), viewID); view != nil {
|
||
return view.Set(tag, value)
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (session *sessionData) popupManager() *popupManager {
|
||
if session.popups == nil {
|
||
session.popups = new(popupManager)
|
||
session.popups.popups = []Popup{}
|
||
}
|
||
return session.popups
|
||
}
|
||
|
||
func (session *sessionData) Popups() []Popup {
|
||
popups := session.popupManager()
|
||
|
||
count := len(popups.popups)
|
||
if count == 0 {
|
||
return nil
|
||
}
|
||
|
||
return slices.Clone(popups.popups)
|
||
}
|
||
|
||
func (session *sessionData) imageManager() *imageManager {
|
||
if session.images == nil {
|
||
session.images = new(imageManager)
|
||
session.images.images = make(map[string]*imageData)
|
||
}
|
||
return session.images
|
||
}
|
||
|
||
func (session *sessionData) callFunc(funcName string, args ...any) {
|
||
if session.bridge != nil {
|
||
session.bridge.callFunc(funcName, args...)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) updateInnerHTML(htmlID, html string) {
|
||
if !session.ignoreViewUpdates() {
|
||
if session.bridge != nil {
|
||
session.bridge.updateInnerHTML(htmlID, html)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) appendToInnerHTML(htmlID, html string) {
|
||
if !session.ignoreViewUpdates() {
|
||
if session.bridge != nil {
|
||
session.bridge.appendToInnerHTML(htmlID, html)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) updateCSSProperty(htmlID, property, value string) {
|
||
if !session.ignoreViewUpdates() {
|
||
if session.bridge != nil {
|
||
session.bridge.updateCSSProperty(htmlID, property, value)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) updateProperty(htmlID, property string, value any) {
|
||
if !session.ignoreViewUpdates() {
|
||
if session.bridge != nil {
|
||
session.bridge.updateProperty(htmlID, property, value)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) removeProperty(htmlID, property string) {
|
||
if !session.ignoreViewUpdates() {
|
||
if session.bridge != nil {
|
||
session.bridge.removeProperty(htmlID, property)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) startUpdateScript(htmlID string) bool {
|
||
if session.bridge != nil {
|
||
return session.bridge.startUpdateScript(htmlID)
|
||
}
|
||
ErrorLog("No connection")
|
||
return false
|
||
}
|
||
|
||
func (session *sessionData) finishUpdateScript(htmlID string) {
|
||
if session.bridge != nil {
|
||
session.bridge.finishUpdateScript(htmlID)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) sendResponse() {
|
||
if session.bridge != nil {
|
||
session.bridge.sendResponse()
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) addAnimationCSS(css string) {
|
||
session.animationCSS += css
|
||
if session.bridge != nil {
|
||
session.bridge.appendAnimationCSS(css)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) removeAnimation(keyframe string) {
|
||
css := session.animationCSS
|
||
index := strings.Index(css, "@keyframes "+keyframe)
|
||
if index < 0 {
|
||
return
|
||
}
|
||
|
||
start := strings.IndexRune(css[index:], '{')
|
||
if start < 0 {
|
||
return
|
||
}
|
||
|
||
n := 1
|
||
end := -1
|
||
for i := start + index + 1; i < len(css); i++ {
|
||
if css[i] == '}' {
|
||
n--
|
||
if n == 0 {
|
||
end = i + 1
|
||
if end < len(css) && css[end] == '\n' {
|
||
end++
|
||
}
|
||
break
|
||
}
|
||
} else if css[i] == '{' {
|
||
n++
|
||
}
|
||
}
|
||
|
||
if end > index {
|
||
session.animationCSS = strings.Trim(css[:index]+css[end:], "\n")
|
||
if session.bridge != nil {
|
||
session.bridge.setAnimationCSS(session.animationCSS)
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) canvasStart(htmlID string) bool {
|
||
if session.bridge != nil {
|
||
session.bridge.canvasStart(htmlID)
|
||
return true
|
||
}
|
||
|
||
ErrorLog("No connection")
|
||
return false
|
||
}
|
||
|
||
func (session *sessionData) callCanvasFunc(funcName string, args ...any) {
|
||
if session.bridge != nil {
|
||
session.bridge.callCanvasFunc(funcName, args...)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) updateCanvasProperty(property string, value any) {
|
||
if session.bridge != nil {
|
||
session.bridge.updateCanvasProperty(property, value)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) createCanvasVar(funcName string, args ...any) any {
|
||
if session.bridge != nil {
|
||
return session.bridge.createCanvasVar(funcName, args...)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (session *sessionData) createPath(arg string) any {
|
||
if session.bridge != nil {
|
||
return session.bridge.createPath2D(arg)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (session *sessionData) callCanvasVarFunc(v any, funcName string, args ...any) {
|
||
if session.bridge != nil && v != nil {
|
||
session.bridge.callCanvasVarFunc(v, funcName, args...)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
|
||
if session.bridge != nil {
|
||
session.bridge.callCanvasImageFunc(url, property, funcName, args...)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) canvasFinish() {
|
||
if session.bridge != nil {
|
||
session.bridge.canvasFinish()
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) canvasTextMetrics(htmlID, font, text string) TextMetrics {
|
||
if session.bridge != nil {
|
||
return session.bridge.canvasTextMetrics(htmlID, font, text)
|
||
}
|
||
|
||
ErrorLog("No connection")
|
||
return TextMetrics{Width: 0}
|
||
}
|
||
|
||
func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
|
||
if session.bridge != nil {
|
||
return session.bridge.htmlPropertyValue(htmlID, name)
|
||
}
|
||
|
||
ErrorLog("No connection")
|
||
return ""
|
||
}
|
||
|
||
func (session *sessionData) handleAnswer(command string, data DataObject) bool {
|
||
switch command {
|
||
case "answer":
|
||
if session.bridge != nil {
|
||
session.bridge.answerReceived(data)
|
||
}
|
||
|
||
case "imageLoaded":
|
||
session.imageManager().imageLoaded(data)
|
||
|
||
case "imageError":
|
||
session.imageManager().imageLoadError(data)
|
||
|
||
default:
|
||
return false
|
||
}
|
||
|
||
if session.bridge != nil {
|
||
session.bridge.sendResponse()
|
||
} else {
|
||
ErrorLog("No connection")
|
||
}
|
||
return true
|
||
}
|
||
|
||
func (session *sessionData) handleRootSize(data DataObject) {
|
||
getValue := func(tag string) int {
|
||
if value, ok := data.PropertyValue(tag); ok {
|
||
float, err := strconv.ParseFloat(value, 64)
|
||
if err == nil {
|
||
return int(float)
|
||
}
|
||
ErrorLog(`Resize event error: ` + err.Error())
|
||
} else {
|
||
ErrorLogF(`Resize event error: the property "%s" not found`, tag)
|
||
}
|
||
return 0
|
||
}
|
||
|
||
if w := getValue("width"); w > 0 {
|
||
session.screenWidth = w
|
||
}
|
||
if h := getValue("height"); h > 0 {
|
||
session.screenHeight = h
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) handleResize(data DataObject) {
|
||
if node := data.PropertyByTag("views"); node != nil && node.Type() == ArrayNode {
|
||
for el := range node.ArrayElements() {
|
||
if el.IsObject() {
|
||
obj := el.Object()
|
||
getFloat := func(tag string) float64 {
|
||
if value, ok := obj.PropertyValue(tag); ok {
|
||
f, err := strconv.ParseFloat(value, 64)
|
||
if err == nil {
|
||
return f
|
||
}
|
||
ErrorLog(`Resize event error: ` + err.Error())
|
||
} else {
|
||
ErrorLogF(`Resize event error: the property "%s" not found`, tag)
|
||
}
|
||
return 0
|
||
}
|
||
if viewID, ok := obj.PropertyValue("id"); ok {
|
||
if n := strings.IndexRune(viewID, '-'); n > 0 {
|
||
if view := session.viewByHTMLID(viewID[:n]); view != nil {
|
||
view.onItemResize(view, viewID[n+1:], getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||
} else {
|
||
DebugLogF(`View with id == %s not found`, viewID[:n])
|
||
}
|
||
} else if view := session.viewByHTMLID(viewID); view != nil {
|
||
view.onResize(view, getFloat("x"), getFloat("y"), getFloat("width"), getFloat("height"))
|
||
view.setScroll(getFloat("scroll-x"), getFloat("scroll-y"), getFloat("scroll-width"), getFloat("scroll-height"))
|
||
} else {
|
||
DebugLogF(`View with id == %s not found`, viewID)
|
||
}
|
||
} else {
|
||
ErrorLog(`"id" property not found`)
|
||
}
|
||
} else {
|
||
ErrorLog(`Resize event error: views element is not object`)
|
||
}
|
||
}
|
||
} else {
|
||
ErrorLog(`Resize event error: invalid "views" property`)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) handleSessionInfo(params DataObject) {
|
||
if value, ok := params.PropertyValue("touch"); ok {
|
||
session.touchScreen = (value == "1" || value == "true")
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("user-agent"); ok {
|
||
session.userAgent = value
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("direction"); ok {
|
||
if value == "rtl" {
|
||
session.textDirection = RightToLeftDirection
|
||
}
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("language"); ok {
|
||
session.language = value
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("languages"); ok {
|
||
session.languages = strings.Split(value, ",")
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("dark"); ok {
|
||
session.darkTheme = (value == "1" || value == "true")
|
||
}
|
||
|
||
if value, ok := params.PropertyValue("pixel-ratio"); ok {
|
||
if f, err := strconv.ParseFloat(value, 64); err != nil {
|
||
ErrorLog(err.Error())
|
||
} else {
|
||
session.pixelRatio = f
|
||
}
|
||
}
|
||
|
||
if node := params.PropertyByTag("storage"); node != nil && node.Type() == ObjectNode {
|
||
if obj := node.Object(); obj != nil {
|
||
for element := range obj.Properties() {
|
||
if element.Type() == TextNode {
|
||
session.clientStorage[element.Tag()] = element.Text()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) handleEvent(command string, data DataObject) {
|
||
switch command {
|
||
case "session-pause":
|
||
session.onPause()
|
||
|
||
case "session-resume":
|
||
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":
|
||
session.handleRootSize(data)
|
||
|
||
case "resize":
|
||
session.handleResize(data)
|
||
|
||
case "sessionInfo":
|
||
session.handleSessionInfo(data)
|
||
|
||
case "storageError":
|
||
if text, ok := data.PropertyValue("error"); ok {
|
||
ErrorLog(text)
|
||
}
|
||
|
||
default:
|
||
if viewID, ok := data.PropertyValue("id"); ok {
|
||
if viewID != "body" {
|
||
if view := session.viewByHTMLID(viewID); view != nil {
|
||
view.handleCommand(view, PropertyName(command), data)
|
||
}
|
||
}
|
||
if command == string(KeyDownEvent) {
|
||
var event KeyEvent
|
||
event.init(data)
|
||
session.hotKey(event)
|
||
}
|
||
} else {
|
||
ErrorLog(`"id" property not found. Event: ` + command)
|
||
}
|
||
}
|
||
|
||
session.bridge.sendResponse()
|
||
}
|
||
|
||
func (session *sessionData) hotKey(event KeyEvent) {
|
||
popups := session.popupManager().popups
|
||
if count := len(popups); count > 0 {
|
||
if popups[count-1].keyEvent(event) {
|
||
return
|
||
}
|
||
}
|
||
|
||
var controlKeys ControlKeyMask = 0
|
||
if event.AltKey {
|
||
controlKeys |= AltKey
|
||
}
|
||
if event.CtrlKey {
|
||
controlKeys |= CtrlKey
|
||
}
|
||
if event.MetaKey {
|
||
controlKeys |= MetaKey
|
||
}
|
||
if event.ShiftKey {
|
||
controlKeys |= ShiftKey
|
||
}
|
||
|
||
key := hotkeyCode(KeyCode(event.Code), controlKeys)
|
||
if fn, ok := session.hotkeys[key]; ok && fn != nil {
|
||
fn(session)
|
||
}
|
||
}
|
||
|
||
func hotkeyCode(keyCode KeyCode, controlKeys ControlKeyMask) string {
|
||
buffer := allocStringBuilder()
|
||
defer freeStringBuilder(buffer)
|
||
|
||
buffer.WriteString(strings.ToLower(string(keyCode)))
|
||
if controlKeys != 0 {
|
||
buffer.WriteRune('-')
|
||
if controlKeys&AltKey != 0 {
|
||
buffer.WriteRune('a')
|
||
}
|
||
if controlKeys&CtrlKey != 0 {
|
||
buffer.WriteRune('c')
|
||
}
|
||
if controlKeys&MetaKey != 0 {
|
||
buffer.WriteRune('m')
|
||
}
|
||
if controlKeys&ShiftKey != 0 {
|
||
buffer.WriteRune('s')
|
||
}
|
||
}
|
||
return buffer.String()
|
||
}
|
||
|
||
func (session *sessionData) SetHotKey(keyCode KeyCode, controlKeys ControlKeyMask, fn func(Session)) {
|
||
hotkey := hotkeyCode(keyCode, controlKeys)
|
||
if fn == nil {
|
||
delete(session.hotkeys, hotkey)
|
||
} else {
|
||
session.hotkeys[hotkey] = fn
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) SetTitle(title string) {
|
||
title, _ = session.GetString(title)
|
||
session.callFunc("setTitle", title)
|
||
}
|
||
|
||
func (session *sessionData) SetTitleColor(color Color) {
|
||
session.callFunc("setTitleColor", color.cssString())
|
||
}
|
||
|
||
func (session *sessionData) RemoteAddr() string {
|
||
return session.bridge.remoteAddr()
|
||
}
|
||
|
||
func (session *sessionData) OpenURL(urlStr string) {
|
||
if _, err := url.ParseRequestURI(urlStr); err != nil {
|
||
ErrorLog(err.Error())
|
||
return
|
||
}
|
||
session.callFunc("openURL", urlStr)
|
||
}
|
||
|
||
func (session *sessionData) ClientItem(key string) (string, bool) {
|
||
value, ok := session.clientStorage[key]
|
||
return value, ok
|
||
}
|
||
|
||
func (session *sessionData) SetClientItem(key, value string) {
|
||
session.clientStorage[key] = value
|
||
session.bridge.callFunc("localStorageSet", key, value)
|
||
}
|
||
|
||
func (session *sessionData) RemoveClientItem(key string) {
|
||
delete(session.clientStorage, key)
|
||
session.bridge.callFunc("localStorageRemove", key)
|
||
}
|
||
|
||
func (session *sessionData) RemoveAllClientItems() {
|
||
session.clientStorage = map[string]string{}
|
||
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)
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) PopupDefault(tag PropertyName) any {
|
||
if value, ok := session.popupDefaults[tag]; ok {
|
||
return value
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (session *sessionData) PopupDefaultsSeq() iter.Seq2[PropertyName, any] {
|
||
return func(yield func(PropertyName, any) bool) {
|
||
for tag, value := range session.popupDefaults {
|
||
if !yield(tag, value) {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func (session *sessionData) SetPopupDefaults(params Params) {
|
||
if len(params) > 0 {
|
||
session.popupDefaults = maps.Clone(params)
|
||
} else {
|
||
session.popupDefaults = nil
|
||
}
|
||
}
|