diff --git a/events.go b/events.go index f8319b2..8b086a7 100644 --- a/events.go +++ b/events.go @@ -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) { if value == nil { return nil, true @@ -173,20 +263,74 @@ func valueToNoArgEventListeners[V any](view View, value any) ([]func(V), bool) { return nil, false } +*/ -func getNoArgEventListeners[V View](view View, subviewID []string, tag PropertyName) []func(V) { - if view = getSubview(view, subviewID); view != nil { - if value := view.Get(tag); value != nil { - if result, ok := value.([]func(V)); ok { - return result +func valueToNoArgEventListeners[V View](value any) ([]noArgListener[V], bool) { + if value == nil { + return nil, true + } + + 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 { - if listeners, ok := valueToNoArgEventListeners[V](view, value); ok { + if listeners, ok := valueToNoArgEventListeners[V](value); ok { if len(listeners) > 0 { view.setRaw(tag, listeners) } else if view.getRaw(tag) != nil { @@ -199,3 +343,23 @@ func setNoArgEventListener[V View](view View, tag PropertyName, value any) []Pro notCompatibleType(tag, value) 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 +} diff --git a/focusEvents.go b/focusEvents.go index e091cbc..9d09ff8 100644 --- a/focusEvents.go +++ b/focusEvents.go @@ -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 // +// 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. // If it is not specified then a value from the first argument (view) is returned. -func GetFocusListeners(view View, subviewID ...string) []func(View) { - return getNoArgEventListeners[View](view, subviewID, FocusEvent) +func GetFocusListeners(view View, subviewID ...string) []any { + return getNoArgEventRawListeners[View](view, subviewID, FocusEvent) } // 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. // If it is not specified then a value from the first argument (view) is returned. -func GetLostFocusListeners(view View, subviewID ...string) []func(View) { - return getNoArgEventListeners[View](view, subviewID, LostFocusEvent) +func GetLostFocusListeners(view View, subviewID ...string) []any { + return getNoArgEventRawListeners[View](view, subviewID, LostFocusEvent) } diff --git a/imageView.go b/imageView.go index 9053748..2be78c5 100644 --- a/imageView.go +++ b/imageView.go @@ -300,7 +300,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d switch command { case "imageViewError": for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, ErrorEvent) { - listener(imageView) + listener.Run(imageView) } case "imageViewLoaded": @@ -309,7 +309,7 @@ func (imageView *imageViewData) handleCommand(self View, command PropertyName, d imageView.currentSrc, _ = data.PropertyValue("current-src") for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) { - listener(imageView) + listener.Run(imageView) } default: @@ -370,3 +370,31 @@ func GetImageViewVerticalAlign(view View, subviewID ...string) int { func GetImageViewHorizontalAlign(view View, subviewID ...string) int { 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) +} diff --git a/popup.go b/popup.go index cc77b41..1b50ec8 100644 --- a/popup.go +++ b/popup.go @@ -2,6 +2,7 @@ package rui import ( "fmt" + "reflect" "strings" ) @@ -281,13 +282,30 @@ type Popup interface { 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 { layerView GridLayout popupView GridLayout contentView View buttons []PopupButton cancelable bool - dismissListener []func(Popup) + dismissListener []popupListener showTransform TransformProperty showOpacity float64 showDuration float64 @@ -649,7 +667,7 @@ func (popup *popupData) init(view View, popupParams Params) { } case DismissEvent: - if listeners, ok := valueToNoArgEventListeners[Popup](popup.contentView, value); ok { + if listeners, ok := valueToPopupEventListeners(value); ok { if listeners != nil { popup.dismissListener = listeners } @@ -887,7 +905,7 @@ func (popup *popupData) onDismiss() { popup.Session().callFunc("removeView", popup.layerView.htmlID()) for _, listener := range popup.dismissListener { - listener(popup) + listener.Run(popup) } } @@ -1014,3 +1032,139 @@ func (manager *popupManager) dismissPopup(popup Popup) { 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 +} diff --git a/view.go b/view.go index 2b2ab01..92a7107 100644 --- a/view.go +++ b/view.go @@ -1162,13 +1162,13 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb case FocusEvent: view.hasFocus = true for _, listener := range getNoArgEventListeners[View](view, nil, command) { - listener(self) + listener.Run(self) } case LostFocusEvent: view.hasFocus = false for _, listener := range getNoArgEventListeners[View](view, nil, command) { - listener(self) + listener.Run(self) } case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent: