mirror of https://github.com/anoshenko/rui.git
Compare commits
5 Commits
6c49f37f68
...
7fd6a7985e
Author | SHA1 | Date |
---|---|---|
Alexei Anoshenko | 7fd6a7985e | |
Alexei Anoshenko | 50e5b8d44d | |
Alexei Anoshenko | 856b09b04b | |
Alexei Anoshenko | 0740a48346 | |
anoshenko | befb2a7484 |
|
@ -1,3 +1,12 @@
|
|||
# v0.14.0
|
||||
* Added the ability to work without creating a WebSocket. Added NoSocket property of AppParams.
|
||||
* Added the ability to run a timer on the client side. Added StartTimer and StopTimer methods to Session interface.
|
||||
* Bug fixing
|
||||
|
||||
# v0.13.x
|
||||
* Added NewHandler function
|
||||
* Bug fixing
|
||||
|
||||
# v0.13.0
|
||||
|
||||
* Added SetHotKey function to Session interface
|
||||
|
|
|
@ -269,14 +269,7 @@ func (app *application) socketReader(bridge *wsBridge) {
|
|||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
|
||||
if !bridge.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, bridge)
|
||||
}
|
||||
bridge.writeMessage("restartSession();")
|
||||
|
||||
default:
|
||||
if !session.handleAnswer(command, obj) {
|
||||
|
|
23
app_post.js
23
app_post.js
|
@ -1,16 +1,15 @@
|
|||
|
||||
function sendMessage(message) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
const script = this.responseText
|
||||
if (script != "") {
|
||||
window.eval(script)
|
||||
//sendMessage("nop{session=" + sessionID +"}")
|
||||
}
|
||||
|
||||
}
|
||||
xhr.send(message);
|
||||
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() {
|
||||
|
|
|
@ -73,6 +73,10 @@ function sessionInfo() {
|
|||
return message + "}";
|
||||
}
|
||||
|
||||
function restartSession() {
|
||||
sendMessage( sessionInfo() );
|
||||
}
|
||||
|
||||
function getIntAttribute(element, tag) {
|
||||
let value = element.getAttribute(tag);
|
||||
if (value) {
|
||||
|
@ -1861,6 +1865,28 @@ function imageError(element, event) {
|
|||
sendMessage(message);
|
||||
}
|
||||
|
||||
let timers = new Map();
|
||||
|
||||
function startTimer(ms, timerID) {
|
||||
let data = {
|
||||
id: setInterval(timerFunc, ms, timerID),
|
||||
ms: ms,
|
||||
};
|
||||
timers.set(timerID, data);
|
||||
}
|
||||
|
||||
function timerFunc(timerID) {
|
||||
sendMessage("timer{session=" + sessionID + ",timerID=" + timerID + "}");
|
||||
}
|
||||
|
||||
function stopTimer(timerID) {
|
||||
let timer = timers.get(timerID);
|
||||
if (timer) {
|
||||
clearInterval(timer.id);
|
||||
timers.delete(timerID);
|
||||
}
|
||||
}
|
||||
|
||||
function canvasTextMetrics(answerID, elementId, font, text) {
|
||||
let w = 0;
|
||||
let ascent = 0;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package rui
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoRepeat is value of the Repeat property of an background image:
|
||||
|
@ -61,6 +64,8 @@ const (
|
|||
// BackgroundElement describes the background element.
|
||||
type BackgroundElement interface {
|
||||
Properties
|
||||
fmt.Stringer
|
||||
stringWriter
|
||||
cssStyle(session Session) string
|
||||
Tag() string
|
||||
Clone() BackgroundElement
|
||||
|
@ -239,3 +244,20 @@ func (image *backgroundImage) cssStyle(session Session) string {
|
|||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) {
|
||||
image.writeToBuffer(buffer, indent, image.Tag(), []string{
|
||||
Source,
|
||||
Width,
|
||||
Height,
|
||||
ImageHorizontalAlign,
|
||||
ImageVerticalAlign,
|
||||
backgroundFit,
|
||||
Repeat,
|
||||
Attachment,
|
||||
})
|
||||
}
|
||||
|
||||
func (image *backgroundImage) String() string {
|
||||
return runStringWriter(image)
|
||||
}
|
||||
|
|
|
@ -336,3 +336,16 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string {
|
|||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) {
|
||||
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
||||
Gradient,
|
||||
CenterX,
|
||||
CenterY,
|
||||
Repeating,
|
||||
})
|
||||
}
|
||||
|
||||
func (gradient *backgroundConicGradient) String() string {
|
||||
return runStringWriter(gradient)
|
||||
}
|
||||
|
|
|
@ -224,6 +224,33 @@ func (point *BackgroundGradientPoint) color(session Session) (Color, bool) {
|
|||
return 0, false
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
value, ok := gradient.properties[Gradient]
|
||||
|
@ -370,6 +397,18 @@ func (gradient *backgroundLinearGradient) cssStyle(session Session) 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 {
|
||||
return "radial-gradient"
|
||||
}
|
||||
|
@ -610,3 +649,17 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) 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)
|
||||
}
|
||||
|
|
27
editView.go
27
editView.go
|
@ -517,19 +517,30 @@ func GetHint(view View, subviewID ...string) string {
|
|||
if len(subviewID) > 0 && subviewID[0] != "" {
|
||||
view = ViewByID(view, subviewID[0])
|
||||
}
|
||||
|
||||
session := view.Session()
|
||||
text := ""
|
||||
if view != nil {
|
||||
if text, ok := stringProperty(view, Hint, view.Session()); ok {
|
||||
return text
|
||||
}
|
||||
if value := valueFromStyle(view, Hint); value != nil {
|
||||
if text, ok := value.(string); ok {
|
||||
if text, ok = view.Session().resolveConstants(text); ok {
|
||||
return text
|
||||
var ok bool
|
||||
text, ok = stringProperty(view, Hint, view.Session())
|
||||
if !ok {
|
||||
if value := valueFromStyle(view, Hint); value != nil {
|
||||
if text, ok = value.(string); ok {
|
||||
if text, ok = session.resolveConstants(text); !ok {
|
||||
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
|
||||
|
|
|
@ -68,6 +68,28 @@ func (properties *propertyList) AllTags() []string {
|
|||
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) {
|
||||
count := object.PropertyCount()
|
||||
for i := 0; i < count; i++ {
|
||||
|
|
46
session.go
46
session.go
|
@ -110,6 +110,14 @@ type Session interface {
|
|||
// 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
|
||||
|
||||
|
@ -197,6 +205,8 @@ type sessionData struct {
|
|||
updateScripts map[string]*strings.Builder
|
||||
clientStorage map[string]string
|
||||
hotkeys map[string]func(Session)
|
||||
timers map[int]func(Session)
|
||||
nextTimerID int
|
||||
}
|
||||
|
||||
func newSession(app Application, id int, customTheme string, params DataObject) Session {
|
||||
|
@ -215,6 +225,8 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
|||
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 {
|
||||
|
@ -664,6 +676,22 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
|||
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)
|
||||
|
||||
|
@ -797,3 +825,21 @@ func (session *sessionData) RemoveAllClientItems() {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
47
viewStyle.go
47
viewStyle.go
|
@ -573,6 +573,9 @@ func supportedPropertyValue(value any) bool {
|
|||
case []ViewShadow:
|
||||
case []View:
|
||||
case []any:
|
||||
case []BackgroundElement:
|
||||
case []BackgroundGradientPoint:
|
||||
case []BackgroundGradientAngle:
|
||||
case map[string]Animation:
|
||||
default:
|
||||
return false
|
||||
|
@ -692,6 +695,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
|||
for _, shadow := range value {
|
||||
buffer.WriteString(indent2)
|
||||
shadow.writeString(buffer, indent)
|
||||
buffer.WriteRune(',')
|
||||
}
|
||||
buffer.WriteRune('\n')
|
||||
buffer.WriteString(indent)
|
||||
|
@ -701,7 +705,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
|||
case []View:
|
||||
switch len(value) {
|
||||
case 0:
|
||||
buffer.WriteString("[]\n")
|
||||
buffer.WriteString("[]")
|
||||
|
||||
case 1:
|
||||
writeViewStyle(value[0].Tag(), value[0], buffer, indent)
|
||||
|
@ -740,6 +744,47 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
|||
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:
|
||||
switch count := len(value); count {
|
||||
case 0:
|
||||
|
|
|
@ -32,42 +32,52 @@ func (style *viewStyle) setRange(tag string, value any) bool {
|
|||
}
|
||||
|
||||
func (style *viewStyle) setBackground(value any) bool {
|
||||
background := []BackgroundElement{}
|
||||
|
||||
switch value := value.(type) {
|
||||
case BackgroundElement:
|
||||
style.properties[Background] = []BackgroundElement{value}
|
||||
return true
|
||||
background = []BackgroundElement{value}
|
||||
|
||||
case []BackgroundElement:
|
||||
style.properties[Background] = value
|
||||
return true
|
||||
background = value
|
||||
|
||||
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:
|
||||
if element := createBackground(value); element != nil {
|
||||
style.properties[Background] = []BackgroundElement{element}
|
||||
return true
|
||||
background = []BackgroundElement{element}
|
||||
}
|
||||
|
||||
case []DataObject:
|
||||
for _, obj := range value {
|
||||
background := []BackgroundElement{}
|
||||
if element := createBackground(obj); element != nil {
|
||||
background = append(background, element)
|
||||
}
|
||||
if len(background) > 0 {
|
||||
style.properties[Background] = background
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case string:
|
||||
if obj := ParseDataText(value); obj != nil {
|
||||
if element := createBackground(obj); element != nil {
|
||||
style.properties[Background] = []BackgroundElement{element}
|
||||
return true
|
||||
background = []BackgroundElement{element}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(background) > 0 {
|
||||
style.properties[Background] = background
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
12
webBridge.go
12
webBridge.go
|
@ -397,7 +397,7 @@ func (bridge *webBridge) canvasFinish() {
|
|||
bridge.writeMessage(bridge.canvasBuffer.String())
|
||||
}
|
||||
|
||||
func (bridge *webBridge) removeValue(funcName, htmlID string, args ...string) (DataObject, bool) {
|
||||
func (bridge *webBridge) remoteValue(funcName string, args ...any) (DataObject, bool) {
|
||||
bridge.answerMutex.Lock()
|
||||
answerID := bridge.answerID
|
||||
bridge.answerID++
|
||||
|
@ -406,11 +406,7 @@ func (bridge *webBridge) removeValue(funcName, htmlID string, args ...string) (D
|
|||
answer := make(chan DataObject)
|
||||
bridge.answer[answerID] = answer
|
||||
|
||||
funcArgs := []any{answerID, htmlID}
|
||||
for _, arg := range args {
|
||||
funcArgs = append(funcArgs, arg)
|
||||
}
|
||||
|
||||
funcArgs := append([]any{answerID}, args...)
|
||||
var result DataObject = nil
|
||||
ok := bridge.callFuncImmediately(funcName, funcArgs...)
|
||||
if ok {
|
||||
|
@ -424,14 +420,14 @@ func (bridge *webBridge) removeValue(funcName, htmlID string, args ...string) (D
|
|||
|
||||
func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
|
||||
result := TextMetrics{}
|
||||
if data, ok := bridge.removeValue("canvasTextMetrics", htmlID, font, text); ok {
|
||||
if data, ok := bridge.remoteValue("canvasTextMetrics", htmlID, font, text); ok {
|
||||
result.Width = dataFloatProperty(data, "width")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
|
||||
if data, ok := bridge.removeValue("getPropertyValue", htmlID, name); ok {
|
||||
if data, ok := bridge.remoteValue("getPropertyValue", htmlID, name); ok {
|
||||
if value, ok := data.PropertyValue("value"); ok {
|
||||
return value
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue