Improved binding for no args listeners

This commit is contained in:
Alexei Anoshenko 2025-06-18 14:19:57 +03:00
parent 4b00299878
commit f2fb948325
5 changed files with 374 additions and 18 deletions

178
events.go
View File

@ -67,6 +67,96 @@ func updateEventListenerHtml(view View, tag PropertyName) {
} }
} }
type noArgListener[V View] interface {
Run(V)
rawListener() any
}
type noArgListener0[V View] struct {
fn func()
}
type noArgListenerV[V View] struct {
fn func(V)
}
type noArgListenerBinding[V View] struct {
name string
}
func newNoArgListener0[V View](fn func()) noArgListener[V] {
obj := new(noArgListener0[V])
obj.fn = fn
return obj
}
func (data *noArgListener0[V]) Run(_ V) {
data.fn()
}
func (data *noArgListener0[V]) rawListener() any {
return data.fn
}
func newNoArgListenerV[V View](fn func(V)) noArgListener[V] {
obj := new(noArgListenerV[V])
obj.fn = fn
return obj
}
func (data *noArgListenerV[V]) Run(view V) {
data.fn(view)
}
func (data *noArgListenerV[V]) rawListener() any {
return data.fn
}
func newNoArgListenerBinding[V View](name string) noArgListener[V] {
obj := new(noArgListenerBinding[V])
obj.name = name
return obj
}
func (data *noArgListenerBinding[V]) Run(view V) {
bind := view.binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, data.name)
return
}
val := reflect.ValueOf(bind)
method := val.MethodByName(data.name)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, data.name)
return
}
methodType := method.Type()
var args []reflect.Value = nil
switch methodType.NumIn() {
case 0:
args = []reflect.Value{}
case 1:
inType := methodType.In(0)
if inType == reflect.TypeOf(view) {
args = []reflect.Value{reflect.ValueOf(view)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *noArgListenerBinding[V]) rawListener() any {
return data.name
}
/*
func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) { func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) {
if value == nil { if value == nil {
return nil, true return nil, true
@ -173,20 +263,74 @@ func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) {
return nil, false return nil, false
} }
*/
func getNoArgEventListeners[V View](view View, subviewID []string, tag PropertyName) []func(V) { func valueToNoArgEventListeners[V View](value any) ([]noArgListener[V], bool) {
if view = getSubview(view, subviewID); view != nil { if value == nil {
if value := view.Get(tag); value != nil { return nil, true
if result, ok := value.([]func(V)); ok { }
return result
switch value := value.(type) {
case []noArgListener[V]:
return value, true
case noArgListener[V]:
return []noArgListener[V]{value}, true
case string:
return []noArgListener[V]{newNoArgListenerBinding[V](value)}, true
case func(V):
return []noArgListener[V]{newNoArgListenerV(value)}, true
case func():
return []noArgListener[V]{newNoArgListener0[V](value)}, true
case []func(V):
result := make([]noArgListener[V], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newNoArgListenerV(fn))
} }
} }
return result, len(result) > 0
case []func():
result := make([]noArgListener[V], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newNoArgListener0[V](fn))
}
}
return result, len(result) > 0
case []any:
result := make([]noArgListener[V], 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(V):
result = append(result, newNoArgListenerV(v))
case func():
result = append(result, newNoArgListener0[V](v))
case string:
result = append(result, newNoArgListenerBinding[V](v))
default:
return nil, false
}
}
}
return result, len(result) > 0
} }
return []func(V){}
return nil, false
} }
func setNoArgEventListener[V View](view View, tag PropertyName, value any) []PropertyName { func setNoArgEventListener[V View](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToNoArgEventListeners[V](view, value); ok { if listeners, ok := valueToNoArgEventListeners[V](value); ok {
if len(listeners) > 0 { if len(listeners) > 0 {
view.setRaw(tag, listeners) view.setRaw(tag, listeners)
} else if view.getRaw(tag) != nil { } else if view.getRaw(tag) != nil {
@ -199,3 +343,23 @@ func setNoArgEventListener[V View](view View, tag PropertyName, value any) []Pro
notCompatibleType(tag, value) notCompatibleType(tag, value)
return nil return nil
} }
func getNoArgEventListeners[V View](view View, subviewID []string, tag PropertyName) []noArgListener[V] {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]noArgListener[V]); ok {
return result
}
}
}
return []noArgListener[V]{}
}
func getNoArgEventRawListeners[V View](view View, subviewID []string, tag PropertyName) []any {
listeners := getNoArgEventListeners[V](view, subviewID, tag)
result := make([]any, len(listeners))
for i, l := range listeners {
result[i] = l.rawListener()
}
return result
}

View File

@ -50,16 +50,26 @@ func focusEventsHtml(view View, buffer *strings.Builder) {
// GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned // GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned
// //
// Result elements can be of the following types:
// - func(rui.View),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned. // The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned. // If it is not specified then a value from the first argument (view) is returned.
func GetFocusListeners(view View, subviewID ...string) []func(View) { func GetFocusListeners(view View, subviewID ...string) []any {
return getNoArgEventListeners[View](view, subviewID, FocusEvent) return getNoArgEventRawListeners[View](view, subviewID, FocusEvent)
} }
// GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned // GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned
// //
// Result elements can be of the following types:
// - func(rui.View),
// - func(),
// - string.
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned. // The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned. // If it is not specified then a value from the first argument (view) is returned.
func GetLostFocusListeners(view View, subviewID ...string) []func(View) { func GetLostFocusListeners(view View, subviewID ...string) []any {
return getNoArgEventListeners[View](view, subviewID, LostFocusEvent) return getNoArgEventRawListeners[View](view, subviewID, LostFocusEvent)
} }

View File

@ -300,7 +300,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d
switch command { switch command {
case "imageViewError": case "imageViewError":
for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, ErrorEvent) { for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, ErrorEvent) {
listener(imageView) listener.Run(imageView)
} }
case "imageViewLoaded": case "imageViewLoaded":
@ -309,7 +309,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d
imageView.currentSrc, _ = data.PropertyValue("current-src") imageView.currentSrc, _ = data.PropertyValue("current-src")
for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) { for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) {
listener(imageView) listener.Run(imageView)
} }
default: default:
@ -370,3 +370,31 @@ func GetImageViewVerticalAlign(view View, subviewID ...string) int {
func GetImageViewHorizontalAlign(view View, subviewID ...string) int { func GetImageViewHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ImageHorizontalAlign, LeftAlign, false) return enumStyledProperty(view, subviewID, ImageHorizontalAlign, LeftAlign, false)
} }
// GetImageViewErrorEventListeners returns the list of "error-event" event listeners.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.ImageView)
// - func()
// - string
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetImageViewErrorEventListeners(view View, subviewID ...string) []any {
return getNoArgEventRawListeners[View](view, subviewID, ErrorEvent)
}
// GetImageViewLoadedEventListeners returns the list of "loaded-event" event listeners.
// If there are no listeners then the empty list is returned
//
// Result elements can be of the following types:
// - func(rui.ImageView)
// - func()
// - string
//
// The second argument (subviewID) specifies the path to the child element whose value needs to be returned.
// If it is not specified then a value from the first argument (view) is returned.
func GetImageViewLoadedEventListeners(view View, subviewID ...string) []any {
return getNoArgEventRawListeners[View](view, subviewID, LoadedEvent)
}

160
popup.go
View File

@ -2,6 +2,7 @@ package rui
import ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
) )
@ -281,13 +282,30 @@ type Popup interface {
dissmissAnimation(listener func(PropertyName)) bool dissmissAnimation(listener func(PropertyName)) bool
} }
type popupListener interface {
Run(Popup)
rawListener() any
}
type popupListener0 struct {
fn func()
}
type popupListener1 struct {
fn func(Popup)
}
type popupListenerBinding struct {
name string
}
type popupData struct { type popupData struct {
layerView GridLayout layerView GridLayout
popupView GridLayout popupView GridLayout
contentView View contentView View
buttons []PopupButton buttons []PopupButton
cancelable bool cancelable bool
dismissListener []func(Popup) dismissListener []popupListener
showTransform TransformProperty showTransform TransformProperty
showOpacity float64 showOpacity float64
showDuration float64 showDuration float64
@ -649,7 +667,7 @@ func (popup *popupData) init(view View, popupParams Params) {
} }
case DismissEvent: case DismissEvent:
if listeners, ok := valueToNoArgEventListeners[Popup](popup.contentView, value); ok { if listeners, ok := valueToPopupEventListeners(value); ok {
if listeners != nil { if listeners != nil {
popup.dismissListener = listeners popup.dismissListener = listeners
} }
@ -887,7 +905,7 @@ func (popup *popupData) onDismiss() {
popup.Session().callFunc("removeView", popup.layerView.htmlID()) popup.Session().callFunc("removeView", popup.layerView.htmlID())
for _, listener := range popup.dismissListener { for _, listener := range popup.dismissListener {
listener(popup) listener.Run(popup)
} }
} }
@ -1014,3 +1032,139 @@ func (manager *popupManager) dismissPopup(popup Popup) {
listener("") listener("")
} }
} }
func newPopupListener0(fn func()) popupListener {
obj := new(popupListener0)
obj.fn = fn
return obj
}
func (data *popupListener0) Run(_ Popup) {
data.fn()
}
func (data *popupListener0) rawListener() any {
return data.fn
}
func newPopupListener1(fn func(Popup)) popupListener {
obj := new(popupListener1)
obj.fn = fn
return obj
}
func (data *popupListener1) Run(popup Popup) {
data.fn(popup)
}
func (data *popupListener1) rawListener() any {
return data.fn
}
func newPopupListenerBinding(name string) popupListener {
obj := new(popupListenerBinding)
obj.name = name
return obj
}
func (data *popupListenerBinding) Run(popup Popup) {
bind := popup.View().binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, data.name)
return
}
val := reflect.ValueOf(bind)
method := val.MethodByName(data.name)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, data.name)
return
}
methodType := method.Type()
var args []reflect.Value = nil
switch methodType.NumIn() {
case 0:
args = []reflect.Value{}
case 1:
inType := methodType.In(0)
if inType == reflect.TypeOf(popup) {
args = []reflect.Value{reflect.ValueOf(popup)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *popupListenerBinding) rawListener() any {
return data.name
}
func valueToPopupEventListeners(value any) ([]popupListener, bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case []popupListener:
return value, true
case popupListener:
return []popupListener{value}, true
case string:
return []popupListener{newPopupListenerBinding(value)}, true
case func(Popup):
return []popupListener{newPopupListener1(value)}, true
case func():
return []popupListener{newPopupListener0(value)}, true
case []func(Popup):
result := make([]popupListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newPopupListener1(fn))
}
}
return result, len(result) > 0
case []func():
result := make([]popupListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newPopupListener0(fn))
}
}
return result, len(result) > 0
case []any:
result := make([]popupListener, 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(Popup):
result = append(result, newPopupListener1(v))
case func():
result = append(result, newPopupListener0(v))
case string:
result = append(result, newPopupListenerBinding(v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}

View File

@ -1162,13 +1162,13 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
case FocusEvent: case FocusEvent:
view.hasFocus = true view.hasFocus = true
for _, listener := range getNoArgEventListeners[View](view, nil, command) { for _, listener := range getNoArgEventListeners[View](view, nil, command) {
listener(self) listener.Run(self)
} }
case LostFocusEvent: case LostFocusEvent:
view.hasFocus = false view.hasFocus = false
for _, listener := range getNoArgEventListeners[View](view, nil, command) { for _, listener := range getNoArgEventListeners[View](view, nil, command) {
listener(self) listener.Run(self)
} }
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent: case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent: