Compare commits

...

16 Commits

Author SHA1 Message Date
Alexei Anoshenko c3c8b9e858 Changed ParseDataText function return values 2025-06-25 17:42:32 +03:00
Alexei Anoshenko 3090a0e94f Spell Checking 2025-06-25 14:36:04 +03:00
Alexei Anoshenko b0185726db Added ViewCreateListener interface 2025-06-25 13:53:08 +03:00
Alexei Anoshenko 2dd8d8d256 Updated readme 2025-06-24 19:31:38 +03:00
Alexei Anoshenko 4cec7fef26 Update CHANGELOG.md 2025-06-24 18:46:41 +03:00
Alexei Anoshenko e618377c11 Added binding support for canvas "draw-function" 2025-06-24 18:41:56 +03:00
Alexei Anoshenko 73b14ed78a Adde binding parameter to CreateView functions 2025-06-24 13:53:36 +03:00
Alexei Anoshenko bbbaf28aba Optimisation 2025-06-23 16:59:24 +03:00
Alexei Anoshenko 0433f460e4 Added change listener binding 2025-06-22 21:04:01 +03:00
Alexei Anoshenko d633c80155 Optimisation 2025-06-20 14:56:42 +03:00
Alexei Anoshenko cb4d197bb7 Bug fixing 2025-06-19 18:36:44 +03:00
Alexei Anoshenko 2f07584b37 Added binding to View.String() 2025-06-19 17:31:39 +03:00
Alexei Anoshenko 24aeeb515b Added binding support for MediaPlayer error event 2025-06-19 14:51:40 +03:00
Alexei Anoshenko f2fb948325 Improved binding for no args listeners 2025-06-18 14:19:57 +03:00
Alexei Anoshenko 4b00299878 Improved binding for 2 args listeners 2025-06-18 13:24:53 +03:00
Alexei Anoshenko 3c3c09b043 Added binding support 2025-06-17 21:08:16 +03:00
65 changed files with 4062 additions and 2088 deletions

View File

@ -1,3 +1,9 @@
# v0.20.0
* Added support of binding
* Added "binding" argument to CreateViewFromResources, CreateViewFromText, and CreateViewFromObject functions
* Changed ParseDataText function return values
# v0.19.0
* Added support of drag-and-drop
@ -5,7 +11,7 @@
# v0.18.2
* fixed typo: GetShadowPropertys -> GetShadowProperty
* fixed typo: GetShadowProperties -> GetShadowProperty
# v0.18.0

View File

@ -1086,7 +1086,7 @@ RadiusProperty, а не структура BoxRadius. Получить стру
Получить значение данного свойства можно с помощью функции
func GetShadowPropertys(view View, subviewID ...string) []ShadowProperty
func GetShadowProperties(view View, subviewID ...string) []ShadowProperty
Если тень не задана, то данная функция вернет пустой массив
@ -2291,14 +2291,14 @@ radius необходимо передать nil
#### Свойство "drag-data"
Для того чтобы сделать View перетаскиваемым ему неоходимо задать свойство "drag-data" (константа DragData).
Для того чтобы сделать View перетаскиваемым ему необходимо задать свойство "drag-data" (константа DragData).
Данное свойство задает множество перетаскиваемых данных в виде ключ:значение и имеет тип:
map[string]string
В качестве ключей рекомендуется использовать mime-тип значения.
Например, если в перетаскиваемыми данными асляется текст, то ключем должен быть "text/plain", если jpeg-изображение, то "image/jpg" и т.п.
Но это только рекомендация, ключем может быть любой текст.
Например, если в перетаскиваемыми данными является текст, то ключом должен быть "text/plain", если jpeg-изображение, то "image/jpg" и т.п.
Но это только рекомендация, ключом может быть любой текст.
Пример
@ -2318,7 +2318,7 @@ radius необходимо передать nil
По умолчанию при перетаскивании перемещается весь View. Часто это бывает не удобно, например если View очень большой.
Свойство "drag-image" (константа DragImage) типа string позволяет задать картинку, которая будет отображаться вместо View при перетаскивании.
В качестве значения "drag-image" задаеться:
В качестве значения "drag-image" задается:
* Имя изображения в ресурсах приложения
* Константа изображения
* URL изображения
@ -2442,15 +2442,15 @@ radius необходимо передать nil
#### События "drag-enter-event", "drag-leave-event" и "drag-over-event"
События "drag-enter-event", "drag-leave-event" и "drag-over-event" генерируются только для View приемника перетескиваемого объекта.
События "drag-enter-event", "drag-leave-event" и "drag-over-event" генерируются только для View приемника перетаскиваемого объекта.
Событие "drag-enter-event" генерируется когда перетаскиваемый оъект входит в область View приемника.
Событие "drag-enter-event" генерируется когда перетаскиваемый объект входит в область View приемника.
Событие "drag-leave-event" генерируется когда перетаскиваемый оъект покидает область View приемника.
Событие "drag-leave-event" генерируется когда перетаскиваемый объект покидает область View приемника.
Событие "drag-over-event" генерируется c определенным интервалом (несколько раз в секунду) пока перетаскиваемый оъект находится область View приемника.
Событие "drag-over-event" генерируется c определенным интервалом (несколько раз в секунду) пока перетаскиваемый объект находится область View приемника.
Пример, меняем цвет рамки с серой на красную когда перетаскиваемый оъект находится над областью приемника
Пример, меняем цвет рамки с серой на красную когда перетаскиваемый объект находится над областью приемника
view.SetParams(rui.Params{
rui.DragEnterEvent: func(view rui.View, event rui.DragAndDropEvent)) {
@ -4085,7 +4085,7 @@ int свойство "current" (константа Current). Значение "c
с помощью int свойств "checkbox-horizontal-align" и "checkbox-vertical-align" (константы
CheckboxHorizontalAlign и CheckboxVerticalAlign)
Свойство "checkbox-horizontal-align" (константа СheckboxHorizontalAlign) может принимать следующие значения:
Свойство "checkbox-horizontal-align" (константа CheckboxHorizontalAlign) может принимать следующие значения:
| Значение | Константа | Имя | Расположение чекбокса |
|:--------:|--------------|----------|-------------------------------------------------|
@ -4647,7 +4647,7 @@ rotation - угол поворота эллипса относительно ц
Метод NewPath() создает пустую фигуру. Далее вы должны описать фигуру используя методы интерфейса Path
Метод NewPathFromSvg(data string) Path создает фигуру описанную в параметре data.
Параметр data является описанием фигуры в формате елемента <path> svg изображения. Например
Параметр data является описанием фигуры в формате элемента <path> svg изображения. Например
path := canvas.NewPathFromSvg("M 30,0 C 30,0 27,8.6486 17,21.622 7,34.595 0,40 0,40 0,40 6,44.3243 17,58.378 28,72.432 30,80 30,80 30,80 37.8387,65.074 43,58.378 53,45.405 60,40 60,40 60,40 53,34.5946 43,21.622 33,8.649 30,0 30,0 Z")
@ -5704,6 +5704,79 @@ Safari и Firefox.
Для получения объекта используется метод Object.
Для получения элементов массива используются методы ArraySize, ArrayElement и ArrayElements
## Связывание (binding)
Механизм связывания предназначен для задания обработчиков событий и слушателей изменений в ресурсах приложений.
Рассмотрим пример:
Файл ресурсов описывающий кнопку button.rui
Button {
click-event = ButtonClick,
}
Код для создания этой кнопки из ресурсов
type button struct {
view rui.View
}
func createButton(session rui.Session) rui.View {
return rui.CreateViewFromResources(session, "button.rui", new(button))
}
func (button *button) OnCreate(view rui.View) {
button.view = view
}
func (button *button) ButtonClick() {
rui.DebugLog("Button clicked")
}
В данном примере в файле ресурсов указано, что в качестве обработчика клика надо использовать функцию с именем "ButtonClick".
При создании View из ресурсов с помощью функции CreateViewFromResources в качестве третьего параметра задается объект связывания.
При возникновении события "click-event" система будет искать в связанном объекте один из следующих методов
ButtonClick()
ButtonClick(rui.View)
ButtonClick(rui.MouseEvent)
ButtonClick(rui.View, rui.MouseEvent)
Система найдет метод ButtonClick() и вызовет его.
Также для связанного объекта может задаваться опциональный метод
OnCreate(view rui.View)
Данный метод вызывается функциями CreateViewFromText, CreateViewFromResources и CreateViewFromObject после создания View.
В данном примере этот метод используется для сохранения указателя на созданный View.
Теперь рассмотрим как добавить отслеживание изменения свойств.
Для этого добавим в пример слушателя изменения свойства "background-color" в свойство "change-listeners"
Button {
click-event = ButtonClick,
change-listeners = {
background-color = BackgroundColorChanged,
},
}
И добавим соответствующий метод
func (button *button) BackgroundColorChanged() {
rui.DebugLog("Background color changed")
}
Данный метод может иметь одно из следующих описаний
BackgroundColorChanged()
BackgroundColorChanged(rui.View)
BackgroundColorChanged(rui.PropertyName)
BackgroundColorChanged(rui.View, rui.PropertyName)
Важное замечание: Все методы вызываемые через связывание должны быть публичными (начинаться с большой буквы)
## Ресурсы
Ресурсы (картинки, темы, переводы и т.д.) с которыми работает приложение должны размещаться по

View File

@ -520,7 +520,7 @@ For the "edit-text-changed" event, this
* func(newText string)
* []func(editor EditView, newText string)
* []func(newText string)
* []any содержащий только func(editor EditView, newText string) и func(newText string)
* []any containing only func(editor EditView, newText string) и func(newText string)
And the "edit-text-changed" property always stores and returns []func(EditView, string).
@ -1062,7 +1062,7 @@ The ShadowProperty text representation has the following format:
You can get the value of "shadow" property using the function
func GetShadowPropertys(view View, subviewID ...string) []ShadowProperty
func GetShadowProperties(view View, subviewID ...string) []ShadowProperty
If no shadow is specified, then this function will return an empty array
@ -5720,6 +5720,80 @@ using the SetResourcePath function before creating the Application:
app.Start("localhost:8000")
}
## Binding
The binding mechanism is designed to set event handlers and listeners for changes in application resources.
Let's look at an example:
The resource file ("button.rui") describing the button:
Button {
click-event = ButtonClick,
}
The code to create this button from resources
type button struct {
view rui.View
}
func createButton(session rui.Session) rui.View {
return rui.CreateViewFromResources(session, "button.rui", new(button))
}
func (button *button) OnCreate(view rui.View) {
button.view = view
}
func (button *button) ButtonClick() {
rui.DebugLog("Button clicked")
}
In this example, the resource file specifies that a function named "ButtonClick" should be used as the click handler.
When creating a View from resources using the CreateViewFromResources function, the binding object is specified as the third parameter.
When a "click-event" occurs, the system will look for one of the following methods in the binding object
ButtonClick()
ButtonClick(rui.View)
ButtonClick(rui.MouseEvent)
ButtonClick(rui.View, rui.MouseEvent)
The system will find the ButtonClick() method and call it.
The optional method OnCreate can also be specified for the associated object
OnCreate(view rui.View)
This method is called by the CreateViewFromText, CreateViewFromResources, and CreateViewFromObject functions after the View has been created.
In this example, this method is used to save a pointer to the created View.
Now let's look at how to add property change tracking.
To do this, let's add a property change listener "background-color" to the property "change-listeners" in the example.
Button {
click-event = ButtonClick,
change-listeners = {
background-color = BackgroundColorChanged,
},
}
And we will add the corresponding method
func (button *button) BackgroundColorChanged() {
rui.DebugLog("Background color changed")
}
This method can have one of the following descriptions
BackgroundColorChanged()
BackgroundColorChanged(rui.View)
BackgroundColorChanged(rui.PropertyName)
BackgroundColorChanged(rui.View, rui.PropertyName)
Important note: All methods called via binding must be public (start with a capital letter)
## Images for screens with different pixel densities
If you need to add separate images to the resources for screens with different pixel densities,
@ -5919,7 +5993,7 @@ The library defines a number of constants and styles. You can override them in y
System styles that you can override:
| Style name | Описание |
| Style name | Description |
|---------------------|---------------------------------------------------------------------|
| ruiApp | This style is used to set the default text style (font, size, etc.) |
| ruiView | Default View Style |
@ -6011,7 +6085,7 @@ Translation files must have the "rui" extension and the following format
<text 2> = <translation 2>,
},
<язык 2> = _{
<language 2> = _{
<text 1> = <translation 1>,
<text 2> = <translation 2>,

View File

@ -2,6 +2,7 @@ package rui
import (
"fmt"
"maps"
"math"
"strconv"
"strings"
@ -209,7 +210,7 @@ type animationData struct {
usageCounter int
view View
listener func(view View, animation AnimationProperty, event PropertyName)
oldListeners map[PropertyName][]func(View, PropertyName)
oldListeners map[PropertyName][]oneArgListener[View, PropertyName]
oldAnimation []AnimationProperty
}
@ -661,7 +662,7 @@ func (animation *animationData) animationCSS(session Session) string {
buffer.WriteString(animation.keyFramesName)
if duration, ok := floatProperty(animation, Duration, session, 1); ok && duration > 0 {
buffer.WriteString(fmt.Sprintf(" %gs ", duration))
fmt.Fprintf(buffer, " %gs ", duration)
} else {
buffer.WriteString(" 1s ")
}
@ -669,7 +670,7 @@ func (animation *animationData) animationCSS(session Session) string {
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
buffer.WriteString(fmt.Sprintf(" %gs", delay))
fmt.Fprintf(buffer, " %gs", delay)
} else {
buffer.WriteString(" 0s")
}
@ -678,7 +679,7 @@ func (animation *animationData) animationCSS(session Session) string {
if iterationCount == 0 {
iterationCount = 1
}
buffer.WriteString(fmt.Sprintf(" %d ", iterationCount))
fmt.Fprintf(buffer, " %d ", iterationCount)
} else {
buffer.WriteString(" infinite ")
}
@ -699,7 +700,7 @@ func (animation *animationData) animationCSS(session Session) string {
func (animation *animationData) transitionCSS(buffer *strings.Builder, session Session) {
if duration, ok := floatProperty(animation, Duration, session, 1); ok && duration > 0 {
buffer.WriteString(fmt.Sprintf(" %gs ", duration))
fmt.Fprintf(buffer, " %gs ", duration)
} else {
buffer.WriteString(" 1s ")
}
@ -707,7 +708,7 @@ func (animation *animationData) transitionCSS(buffer *strings.Builder, session S
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
buffer.WriteString(fmt.Sprintf(" %gs", delay))
fmt.Fprintf(buffer, " %gs", delay)
}
}
@ -979,11 +980,10 @@ func (style *viewStyle) Transition(tag PropertyName) AnimationProperty {
}
func (style *viewStyle) Transitions() map[PropertyName]AnimationProperty {
result := map[PropertyName]AnimationProperty{}
for tag, animation := range getTransitionProperty(style) {
result[tag] = animation
if transitions := getTransitionProperty(style); transitions != nil {
return maps.Clone(transitions)
}
return result
return map[PropertyName]AnimationProperty{}
}
func (style *viewStyle) SetTransition(tag PropertyName, animation AnimationProperty) {
@ -1083,7 +1083,9 @@ func SetAnimated(rootView View, viewID string, tag PropertyName, value any, anim
}
// IsAnimationPaused returns "true" if an animation of the subview is paused, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsAnimationPaused(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, AnimationPaused, false)
}

View File

@ -156,45 +156,6 @@ const (
AnimationIterationEvent PropertyName = "animation-iteration-event"
)
/*
func setTransitionListener(properties Properties, tag PropertyName, value any) bool {
if listeners, ok := valueToOneArgEventListeners[View, string](value); ok {
if len(listeners) == 0 {
properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
return true
}
notCompatibleType(tag, value)
return false
}
func (view *viewData) removeTransitionListener(tag PropertyName) {
delete(view.properties, tag)
if view.created {
if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
func transitionEventsHtml(view View, buffer *strings.Builder) {
for _, tag := range []PropertyName{TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent} {
if value := view.getRaw(tag); value != nil {
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
}
}
}
*/
func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) {
if propertyName, ok := data.PropertyValue("property"); ok {
property := PropertyName(propertyName)
@ -208,50 +169,11 @@ func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject)
}
for _, listener := range getOneArgEventListeners[View, PropertyName](view, nil, tag) {
listener(view, property)
listener.Run(view, property)
}
}
}
/*
func setAnimationListener(properties Properties, tag PropertyName, value any) bool {
if listeners, ok := valueToOneArgEventListeners[View, string](value); ok {
if len(listeners) == 0 {
properties.setRaw(tag, nil)
} else {
properties.setRaw(tag, listeners)
}
return true
}
notCompatibleType(tag, value)
return false
}
func (view *viewData) removeAnimationListener(tag PropertyName) {
delete(view.properties, tag)
if view.created {
if js, ok := eventJsFunc[tag]; ok {
view.session.removeProperty(view.htmlID(), js.jsEvent)
}
}
}
func animationEventsHtml(view View, buffer *strings.Builder) {
for _, tag := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent} {
if value := view.getRaw(tag); value != nil {
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
buffer.WriteString(`(this, event)" `)
}
}
}
}
}
*/
func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) {
if listeners := getOneArgEventListeners[View, string](view, nil, tag); len(listeners) > 0 {
id := ""
@ -263,63 +185,135 @@ func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) {
}
}
for _, listener := range listeners {
listener(view, id)
listener.Run(view, id)
}
}
}
// GetTransitionRunListeners returns the "transition-run-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTransitionRunListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, TransitionRunEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetTransitionRunListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, TransitionRunEvent)
}
// GetTransitionStartListeners returns the "transition-start-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTransitionStartListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, TransitionStartEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetTransitionStartListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, TransitionStartEvent)
}
// GetTransitionEndListeners returns the "transition-end-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTransitionEndListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, TransitionEndEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetTransitionEndListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, TransitionEndEvent)
}
// GetTransitionCancelListeners returns the "transition-cancel-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTransitionCancelListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, TransitionCancelEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetTransitionCancelListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, TransitionCancelEvent)
}
// GetAnimationStartListeners returns the "animation-start-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetAnimationStartListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, AnimationStartEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetAnimationStartListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, AnimationStartEvent)
}
// GetAnimationEndListeners returns the "animation-end-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetAnimationEndListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, AnimationEndEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetAnimationEndListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, AnimationEndEvent)
}
// GetAnimationCancelListeners returns the "animation-cancel-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetAnimationCancelListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, AnimationCancelEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetAnimationCancelListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, AnimationCancelEvent)
}
// GetAnimationIterationListeners returns the "animation-iteration-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetAnimationIterationListeners(view View, subviewID ...string) []func(View, string) {
return getOneArgEventListeners[View, string](view, subviewID, AnimationIterationEvent)
//
// Result elements can be of the following types:
// - func(rui.View, string),
// - func(rui.View),
// - func(string),
// - 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 GetAnimationIterationListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, string](view, subviewID, AnimationIterationEvent)
}

View File

@ -1,5 +1,7 @@
package rui
import "slices"
func (animation *animationData) Start(view View, listener func(view View, animation AnimationProperty, event PropertyName)) bool {
if view == nil {
ErrorLog("nil View in animation.Start() function")
@ -13,28 +15,22 @@ func (animation *animationData) Start(view View, listener func(view View, animat
animation.listener = listener
animation.oldAnimation = nil
//if getOneArgEventListeners[View, PropertyName](view, nil, Animation)
if value := view.Get(Animation); value != nil {
if oldAnimation, ok := value.([]AnimationProperty); ok && len(oldAnimation) > 0 {
animation.oldAnimation = oldAnimation
}
}
animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
animation.oldListeners = map[PropertyName][]oneArgListener[View, PropertyName]{}
setListeners := func(event PropertyName, listener func(View, PropertyName)) {
var listeners []func(View, PropertyName) = nil
if value := view.Get(event); value != nil {
if oldListeners, ok := value.([]func(View, PropertyName)); ok && len(oldListeners) > 0 {
listeners = oldListeners
}
}
if listeners == nil {
view.Set(event, listener)
} else {
animation.oldListeners[event] = listeners
view.Set(event, append(listeners, listener))
listeners := getOneArgEventListeners[View, PropertyName](view, nil, event)
if len(listeners) > 0 {
animation.oldListeners[event] = slices.Clone(listeners)
}
view.Set(event, append(listeners, newOneArgListenerVE(listener)))
}
setListeners(AnimationStartEvent, animation.onAnimationStart)
@ -49,7 +45,7 @@ func (animation *animationData) Start(view View, listener func(view View, animat
func (animation *animationData) finish() {
if animation.view != nil {
for _, event := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationCancelEvent, AnimationIterationEvent} {
if listeners, ok := animation.oldListeners[event]; ok {
if listeners, ok := animation.oldListeners[event]; ok && len(listeners) > 0 {
animation.view.Set(event, listeners)
} else {
animation.view.Remove(event)
@ -63,7 +59,7 @@ func (animation *animationData) finish() {
animation.view.Set(Animation, "")
}
animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
animation.oldListeners = map[PropertyName][]oneArgListener[View, PropertyName]{}
animation.view = nil
animation.listener = nil

View File

@ -164,7 +164,12 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
DebugLog(message)
}
if obj := ParseDataText(message); obj != nil {
obj, err := ParseDataText(message)
if err != nil {
ErrorLog(err.Error())
return
}
var session Session = nil
var response chan string = nil
@ -224,7 +229,6 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
}
}
}
}
func (app *application) socketReader(bridge *wsBridge) {
var session Session
@ -241,9 +245,13 @@ func (app *application) socketReader(bridge *wsBridge) {
DebugLog("🖥️ -> " + message)
}
if obj := ParseDataText(message); obj != nil {
command := obj.Tag()
switch command {
obj, err := ParseDataText(message)
if err != nil {
ErrorLog(err.Error())
return
}
switch command := obj.Tag(); command {
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
@ -296,7 +304,6 @@ func (app *application) socketReader(bridge *wsBridge) {
}
}
}
}
func sessionEventHandler(session Session, events chan DataObject, bridge bridge) {
for {

View File

@ -78,6 +78,16 @@ func createBackground(obj DataObject) BackgroundElement {
return result
}
func parseBackgroundText(text string) BackgroundElement {
obj, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return nil
}
return createBackground(obj)
}
func parseBackgroundValue(value any) []BackgroundElement {
switch value := value.(type) {
@ -96,15 +106,11 @@ func parseBackgroundValue(value any) []BackgroundElement {
} else {
return nil
}
} else if obj := ParseDataText(el.Value()); obj != nil {
if element := createBackground(obj); element != nil {
} else if element := parseBackgroundText(el.Value()); element != nil {
background = append(background, element)
} else {
return nil
}
} else {
return nil
}
}
return background
@ -125,44 +131,34 @@ func parseBackgroundValue(value any) []BackgroundElement {
return background
case string:
if obj := ParseDataText(value); obj != nil {
if element := createBackground(obj); element != nil {
if element := parseBackgroundText(value); element != nil {
return []BackgroundElement{element}
}
}
case []string:
elements := make([]BackgroundElement, 0, len(value))
for _, element := range value {
if obj := ParseDataText(element); obj != nil {
if element := createBackground(obj); element != nil {
for _, text := range value {
if element := parseBackgroundText(text); element != nil {
elements = append(elements, element)
} else {
return nil
}
} else {
return nil
}
}
return elements
case []any:
elements := make([]BackgroundElement, 0, len(value))
for _, element := range value {
switch element := element.(type) {
for _, val := range value {
switch val := val.(type) {
case BackgroundElement:
elements = append(elements, element)
elements = append(elements, val)
case string:
if obj := ParseDataText(element); obj != nil {
if element := createBackground(obj); element != nil {
if element := parseBackgroundText(val); element != nil {
elements = append(elements, element)
} else {
return nil
}
} else {
return nil
}
default:
return nil
@ -268,14 +264,16 @@ func backgroundStyledPropery(view View, subviewID []string, tag PropertyName) []
// GetBackground returns the view background.
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackground(view View, subviewID ...string) []BackgroundElement {
return backgroundStyledPropery(view, subviewID, Background)
}
// GetMask returns the view mask.
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetMask(view View, subviewID ...string) []BackgroundElement {
return backgroundStyledPropery(view, subviewID, Mask)
}
@ -284,7 +282,8 @@ func GetMask(view View, subviewID ...string) []BackgroundElement {
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackgroundClip(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundClip, 0, false)
}
@ -293,7 +292,8 @@ func GetBackgroundClip(view View, subviewID ...string) int {
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackgroundOrigin(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundOrigin, 0, false)
}
@ -302,7 +302,8 @@ func GetBackgroundOrigin(view View, subviewID ...string) int {
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetMaskClip(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MaskClip, 0, false)
}
@ -311,7 +312,8 @@ func GetMaskClip(view View, subviewID ...string) int {
//
// BorderBox (0), PaddingBox (1), ContentBox (2)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetMaskOrigin(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MaskOrigin, 0, false)
}

View File

@ -43,7 +43,7 @@ type backgroundRadialGradient struct {
//
// The following properties can be used:
// - "gradient" (Gradient) - Describes gradient stop points. This is a mandatory property while describing background gradients.
// - "center-x" (CenterX), "center-y" (CenterY) - Defines the gradient center point cooordinates.
// - "center-x" (CenterX), "center-y" (CenterY) - Defines the gradient center point coordinates.
// - "radial-gradient-radius" (RadialGradientRadius) - Defines radius of the radial gradient.
// - "radial-gradient-shape" (RadialGradientShape) - Defines shape of the radial gradient.
// - "repeating" (Repeating) - Defines whether stop points needs to be repeated after the last one.

View File

@ -610,7 +610,10 @@ func borderSet(properties Properties, tag PropertyName, value any) []PropertyNam
case Left, Right, Top, Bottom:
switch value := value.(type) {
case string:
if obj := ParseDataText(value); obj != nil {
obj, err := ParseDataText(value)
if err != nil {
ErrorLog(err.Error())
} else {
return setSingleBorderObject(tag, obj)
}

View File

@ -201,7 +201,7 @@ type Canvas interface {
// SetConicGradientFillStyle sets a conic gradient around a point
// to use inside shapes
// * x, y - coordinates of the center of the conic gradient in pilels;
// * x, y - coordinates of the center of the conic gradient in piles;
// * startAngle - the angle at which to begin the gradient, in radians. The angle starts from a line going horizontally right from the center, and proceeds clockwise.
// * startColor - the start color;
// * endColor - the end color;
@ -210,7 +210,7 @@ type Canvas interface {
// SetConicGradientFillStyle sets a conic gradient around a point
// to use inside shapes
// * x, y - coordinates of the center of the conic gradient in pilels;
// * x, y - coordinates of the center of the conic gradient in piles;
// * startAngle - the angle at which to begin the gradient, in radians. The angle starts from a line going horizontally right from the center, and proceeds clockwise.
// * startColor - the start color;
// * endColor - the end color;
@ -575,7 +575,7 @@ func (canvas *canvasData) SetLineDash(dash []float64, offset float64) {
for _, val := range dash {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
fmt.Fprintf(buffer, "%g", val))
}
buffer.WriteRune(']')

View File

@ -1,5 +1,7 @@
package rui
import "reflect"
// DrawFunction is the constant for "draw-function" property tag.
//
// Used by `CanvasView`.
@ -55,7 +57,7 @@ func (canvasView *canvasViewData) removeFunc(tag PropertyName) []PropertyName {
if tag == DrawFunction {
if canvasView.getRaw(DrawFunction) != nil {
canvasView.setRaw(DrawFunction, nil)
canvasView.Redraw()
//canvasView.Redraw()
return []PropertyName{DrawFunction}
}
return []PropertyName{}
@ -66,9 +68,14 @@ func (canvasView *canvasViewData) removeFunc(tag PropertyName) []PropertyName {
func (canvasView *canvasViewData) setFunc(tag PropertyName, value any) []PropertyName {
if tag == DrawFunction {
if fn, ok := value.(func(Canvas)); ok {
canvasView.setRaw(DrawFunction, fn)
} else {
switch value := value.(type) {
case func(Canvas):
canvasView.setRaw(DrawFunction, value)
case string:
canvasView.setRaw(DrawFunction, value)
default:
notCompatibleType(tag, value)
return nil
}
@ -94,8 +101,30 @@ func (canvasView *canvasViewData) Redraw() {
canvas := newCanvas(canvasView)
canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height)
if value := canvasView.getRaw(DrawFunction); value != nil {
if drawer, ok := value.(func(Canvas)); ok {
switch drawer := value.(type) {
case func(Canvas):
drawer(canvas)
case string:
bind := canvasView.binding()
if bind == nil {
ErrorLogF(`There is no a binding object for call "%s"`, drawer)
break
}
val := reflect.ValueOf(bind)
method := val.MethodByName(drawer)
if !method.IsValid() {
ErrorLogF(`The "%s" method is not valid`, drawer)
break
}
methodType := method.Type()
if methodType.NumIn() == 1 && equalType(methodType.In(0), reflect.TypeOf(canvas)) {
method.Call([]reflect.Value{reflect.ValueOf(canvas)})
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, drawer)
}
}
}
canvas.finishDraw()

View File

@ -53,8 +53,8 @@ func (button *checkboxData) init(session Session) {
button.remove = button.removeFunc
button.changed = button.propertyChanged
button.setRaw(ClickEvent, []func(View, MouseEvent){checkboxClickListener})
button.setRaw(KeyDownEvent, []func(View, KeyEvent){checkboxKeyListener})
button.setRaw(ClickEvent, []oneArgListener[View, MouseEvent]{newOneArgListenerVE(checkboxClickListener)})
button.setRaw(KeyDownEvent, []oneArgListener[View, KeyEvent]{newOneArgListenerVE(checkboxKeyListener)})
}
func (button *checkboxData) Focusable() bool {
@ -67,9 +67,9 @@ func (button *checkboxData) propertyChanged(tag PropertyName) {
case Checked:
session := button.Session()
checked := IsCheckboxChecked(button)
if listeners := GetCheckboxChangedListeners(button); len(listeners) > 0 {
if listeners := getOneArgEventListeners[Checkbox, bool](button, nil, CheckboxChangedEvent); len(listeners) > 0 {
for _, listener := range listeners {
listener(button, checked)
listener.Run(button, checked)
}
}
@ -103,7 +103,7 @@ func (button *checkboxData) setFunc(tag PropertyName, value any) []PropertyName
switch tag {
case ClickEvent:
if listeners, ok := valueToOneArgEventListeners[View, MouseEvent](value); ok && listeners != nil {
listeners = append(listeners, checkboxClickListener)
listeners = append(listeners, newOneArgListenerVE(checkboxClickListener))
button.setRaw(tag, listeners)
return []PropertyName{tag}
}
@ -111,7 +111,7 @@ func (button *checkboxData) setFunc(tag PropertyName, value any) []PropertyName
case KeyDownEvent:
if listeners, ok := valueToOneArgEventListeners[View, KeyEvent](value); ok && listeners != nil {
listeners = append(listeners, checkboxKeyListener)
listeners = append(listeners, newOneArgListenerVE(checkboxKeyListener))
button.setRaw(tag, listeners)
return []PropertyName{tag}
}
@ -134,31 +134,17 @@ func (button *checkboxData) setFunc(tag PropertyName, value any) []PropertyName
func (button *checkboxData) removeFunc(tag PropertyName) []PropertyName {
switch tag {
case ClickEvent:
button.setRaw(ClickEvent, []func(View, MouseEvent){checkboxClickListener})
button.setRaw(ClickEvent, []oneArgListener[View, MouseEvent]{newOneArgListenerVE(checkboxClickListener)})
return []PropertyName{ClickEvent}
case KeyDownEvent:
button.setRaw(KeyDownEvent, []func(View, KeyEvent){checkboxKeyListener})
button.setRaw(KeyDownEvent, []oneArgListener[View, KeyEvent]{newOneArgListenerVE(checkboxKeyListener)})
return []PropertyName{ClickEvent}
}
return button.viewsContainerData.removeFunc(tag)
}
/*
func (button *checkboxData) changedCheckboxState(state bool) {
for _, listener := range GetCheckboxChangedListeners(button) {
listener(button, state)
}
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
button.htmlCheckbox(buffer, state)
button.Session().updateInnerHTML(button.htmlID()+"checkbox", buffer.String())
}
*/
func checkboxClickListener(view View, _ MouseEvent) {
view.Set(Checked, !IsCheckboxChecked(view))
BlurView(view)
@ -302,26 +288,41 @@ func checkboxVerticalAlignCSS(view View) string {
}
// IsCheckboxChecked returns true if the Checkbox is checked, false otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsCheckboxChecked(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Checked, false)
}
// GetCheckboxVerticalAlign return the vertical align of a Checkbox subview: TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is 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.
func GetCheckboxVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxVerticalAlign, LeftAlign, false)
}
// GetCheckboxHorizontalAlign return the vertical align of a Checkbox subview: LeftAlign (0), RightAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is 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.
func GetCheckboxHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxHorizontalAlign, TopAlign, false)
}
// GetCheckboxChangedListeners returns the CheckboxChangedListener list of an Checkbox subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetCheckboxChangedListeners(view View, subviewID ...string) []func(Checkbox, bool) {
return getOneArgEventListeners[Checkbox, bool](view, subviewID, CheckboxChangedEvent)
// If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(Checkbox, bool),
// - func(Checkbox),
// - func(bool),
// - 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 GetCheckboxChangedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[Checkbox, bool](view, subviewID, CheckboxChangedEvent)
}

View File

@ -106,13 +106,13 @@ func (picker *colorPickerData) propertyChanged(tag PropertyName) {
color := GetColorPickerValue(picker)
picker.Session().callFunc("setInputValue", picker.htmlID(), color.rgbString())
if listeners := GetColorChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[ColorPicker, Color](picker, nil, ColorChangedEvent); len(listeners) > 0 {
oldColor := Color(0)
if value := picker.getRaw("old-color"); value != nil {
oldColor = value.(Color)
}
for _, listener := range listeners {
listener(picker, color, oldColor)
listener.Run(picker, color, oldColor)
}
}
@ -156,11 +156,11 @@ func (picker *colorPickerData) handleCommand(self View, command PropertyName, da
oldColor := GetColorPickerValue(picker)
picker.properties[ColorPickerValue] = color
if color != oldColor {
for _, listener := range GetColorChangedListeners(picker) {
listener(picker, color, oldColor)
for _, listener := range getTwoArgEventListeners[ColorPicker, Color](picker, nil, ColorChangedEvent) {
listener.Run(picker, color, oldColor)
}
if listener, ok := picker.changeListener[ColorPickerValue]; ok {
listener(picker, ColorPickerValue)
listener.Run(picker, ColorPickerValue)
}
}
}
@ -172,7 +172,9 @@ func (picker *colorPickerData) handleCommand(self View, command PropertyName, da
}
// GetColorPickerValue returns the value of ColorPicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetColorPickerValue(view View, subviewID ...string) Color {
if view = getSubview(view, subviewID); view != nil {
if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok {
@ -191,7 +193,18 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
// GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
return getTwoArgEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
//
// Result elements can be of the following types:
// - func(rui.ColorPicker, rui.Color, rui.Color),
// - func(rui.ColorPicker, rui.Color),
// - func(rui.ColorPicker),
// - func(rui.Color, rui.Color),
// - func(rui.Color),
// - 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 GetColorChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
}

View File

@ -249,13 +249,17 @@ func GetColumnSeparatorColor(view View, subviewID ...string) Color {
// GetColumnFill returns a "column-fill" property value of the subview.
// Returns one of next values: ColumnFillBalance (0) or ColumnFillAuto (1)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetColumnFill(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ColumnFill, ColumnFillBalance, true)
}
// IsColumnSpanAll returns a "column-span-all" property value of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsColumnSpanAll(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, ColumnSpanAll, false)
}

View File

@ -93,12 +93,12 @@ func NewColumnSeparator(style int, color Color, width SizeUnit) ColumnSeparatorP
func (separator *columnSeparatorProperty) init() {
separator.dataProperty.init()
separator.normalize = normalizeVolumnSeparatorTag
separator.normalize = normalizeColumnSeparatorTag
separator.set = columnSeparatorSet
separator.supportedProperties = []PropertyName{Style, Width, ColorTag}
}
func normalizeVolumnSeparatorTag(tag PropertyName) PropertyName {
func normalizeColumnSeparatorTag(tag PropertyName) PropertyName {
tag = defaultNormalize(tag)
switch tag {
case ColumnSeparatorStyle, "separator-style":

View File

@ -98,8 +98,8 @@ func (customView *CustomViewData) SetParams(params Params) bool {
}
// SetChangeListener set the function to track the change of the View property
func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
customView.superView.SetChangeListener(tag, listener)
func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener any) bool {
return customView.superView.SetChangeListener(tag, listener)
}
// Remove removes the property with name defined by the argument
@ -292,15 +292,15 @@ func (customView *CustomViewData) ViewIndex(view View) int {
return -1
}
func (customView *CustomViewData) exscludeTags() []PropertyName {
func (customView *CustomViewData) excludeTags() []PropertyName {
if customView.superView != nil {
exsclude := []PropertyName{}
exclude := []PropertyName{}
for tag, value := range customView.defaultParams {
if value == customView.superView.getRaw(tag) {
exsclude = append(exsclude, tag)
exclude = append(exclude, tag)
}
}
return exsclude
return exclude
}
return nil
}
@ -310,7 +310,7 @@ func (customView *CustomViewData) String() string {
if customView.superView != nil {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
writeViewStyle(customView.tag, customView, buffer, "", customView.exscludeTags())
writeViewStyle(customView.tag, customView, buffer, "", customView.excludeTags())
return buffer.String()
}
return customView.tag + " { }"
@ -352,3 +352,10 @@ func (customView *CustomViewData) LoadFile(file FileInfo, result func(FileInfo,
customView.superView.LoadFile(file, result)
}
}
func (customView *CustomViewData) binding() any {
if customView.superView != nil {
return customView.superView.binding()
}
return nil
}

400
data.go
View File

@ -1,6 +1,9 @@
package rui
import (
"errors"
"fmt"
"slices"
"strings"
"unicode"
)
@ -47,16 +50,22 @@ type DataObject interface {
// ToParams create a params(map) representation of a data object
ToParams() Params
// PropertyByTag removes a data node corresponding to a property tag and returns it
RemovePropertyByTag(tag string) DataNode
}
// DataNodeType defines the type of DataNode
type DataNodeType int
// Constants which are used to describe a node type, see [DataNode]
const (
// TextNode - node is the pair "tag - text value". Syntax: <tag> = <text>
TextNode = 0
TextNode DataNodeType = 0
// ObjectNode - node is the pair "tag - object". Syntax: <tag> = <object name>{...}
ObjectNode = 1
ObjectNode DataNodeType = 1
// ArrayNode - node is the pair "tag - object". Syntax: <tag> = [...]
ArrayNode = 2
ArrayNode DataNodeType = 2
)
// DataNode interface of a data node
@ -65,7 +74,7 @@ type DataNode interface {
Tag() string
// Type returns a node type. Possible values are TextNode, ObjectNode and ArrayNode
Type() int
Type() DataNodeType
// Text returns node text
Text() string
@ -158,6 +167,28 @@ func (object *dataObject) PropertyByTag(tag string) DataNode {
return nil
}
func (object *dataObject) RemovePropertyByTag(tag string) DataNode {
if object.property != nil {
for i, node := range object.property {
if node.Tag() == tag {
switch i {
case 0:
object.property = object.property[1:]
case len(object.property) - 1:
object.property = object.property[:len(object.property)-1]
default:
object.property = append(object.property[:i], object.property[i+1:]...)
}
return node
}
}
}
return nil
}
func (object *dataObject) PropertyValue(tag string) (string, bool) {
if node := object.PropertyByTag(tag); node != nil && node.Type() == TextNode {
return node.Text(), true
@ -253,7 +284,7 @@ func (node *dataNode) Tag() string {
return node.tag
}
func (node *dataNode) Type() int {
func (node *dataNode) Type() DataNodeType {
if node.array != nil {
return ArrayNode
}
@ -314,54 +345,49 @@ func (node *dataNode) ArrayAsParams() []Params {
return result
}
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) DataObject {
if strings.ContainsAny(text, "\r") {
text = strings.Replace(text, "\r\n", "\n", -1)
text = strings.Replace(text, "\r", "\n", -1)
type dataParser struct {
data []rune
size int
pos int
line int
lineStart int
}
data := append([]rune(text), rune(0))
pos := 0
size := len(data) - 1
line := 1
lineStart := 0
skipSpaces := func(skipNewLine bool) {
for pos < size {
switch data[pos] {
func (parser *dataParser) skipSpaces(skipNewLine bool) {
for parser.pos < parser.size {
switch parser.data[parser.pos] {
case '\n':
if !skipNewLine {
return
}
line++
lineStart = pos + 1
parser.line++
parser.lineStart = parser.pos + 1
case '/':
if pos+1 < size {
switch data[pos+1] {
if parser.pos+1 < parser.size {
switch parser.data[parser.pos+1] {
case '/':
pos += 2
for pos < size && data[pos] != '\n' {
pos++
parser.pos += 2
for parser.pos < parser.size && parser.data[parser.pos] != '\n' {
parser.pos++
}
pos--
parser.pos--
case '*':
pos += 3
parser.pos += 3
for {
if pos >= size {
if parser.pos >= parser.size {
ErrorLog("Unexpected end of file")
return
}
if data[pos-1] == '*' && data[pos] == '/' {
if parser.data[parser.pos-1] == '*' && parser.data[parser.pos] == '/' {
break
}
if data[pos-1] == '\n' {
line++
lineStart = pos
if parser.data[parser.pos-1] == '\n' {
parser.line++
parser.lineStart = parser.pos
}
pos++
parser.pos++
}
default:
@ -373,75 +399,72 @@ func ParseDataText(text string) DataObject {
// do nothing
default:
if !unicode.IsSpace(data[pos]) {
if !unicode.IsSpace(parser.data[parser.pos]) {
return
}
}
pos++
parser.pos++
}
}
parseTag := func() (string, bool) {
skipSpaces(true)
startPos := pos
if data[pos] == '`' {
pos++
func (parser *dataParser) parseTag() (string, error) {
parser.skipSpaces(true)
startPos := parser.pos
switch parser.data[parser.pos] {
case '`':
parser.pos++
startPos++
for data[pos] != '`' {
pos++
if pos >= size {
ErrorLog("Unexpected end of text")
return string(data[startPos:size]), false
for parser.data[parser.pos] != '`' {
parser.pos++
if parser.pos >= parser.size {
return string(parser.data[startPos:parser.size]), errors.New("unexpected end of text")
}
}
str := string(data[startPos:pos])
pos++
return str, true
str := string(parser.data[startPos:parser.pos])
parser.pos++
return str, nil
} else if data[pos] == '\'' || data[pos] == '"' {
stopSymbol := data[pos]
pos++
case '\'', '"':
stopSymbol := parser.data[parser.pos]
parser.pos++
startPos++
slash := false
for stopSymbol != data[pos] {
if data[pos] == '\\' {
pos += 2
for stopSymbol != parser.data[parser.pos] {
if parser.data[parser.pos] == '\\' {
parser.pos += 2
slash = true
} else {
pos++
parser.pos++
}
if pos >= size {
ErrorLog("Unexpected end of text")
return string(data[startPos:size]), false
if parser.pos >= parser.size {
return string(parser.data[startPos:parser.size]), errors.New("unexpected end of text")
}
}
if !slash {
str := string(data[startPos:pos])
pos++
skipSpaces(false)
return str, true
str := string(parser.data[startPos:parser.pos])
parser.pos++
parser.skipSpaces(false)
return str, nil
}
buffer := make([]rune, pos-startPos+1)
buffer := make([]rune, parser.pos-startPos+1)
n1 := 0
n2 := startPos
invalidEscape := func() (string, bool) {
str := string(data[startPos:pos])
pos++
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
return str, false
invalidEscape := func() (string, error) {
str := string(parser.data[startPos:parser.pos])
parser.pos++
return str, fmt.Errorf(`invalid escape sequence in "%s" (position %d)`, str, n2-2-startPos)
}
for n2 < pos {
if data[n2] != '\\' {
buffer[n1] = data[n2]
for n2 < parser.pos {
if parser.data[n2] != '\\' {
buffer[n1] = parser.data[n2]
n2++
} else {
n2 += 2
switch data[n2-1] {
switch parser.data[n2-1] {
case 'n':
buffer[n1] = '\n'
@ -461,12 +484,12 @@ func ParseDataText(text string) DataObject {
buffer[n1] = '\\'
case 'x', 'X':
if n2+2 > pos {
if n2+2 > parser.pos {
return invalidEscape()
}
x := 0
for range 2 {
ch := data[n2]
ch := parser.data[n2]
if ch >= '0' && ch <= '9' {
x = x*16 + int(ch-'0')
} else if ch >= 'a' && ch <= 'f' {
@ -481,12 +504,12 @@ func ParseDataText(text string) DataObject {
buffer[n1] = rune(x)
case 'u', 'U':
if n2+4 > pos {
if n2+4 > parser.pos {
return invalidEscape()
}
x := 0
for range 4 {
ch := data[n2]
ch := parser.data[n2]
if ch >= '0' && ch <= '9' {
x = x*16 + int(ch-'0')
} else if ch >= 'a' && ch <= 'f' {
@ -501,97 +524,84 @@ func ParseDataText(text string) DataObject {
buffer[n1] = rune(x)
default:
str := string(data[startPos:pos])
ErrorLogF("Invalid escape sequence in \"%s\" (position %d)", str, n2-2-startPos)
return str, false
str := string(parser.data[startPos:parser.pos])
return str, fmt.Errorf(`invalid escape sequence in "%s" (position %d)`, str, n2-2-startPos)
}
}
n1++
}
pos++
skipSpaces(false)
return string(buffer[0:n1]), true
parser.pos++
parser.skipSpaces(false)
return string(buffer[0:n1]), nil
}
stopSymbol := func(symbol rune) bool {
if unicode.IsSpace(symbol) {
return true
}
for _, sym := range []rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'} {
if sym == symbol {
return true
}
}
return false
for parser.pos < parser.size && !parser.stopSymbol(parser.data[parser.pos]) {
parser.pos++
}
for pos < size && !stopSymbol(data[pos]) {
pos++
}
endPos := pos
skipSpaces(false)
endPos := parser.pos
parser.skipSpaces(false)
if startPos == endPos {
//ErrorLog("empty tag")
return "", true
return "", nil
}
return string(data[startPos:endPos]), true
return string(parser.data[startPos:endPos]), nil
}
var parseObject func(tag string) DataObject
var parseArray func() []DataValue
func (parser *dataParser) stopSymbol(symbol rune) bool {
return unicode.IsSpace(symbol) ||
slices.Contains([]rune{'=', '{', '}', '[', ']', ',', ' ', '\t', '\n', '\'', '"', '`', '/'}, symbol)
}
parseNode := func() DataNode {
func (parser *dataParser) parseNode() (DataNode, error) {
var tag string
var ok bool
var err error
if tag, ok = parseTag(); !ok {
return nil
if tag, err = parser.parseTag(); err != nil {
return nil, err
}
skipSpaces(true)
if data[pos] != '=' {
ErrorLogF("expected '=' after a tag name (line: %d, position: %d)", line, pos-lineStart)
return nil
parser.skipSpaces(true)
if parser.data[parser.pos] != '=' {
return nil, fmt.Errorf("expected '=' after a tag name (line: %d, position: %d)", parser.line, parser.pos-parser.lineStart)
}
pos++
skipSpaces(true)
switch data[pos] {
parser.pos++
parser.skipSpaces(true)
switch parser.data[parser.pos] {
case '[':
node := new(dataNode)
node.tag = tag
if node.array = parseArray(); node.array == nil {
return nil
if node.array, err = parser.parseArray(); err != nil {
return nil, err
}
return node
return node, nil
case '{':
node := new(dataNode)
node.tag = tag
if node.value = parseObject("_"); node.value == nil {
return nil
if node.value, err = parser.parseObject("_"); err != nil {
return nil, err
}
return node
return node, nil
case '}', ']', '=':
ErrorLogF("Expected '[', '{' or a tag name after '=' (line: %d, position: %d)", line, pos-lineStart)
return nil
return nil, fmt.Errorf(`expected '[', '{' or a tag name after '=' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
default:
var str string
if str, ok = parseTag(); !ok {
return nil
if str, err = parser.parseTag(); err != nil {
return nil, err
}
node := new(dataNode)
node.tag = tag
if data[pos] == '{' {
if node.value = parseObject(str); node.value == nil {
return nil
if parser.data[parser.pos] == '{' {
if node.value, err = parser.parseObject(str); err != nil {
return nil, err
}
} else {
val := new(dataStringValue)
@ -599,91 +609,88 @@ func ParseDataText(text string) DataObject {
node.value = val
}
return node
return node, nil
}
}
parseObject = func(tag string) DataObject {
if data[pos] != '{' {
ErrorLogF("Expected '{' (line: %d, position: %d)", line, pos-lineStart)
return nil
func (parser *dataParser) parseObject(tag string) (DataObject, error) {
if parser.data[parser.pos] != '{' {
return nil, fmt.Errorf(`expected '{' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
pos++
parser.pos++
obj := new(dataObject)
obj.tag = tag
obj.property = []DataNode{}
for pos < size {
var node DataNode
skipSpaces(true)
if data[pos] == '}' {
pos++
skipSpaces(false)
return obj
for parser.pos < parser.size {
parser.skipSpaces(true)
if parser.data[parser.pos] == '}' {
parser.pos++
parser.skipSpaces(false)
return obj, nil
}
if node = parseNode(); node == nil {
return nil
node, err := parser.parseNode()
if err != nil {
return nil, err
}
obj.property = append(obj.property, node)
if data[pos] == '}' {
pos++
skipSpaces(true)
return obj
} else if data[pos] != ',' && data[pos] != '\n' {
ErrorLogF(`Expected '}', '\n' or ',' (line: %d, position: %d)`, line, pos-lineStart)
return nil
if parser.data[parser.pos] == '}' {
parser.pos++
parser.skipSpaces(true)
return obj, nil
} else if parser.data[parser.pos] != ',' && parser.data[parser.pos] != '\n' {
return nil, fmt.Errorf(`expected '}', '\n' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
if data[pos] != '\n' {
pos++
if parser.data[parser.pos] != '\n' {
parser.pos++
}
skipSpaces(true)
for data[pos] == ',' {
pos++
skipSpaces(true)
parser.skipSpaces(true)
for parser.data[parser.pos] == ',' {
parser.pos++
parser.skipSpaces(true)
}
}
ErrorLog("Unexpected end of text")
return nil
return nil, errors.New("unexpected end of text")
}
parseArray = func() []DataValue {
pos++
skipSpaces(true)
func (parser *dataParser) parseArray() ([]DataValue, error) {
parser.pos++
parser.skipSpaces(true)
array := []DataValue{}
for pos < size {
var tag string
var ok bool
skipSpaces(true)
for data[pos] == ',' && pos < size {
pos++
skipSpaces(true)
for parser.pos < parser.size {
parser.skipSpaces(true)
for parser.data[parser.pos] == ',' && parser.pos < parser.size {
parser.pos++
parser.skipSpaces(true)
}
if pos >= size {
if parser.pos >= parser.size {
break
}
if data[pos] == ']' {
pos++
skipSpaces(true)
return array
if parser.data[parser.pos] == ']' {
parser.pos++
parser.skipSpaces(true)
return array, nil
}
if tag, ok = parseTag(); !ok {
return nil
tag, err := parser.parseTag()
if err != nil {
return nil, err
}
if data[pos] == '{' {
obj := parseObject(tag)
if obj == nil {
return nil
if parser.data[parser.pos] == '{' {
obj, err := parser.parseObject(tag)
if err != nil {
return nil, err
}
array = append(array, obj)
} else {
@ -692,12 +699,11 @@ func ParseDataText(text string) DataObject {
array = append(array, val)
}
switch data[pos] {
switch parser.data[parser.pos] {
case ']', ',', '\n':
default:
ErrorLogF("Expected ']' or ',' (line: %d, position: %d)", line, pos-lineStart)
return nil
return nil, fmt.Errorf(`expected ']' or ',' (line: %d, position: %d)`, parser.line, parser.pos-parser.lineStart)
}
/*
@ -713,12 +719,28 @@ func ParseDataText(text string) DataObject {
*/
}
ErrorLog("Unexpected end of text")
return nil
return nil, errors.New("unexpected end of text")
}
if tag, ok := parseTag(); ok {
return parseObject(tag)
// ParseDataText - parse text and return DataNode
func ParseDataText(text string) (DataObject, error) {
if strings.ContainsAny(text, "\r") {
text = strings.ReplaceAll(text, "\r\n", "\n")
text = strings.ReplaceAll(text, "\r", "\n")
}
return nil
parser := dataParser{
data: append([]rune(text), rune(0)),
pos: 0,
line: 1,
lineStart: 0,
}
parser.size = len(parser.data) - 1
tag, err := parser.parseTag()
if err != nil {
return nil, err
}
return parser.parseObject(tag)
}

View File

@ -312,7 +312,9 @@ func dataListHtmlProperties(view View, buffer *strings.Builder) {
}
// GetDataList returns the data list of an editor.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDataList(view View, subviewID ...string) []string {
if view = getSubview(view, subviewID); view != nil {
return getDataListProperty(view)

View File

@ -6,10 +6,6 @@ import (
func TestParseDataText(t *testing.T) {
SetErrorLog(func(text string) {
t.Error(text)
})
text := `obj1 {
key1 = val1,
key2=obj2{
@ -27,8 +23,10 @@ func TestParseDataText(t *testing.T) {
key3 = "\n \t \\ \r \" ' \X4F\x4e \U01Ea",` +
"key4=`" + `\n \t \\ \r \" ' \x8F \UF80a` + "`\r}"
obj := ParseDataText(text)
if obj != nil {
obj, err := ParseDataText(text)
if err != nil {
t.Error(err)
} else {
if obj.Tag() != "obj1" {
t.Error(`obj.Tag() != "obj1"`)
}
@ -75,7 +73,7 @@ func TestParseDataText(t *testing.T) {
t.Errorf(`obj.PropertyValue("key5") result: ("%s",%v)`, val, ok)
}
testKey := func(obj DataObject, index int, tag string, nodeType int) DataNode {
testKey := func(obj DataObject, index int, tag string, nodeType DataNodeType) DataNode {
key := obj.Property(index)
if key == nil {
t.Errorf(`%s.Property(%d) == nil`, obj.Tag(), index)
@ -118,7 +116,7 @@ func TestParseDataText(t *testing.T) {
type testKeyData struct {
tag string
nodeType int
nodeType DataNodeType
}
data := []testKeyData{
@ -173,9 +171,6 @@ func TestParseDataText(t *testing.T) {
}
}
SetErrorLog(func(text string) {
})
failText := []string{
" ",
"obj[]",
@ -204,7 +199,7 @@ func TestParseDataText(t *testing.T) {
}
for _, txt := range failText {
if obj := ParseDataText(txt); obj != nil {
if _, err := ParseDataText(txt); err == nil {
t.Errorf("result ParseDataText(\"%s\") must be fail", txt)
}
}

View File

@ -274,7 +274,7 @@ func (picker *datePickerData) propertyChanged(tag PropertyName) {
date := GetDatePickerValue(picker)
session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
if listeners := GetDateChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[DatePicker, time.Time](picker, nil, DateChangedEvent); len(listeners) > 0 {
oldDate := time.Now()
if value := picker.getRaw("old-date"); value != nil {
if date, ok := value.(time.Time); ok {
@ -282,7 +282,7 @@ func (picker *datePickerData) propertyChanged(tag PropertyName) {
}
}
for _, listener := range listeners {
listener(picker, date, oldDate)
listener.Run(picker, date, oldDate)
}
}
@ -348,11 +348,11 @@ func (picker *datePickerData) handleCommand(self View, command PropertyName, dat
oldValue := GetDatePickerValue(picker)
picker.properties[DatePickerValue] = value
if value != oldValue {
for _, listener := range GetDateChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[DatePicker, time.Time](picker, nil, DateChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[DatePickerValue]; ok {
listener(picker, DatePickerValue)
listener.Run(picker, DatePickerValue)
}
}
}
@ -400,7 +400,9 @@ func getDateProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool
// GetDatePickerMin returns the min date of DatePicker subview and "true" as the second value if the min date is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) {
if view = getSubview(view, subviewID); view != nil {
return getDateProperty(view, DatePickerMin, Min)
@ -410,7 +412,9 @@ func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) {
// GetDatePickerMax returns the max date of DatePicker subview and "true" as the second value if the min date is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) {
if view = getSubview(view, subviewID); view != nil {
return getDateProperty(view, DatePickerMax, Max)
@ -419,13 +423,17 @@ func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) {
}
// GetDatePickerStep returns the date changing step in days of DatePicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDatePickerStep(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, DatePickerStep, 0)
}
// GetDatePickerValue returns the date of DatePicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDatePickerValue(view View, subviewID ...string) time.Time {
if view = getSubview(view, subviewID); view == nil {
return time.Now()
@ -436,7 +444,18 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
// GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
return getTwoArgEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
//
// Result elements can be of the following types:
// - func(rui.DatePicker, time.Time, time.Time),
// - func(rui.DatePicker, time.Time),
// - func(rui.DatePicker),
// - func(time.Time, time.Time),
// - func(time.Time),
// - 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 GetDateChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
}

View File

@ -92,7 +92,7 @@ func (detailsView *detailsViewData) setFunc(tag PropertyName, value any) []Prope
value.setParentID(detailsView.htmlID())
case DataObject:
if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
if view := CreateViewFromObject(detailsView.Session(), value, nil); view != nil {
detailsView.setRaw(Summary, view)
view.setParentID(detailsView.htmlID())
} else {
@ -193,7 +193,7 @@ func (detailsView *detailsViewData) handleCommand(self View, command PropertyNam
if n, ok := dataIntProperty(data, "open"); ok {
detailsView.properties[Expanded] = (n != 0)
if listener, ok := detailsView.changeListener[Expanded]; ok {
listener(detailsView, Expanded)
listener.Run(detailsView, Expanded)
}
}
return true
@ -202,7 +202,9 @@ func (detailsView *detailsViewData) handleCommand(self View, command PropertyNam
}
// GetDetailsSummary returns a value of the Summary property of DetailsView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDetailsSummary(view View, subviewID ...string) View {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(Summary); value != nil {
@ -219,13 +221,17 @@ func GetDetailsSummary(view View, subviewID ...string) View {
}
// IsDetailsExpanded returns a value of the Expanded property of DetailsView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsDetailsExpanded(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Expanded, false)
}
// IsDetailsExpanded returns a value of the HideSummaryMarker property of DetailsView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsSummaryMarkerHidden(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, HideSummaryMarker, false)
}

View File

@ -49,7 +49,7 @@ const (
// Supported types: int, string.
//
// Values:
// - 0 (DropEffectUndefined) or "undefined" - The property value is not defined (defaut value).
// - 0 (DropEffectUndefined) or "undefined" - The property value is not defined (default value).
// - 1 (DropEffectCopy) or "copy" - A copy of the source item may be made at the new location.
// - 2 (DropEffectMove) or "move" - An item may be moved to a new location.
// - 4 (DropEffectLink) or "link" - A link may be established to the source at the new location.
@ -68,7 +68,7 @@ const (
// Supported types: int, string.
//
// Values:
// - 0 (DropEffectUndefined) or "undefined" - The property value is not defined (defaut value). Equivalent to DropEffectAll
// - 0 (DropEffectUndefined) or "undefined" - The property value is not defined (default value). Equivalent to DropEffectAll
// - 1 (DropEffectCopy) or "copy" - A copy of the source item may be made at the new location.
// - 2 (DropEffectMove) or "move" - An item may be moved to a new location.
// - 3 (DropEffectLink) or "link" - A link may be established to the source at the new location.
@ -84,7 +84,16 @@ const (
// Fired when the user starts dragging an element or text selection.
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DragStartEvent PropertyName = "drag-start-event"
// DragEndEvent is the constant for "drag-end-event" property tag.
@ -93,7 +102,16 @@ const (
// Fired when a drag operation ends (by releasing a mouse button or hitting the escape key).
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DragEndEvent PropertyName = "drag-end-event"
// DragEnterEvent is the constant for "drag-enter-event" property tag.
@ -102,7 +120,16 @@ const (
// Fired when a dragged element or text selection enters a valid drop target.
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DragEnterEvent PropertyName = "drag-enter-event"
// DragLeaveEvent is the constant for "drag-leave-event" property tag.
@ -111,7 +138,16 @@ const (
// Fired when a dragged element or text selection leaves a valid drop target.
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DragLeaveEvent PropertyName = "drag-leave-event"
// DragOverEvent is the constant for "drag-over-event" property tag.
@ -120,7 +156,16 @@ const (
// Fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DragOverEvent PropertyName = "drag-over-event"
// DropEvent is the constant for "drop-event" property tag.
@ -129,7 +174,16 @@ const (
// Fired when an element or text selection is dropped on a valid drop target.
//
// General listener format:
// func(view rui.View, event rui.DragAndDropEvent).
//
// where:
// - view - Interface of a view which generated this event,
// - event - event parameters.
//
// Allowed listener formats:
// func(view rui.View)
// func(rui.DragAndDropEvent)
// func()
DropEvent PropertyName = "drop-event"
// DropEffectUndefined - the value of the "drop-effect" and "drop-effect-allowed" properties: the value is not defined (default value).
@ -156,7 +210,7 @@ const (
// DropEffectLinkMove - the value of the "drop-effect-allowed" property: a link or move operation is permitted.
DropEffectLinkMove = DropEffectLink + DropEffectMove
// DropEffectAll - the value of the "drop-effect-allowed" property: all operations (copy, move, and link) are permitted (defaut value).
// DropEffectAll - the value of the "drop-effect-allowed" property: all operations (copy, move, and link) are permitted (default value).
DropEffectAll = DropEffectCopy + DropEffectMove + DropEffectLink
)
@ -336,7 +390,7 @@ func handleDragAndDropEvents(view View, tag PropertyName, data DataObject) {
event.init(view.Session(), data)
for _, listener := range listeners {
listener(view, event)
listener.Run(view, event)
}
}
}
@ -408,17 +462,17 @@ func dragAndDropHtml(view View, buffer *strings.Builder) {
if f := GetDragImageXOffset(view); f != 0 {
buffer.WriteString(` data-drag-image-x="`)
buffer.WriteString(fmt.Sprintf("%g", f))
fmt.Fprintf(buffer, "%g", f)
buffer.WriteString(`" `)
}
if f := GetDragImageYOffset(view); f != 0 {
buffer.WriteString(` data-drag-image-y="`)
buffer.WriteString(fmt.Sprintf("%g", f))
fmt.Fprintf(buffer, "%g", f)
buffer.WriteString(`" `)
}
effects := []string{"undifined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"}
effects := []string{"undefined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"}
if n := GetDropEffectAllowed(view); n > 0 && n < len(effects) {
buffer.WriteString(` data-drop-effect-allowed="`)
buffer.WriteString(effects[n])
@ -434,41 +488,99 @@ func (view *viewData) LoadFile(file FileInfo, result func(FileInfo, []byte)) {
}
// GetDragStartEventListeners returns the "drag-start-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDragStartEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DragStartEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDragStartEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DragStartEvent)
}
// GetDragEndEventListeners returns the "drag-end-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDragEndEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DragEndEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDragEndEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DragEndEvent)
}
// GetDragEnterEventListeners returns the "drag-enter-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDragEnterEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DragEnterEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDragEnterEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DragEnterEvent)
}
// GetDragLeaveEventListeners returns the "drag-leave-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDragLeaveEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DragLeaveEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDragLeaveEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DragLeaveEvent)
}
// GetDragOverEventListeners returns the "drag-over-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDragOverEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DragOverEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDragOverEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DragOverEvent)
}
// GetDropEventListeners returns the "drag-start-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDropEventListeners(view View, subviewID ...string) []func(View, DragAndDropEvent) {
return getOneArgEventListeners[View, DragAndDropEvent](view, subviewID, DropEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.DragAndDropEvent),
// - func(rui.View),
// - func(rui.DragAndDropEvent),
// - 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 GetDropEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, DragAndDropEvent](view, subviewID, DropEvent)
}
// GetDropEventListeners returns the "drag-data" data.
//
// 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 GetDragData(view View, subviewID ...string) map[string]string {
result := map[string]string{}
if view = getSubview(view, subviewID); view != nil {
@ -483,7 +595,9 @@ func GetDragData(view View, subviewID ...string) map[string]string {
}
// GetDragImage returns the drag feedback image.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDragImage(view View, subviewID ...string) string {
if view = getSubview(view, subviewID); view != nil {
value := view.getRaw(DragImage)
@ -508,13 +622,17 @@ func GetDragImage(view View, subviewID ...string) string {
}
// GetDragImageXOffset returns the horizontal offset in pixels within the drag feedback image.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDragImageXOffset(view View, subviewID ...string) float64 {
return floatStyledProperty(view, subviewID, DragImageXOffset, 0)
}
// GetDragImageYOffset returns the vertical offset in pixels within the drag feedback image.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDragImageYOffset(view View, subviewID ...string) float64 {
return floatStyledProperty(view, subviewID, DragImageYOffset, 0)
}
@ -529,7 +647,8 @@ func GetDragImageYOffset(view View, subviewID ...string) float64 {
// - 2 (DropEffectMove) - An item may be moved to a new location.
// - 4 (DropEffectLink) - A link may be established to the source at the new location.
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDropEffect(view View, subviewID ...string) int {
if view = getSubview(view, subviewID); view != nil {
value := view.getRaw(DropEffect)
@ -573,7 +692,8 @@ func GetDropEffect(view View, subviewID ...string) int {
// - 6 (DropEffectLinkMove) - A link or move operation is permitted.
// - 7 (DropEffectAll) - All operations are permitted.
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDropEffectAllowed(view View, subviewID ...string) int {
if view = getSubview(view, subviewID); view != nil {
value := view.getRaw(DropEffectAllowed)

View File

@ -104,8 +104,8 @@ func (list *dropDownListData) propertyChanged(tag PropertyName) {
list.Session().callFunc("selectDropDownListItem", list.htmlID(), current)
oldCurrent, _ := intProperty(list, "old-current", list.Session(), -1)
for _, listener := range GetDropDownListeners(list) {
listener(list, current, oldCurrent)
for _, listener := range getTwoArgEventListeners[DropDownList, int](list, nil, DropDownEvent) {
listener.Run(list, current, oldCurrent)
}
default:
@ -245,11 +245,11 @@ func (list *dropDownListData) handleCommand(self View, command PropertyName, dat
if GetCurrent(list) != number && number >= 0 && number < len(items) {
old := GetCurrent(list)
list.properties[Current] = number
for _, listener := range GetDropDownListeners(list) {
listener(list, number, old)
for _, listener := range getTwoArgEventListeners[DropDownList, int](list, nil, DropDownEvent) {
listener.Run(list, number, old)
}
if listener, ok := list.changeListener[Current]; ok {
listener(list, Current)
listener.Run(list, Current)
}
}
} else {
@ -264,13 +264,26 @@ func (list *dropDownListData) handleCommand(self View, command PropertyName, dat
}
// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
return getTwoArgEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
//
// Result elements can be of the following types:
// - func(rui.DropDownList, int, int),
// - func(rui.DropDownList, int),
// - func(rui.DropDownList),
// - func(int, int),
// - func(int),
// - 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 GetDropDownListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[DropDownList, int](view, subviewID, DropDownEvent)
}
// GetDropDownItems return the DropDownList items list.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDropDownItems(view View, subviewID ...string) []string {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(Items); value != nil {
@ -313,14 +326,18 @@ func getIndicesArray(view View, tag PropertyName) []int {
}
// GetDropDownDisabledItems return an array of disabled(non selectable) items indices of DropDownList.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDropDownDisabledItems(view View, subviewID ...string) []int {
view = getSubview(view, subviewID)
return getIndicesArray(view, DisabledItems)
}
// GetDropDownItemSeparators return an array of indices of DropDownList items after which a separator should be added.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetDropDownItemSeparators(view View, subviewID ...string) []int {
view = getSubview(view, subviewID)
return getIndicesArray(view, ItemSeparators)

View File

@ -268,11 +268,11 @@ func (edit *editViewData) AppendText(text string) {
}
func (edit *editViewData) textChanged(newText, oldText string) {
for _, listener := range GetTextChangedListeners(edit) {
listener(edit, newText, oldText)
for _, listener := range getTwoArgEventListeners[EditView, string](edit, nil, EditTextChangedEvent) {
listener.Run(edit, newText, oldText)
}
if listener, ok := edit.changeListener[Text]; ok {
listener(edit, Text)
listener.Run(edit, Text)
}
}
@ -451,26 +451,43 @@ func IsReadOnly(view View, subviewID ...string) bool {
}
// IsSpellcheck returns a value of the Spellcheck property of EditView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsSpellcheck(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Spellcheck, false)
}
// GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
return getTwoArgEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
//
// Result elements can be of the following types:
// - func(rui.EditView, string, string),
// - func(rui.EditView, string),
// - func(rui.EditView),
// - func(string, string),
// - func(string),
// - 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 GetTextChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[EditView, string](view, subviewID, EditTextChangedEvent)
}
// GetEditViewType returns a value of the Type property of EditView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetEditViewType(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, EditViewType, SingleLineText, false)
}
// GetEditViewPattern returns a value of the Pattern property of EditView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetEditViewPattern(view View, subviewID ...string) string {
if view = getSubview(view, subviewID); view != nil {
if pattern, ok := stringProperty(view, EditViewPattern, view.Session()); ok {
@ -488,13 +505,17 @@ func GetEditViewPattern(view View, subviewID ...string) string {
}
// IsEditViewWrap returns a value of the EditWrap property of MultiLineEditView.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsEditViewWrap(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, EditWrap, false)
}
// AppendEditText appends the text to the EditView content.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func AppendEditText(view View, subviewID string, text string) {
if subviewID != "" {
if edit := EditViewByID(view, subviewID); edit != nil {
@ -509,7 +530,9 @@ func AppendEditText(view View, subviewID string, text string) {
}
// GetCaretColor returns the color of the text input caret.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCaretColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, CaretColor, false)
}

656
events.go
View File

@ -1,6 +1,9 @@
package rui
import "strings"
import (
"reflect"
"strings"
)
var eventJsFunc = map[PropertyName]struct{ jsEvent, jsFunc string }{
FocusEvent: {jsEvent: "onfocus", jsFunc: "focusEvent"},
@ -38,464 +41,10 @@ var eventJsFunc = map[PropertyName]struct{ jsEvent, jsFunc string }{
DragLeaveEvent: {jsEvent: "ondragleave", jsFunc: "dragLeaveEvent"},
}
func valueToNoArgEventListeners[V any](value any) ([]func(V), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V):
return []func(V){value}, true
case func():
fn := func(V) {
value()
}
return []func(V){fn}, true
case []func(V):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V):
listeners[i] = v
case func():
listeners[i] = func(V) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func valueToOneArgEventListeners[V View, E any](value any) ([]func(V, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E):
return []func(V, E){value}, true
case func(E):
fn := func(_ V, event E) {
value(event)
}
return []func(V, E){fn}, true
case func(V):
fn := func(view V, _ E) {
value(view)
}
return []func(V, E){fn}, true
case func():
fn := func(V, E) {
value()
}
return []func(V, E){fn}, true
case []func(V, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(_ V, event E) {
v(event)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(view V, _ E) {
v(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
listeners[i] = func(V, E) {
v()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch v := v.(type) {
case func(V, E):
listeners[i] = v
case func(E):
listeners[i] = func(_ V, event E) {
v(event)
}
case func(V):
listeners[i] = func(view V, _ E) {
v(view)
}
case func():
listeners[i] = func(V, E) {
v()
}
default:
return nil, false
}
}
return listeners, true
}
return nil, false
}
func valueToTwoArgEventListeners[V View, E any](value any) ([]func(V, E, E), bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case func(V, E, E):
return []func(V, E, E){value}, true
case func(V, E):
fn := func(v V, val, _ E) {
value(v, val)
}
return []func(V, E, E){fn}, true
case func(E, E):
fn := func(_ V, val, old E) {
value(val, old)
}
return []func(V, E, E){fn}, true
case func(E):
fn := func(_ V, val, _ E) {
value(val)
}
return []func(V, E, E){fn}, true
case func(V):
fn := func(v V, _, _ E) {
value(v)
}
return []func(V, E, E){fn}, true
case func():
fn := func(V, E, E) {
value()
}
return []func(V, E, E){fn}, true
case []func(V, E, E):
if len(value) == 0 {
return nil, true
}
for _, fn := range value {
if fn == nil {
return nil, false
}
}
return value, true
case []func(V, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
}
return listeners, true
case []func(E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
}
return listeners, true
case []func(E, E):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
}
return listeners, true
case []func(V):
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(view V, _, _ E) {
fn(view)
}
}
return listeners, true
case []func():
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, fn := range value {
if fn == nil {
return nil, false
}
listeners[i] = func(V, E, E) {
fn()
}
}
return listeners, true
case []any:
count := len(value)
if count == 0 {
return nil, true
}
listeners := make([]func(V, E, E), count)
for i, v := range value {
if v == nil {
return nil, false
}
switch fn := v.(type) {
case func(V, E, E):
listeners[i] = fn
case func(V, E):
listeners[i] = func(view V, val, _ E) {
fn(view, val)
}
case func(E, E):
listeners[i] = func(_ V, val, old E) {
fn(val, old)
}
case func(E):
listeners[i] = func(_ V, val, _ E) {
fn(val)
}
case func(V):
listeners[i] = func(view V, _, _ E) {
fn(view)
}
case func():
listeners[i] = func(V, E, E) {
fn()
}
default:
return nil, false
}
}
return listeners, true
}
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
}
}
}
return []func(V){}
}
func getOneArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []func(V, E) {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E)); ok {
return result
}
}
}
return []func(V, E){}
}
func getTwoArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []func(V, E, E) {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]func(V, E, E)); ok {
return result
}
}
}
return []func(V, E, E){}
}
func setNoArgEventListener[V View](properties Properties, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToNoArgEventListeners[V](value); ok {
if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
func setOneArgEventListener[V View, T any](properties Properties, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToOneArgEventListeners[V, T](value); ok {
if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
notCompatibleType(tag, value)
return nil
}
func setTwoArgEventListener[V View, T any](properties Properties, tag PropertyName, value any) []PropertyName {
listeners, ok := valueToTwoArgEventListeners[V, T](value)
if !ok {
notCompatibleType(tag, value)
return nil
} else if len(listeners) > 0 {
properties.setRaw(tag, listeners)
} else if properties.getRaw(tag) != nil {
properties.setRaw(tag, nil)
} else {
return []PropertyName{}
}
return []PropertyName{tag}
}
func viewEventsHtml[T any](view View, events []PropertyName, buffer *strings.Builder) {
for _, tag := range events {
if value := view.getRaw(tag); value != nil {
if js, ok := eventJsFunc[tag]; ok {
if listeners, ok := value.([]func(View, T)); ok && len(listeners) > 0 {
if value := getOneArgEventListeners[View, T](view, nil, tag); len(value) > 0 {
buffer.WriteString(js.jsEvent)
buffer.WriteString(`="`)
buffer.WriteString(js.jsFunc)
@ -504,7 +53,6 @@ func viewEventsHtml[T any](view View, events []PropertyName, buffer *strings.Bui
}
}
}
}
func updateEventListenerHtml(view View, tag PropertyName) {
if js, ok := eventJsFunc[tag]; ok {
@ -518,3 +66,197 @@ 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:
if equalType(methodType.In(0), 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 equalType(inType reflect.Type, argType reflect.Type) bool {
return inType == argType || (inType.Kind() == reflect.Interface && argType.Implements(inType))
}
func (data *noArgListenerBinding[V]) rawListener() any {
return data.name
}
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 nil, false
}
func setNoArgEventListener[V View](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToNoArgEventListeners[V](value); ok {
return setArrayPropertyValue(view, tag, listeners)
}
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
}
func getNoArgBinding[V View](listeners []noArgListener[V]) string {
for _, listener := range listeners {
raw := listener.rawListener()
if text, ok := raw.(string); ok && text != "" {
return text
}
}
return ""
}

271
events1arg.go Normal file
View File

@ -0,0 +1,271 @@
package rui
import (
"reflect"
)
type oneArgListener[V View, E any] interface {
Run(V, E)
rawListener() any
}
type oneArgListener0[V View, E any] struct {
fn func()
}
type oneArgListenerV[V View, E any] struct {
fn func(V)
}
type oneArgListenerE[V View, E any] struct {
fn func(E)
}
type oneArgListenerVE[V View, E any] struct {
fn func(V, E)
}
type oneArgListenerBinding[V View, E any] struct {
name string
}
func newOneArgListener0[V View, E any](fn func()) oneArgListener[V, E] {
obj := new(oneArgListener0[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListener0[V, E]) Run(_ V, _ E) {
data.fn()
}
func (data *oneArgListener0[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerV[V View, E any](fn func(V)) oneArgListener[V, E] {
obj := new(oneArgListenerV[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerV[V, E]) Run(view V, _ E) {
data.fn(view)
}
func (data *oneArgListenerV[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerE[V View, E any](fn func(E)) oneArgListener[V, E] {
obj := new(oneArgListenerE[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerE[V, E]) Run(_ V, event E) {
data.fn(event)
}
func (data *oneArgListenerE[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerVE[V View, E any](fn func(V, E)) oneArgListener[V, E] {
obj := new(oneArgListenerVE[V, E])
obj.fn = fn
return obj
}
func (data *oneArgListenerVE[V, E]) Run(view V, arg E) {
data.fn(view, arg)
}
func (data *oneArgListenerVE[V, E]) rawListener() any {
return data.fn
}
func newOneArgListenerBinding[V View, E any](name string) oneArgListener[V, E] {
obj := new(oneArgListenerBinding[V, E])
obj.name = name
return obj
}
func (data *oneArgListenerBinding[V, E]) Run(view V, event E) {
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 equalType(inType, reflect.TypeOf(event)) {
args = []reflect.Value{reflect.ValueOf(event)}
} else if equalType(inType, reflect.TypeOf(view)) {
args = []reflect.Value{reflect.ValueOf(view)}
}
case 2:
if equalType(methodType.In(0), reflect.TypeOf(view)) &&
equalType(methodType.In(1), reflect.TypeOf(event)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(event)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *oneArgListenerBinding[V, E]) rawListener() any {
return data.name
}
func valueToOneArgEventListeners[V View, E any](value any) ([]oneArgListener[V, E], bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case []oneArgListener[V, E]:
return value, true
case oneArgListener[V, E]:
return []oneArgListener[V, E]{value}, true
case string:
return []oneArgListener[V, E]{newOneArgListenerBinding[V, E](value)}, true
case func(V, E):
return []oneArgListener[V, E]{newOneArgListenerVE(value)}, true
case func(V):
return []oneArgListener[V, E]{newOneArgListenerV[V, E](value)}, true
case func(E):
return []oneArgListener[V, E]{newOneArgListenerE[V](value)}, true
case func():
return []oneArgListener[V, E]{newOneArgListener0[V, E](value)}, true
case []func(V, E):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerVE(fn))
}
}
return result, len(result) > 0
case []func(E):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerE[V](fn))
}
}
return result, len(result) > 0
case []func(V):
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListenerV[V, E](fn))
}
}
return result, len(result) > 0
case []func():
result := make([]oneArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newOneArgListener0[V, E](fn))
}
}
return result, len(result) > 0
case []any:
result := make([]oneArgListener[V, E], 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(V, E):
result = append(result, newOneArgListenerVE(v))
case func(E):
result = append(result, newOneArgListenerE[V](v))
case func(V):
result = append(result, newOneArgListenerV[V, E](v))
case func():
result = append(result, newOneArgListener0[V, E](v))
case string:
result = append(result, newOneArgListenerBinding[V, E](v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}
func setOneArgEventListener[V View, T any](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToOneArgEventListeners[V, T](value); ok {
return setArrayPropertyValue(view, tag, listeners)
}
notCompatibleType(tag, value)
return nil
}
func getOneArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []oneArgListener[V, E] {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]oneArgListener[V, E]); ok {
return result
}
}
}
return []oneArgListener[V, E]{}
}
func getOneArgEventRawListeners[V View, E any](view View, subviewID []string, tag PropertyName) []any {
listeners := getOneArgEventListeners[V, E](view, subviewID, tag)
result := make([]any, len(listeners))
for i, l := range listeners {
result[i] = l.rawListener()
}
return result
}
func getOneArgBinding[V View, E any](listeners []oneArgListener[V, E]) string {
for _, listener := range listeners {
raw := listener.rawListener()
if text, ok := raw.(string); ok && text != "" {
return text
}
}
return ""
}

345
events2arg.go Normal file
View File

@ -0,0 +1,345 @@
package rui
import "reflect"
type twoArgListener[V View, E any] interface {
Run(V, E, E)
rawListener() any
}
type twoArgListener0[V View, E any] struct {
fn func()
}
type twoArgListenerV[V View, E any] struct {
fn func(V)
}
type twoArgListenerE[V View, E any] struct {
fn func(E)
}
type twoArgListenerVE[V View, E any] struct {
fn func(V, E)
}
type twoArgListenerEE[V View, E any] struct {
fn func(E, E)
}
type twoArgListenerVEE[V View, E any] struct {
fn func(V, E, E)
}
type twoArgListenerBinding[V View, E any] struct {
name string
}
func newTwoArgListener0[V View, E any](fn func()) twoArgListener[V, E] {
obj := new(twoArgListener0[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListener0[V, E]) Run(_ V, _ E, _ E) {
data.fn()
}
func (data *twoArgListener0[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerV[V View, E any](fn func(V)) twoArgListener[V, E] {
obj := new(twoArgListenerV[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerV[V, E]) Run(view V, _ E, _ E) {
data.fn(view)
}
func (data *twoArgListenerV[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerE[V View, E any](fn func(E)) twoArgListener[V, E] {
obj := new(twoArgListenerE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerE[V, E]) Run(_ V, arg E, _ E) {
data.fn(arg)
}
func (data *twoArgListenerE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerVE[V View, E any](fn func(V, E)) twoArgListener[V, E] {
obj := new(twoArgListenerVE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerVE[V, E]) Run(view V, arg E, _ E) {
data.fn(view, arg)
}
func (data *twoArgListenerVE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerEE[V View, E any](fn func(E, E)) twoArgListener[V, E] {
obj := new(twoArgListenerEE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerEE[V, E]) Run(_ V, arg1 E, arg2 E) {
data.fn(arg1, arg2)
}
func (data *twoArgListenerEE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerVEE[V View, E any](fn func(V, E, E)) twoArgListener[V, E] {
obj := new(twoArgListenerVEE[V, E])
obj.fn = fn
return obj
}
func (data *twoArgListenerVEE[V, E]) Run(view V, arg1 E, arg2 E) {
data.fn(view, arg1, arg2)
}
func (data *twoArgListenerVEE[V, E]) rawListener() any {
return data.fn
}
func newTwoArgListenerBinding[V View, E any](name string) twoArgListener[V, E] {
obj := new(twoArgListenerBinding[V, E])
obj.name = name
return obj
}
func (data *twoArgListenerBinding[V, E]) Run(view V, arg1 E, arg2 E) {
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 equalType(inType, reflect.TypeOf(arg1)) {
args = []reflect.Value{reflect.ValueOf(arg1)}
} else if equalType(inType, reflect.TypeOf(view)) {
args = []reflect.Value{reflect.ValueOf(view)}
}
case 2:
inType0 := methodType.In(0)
inType1 := methodType.In(1)
if equalType(inType0, reflect.TypeOf(view)) && equalType(inType1, reflect.TypeOf(arg1)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1)}
} else if equalType(inType0, reflect.TypeOf(arg1)) && equalType(inType1, reflect.TypeOf(arg2)) {
args = []reflect.Value{reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
}
case 3:
if equalType(methodType.In(0), reflect.TypeOf(view)) &&
equalType(methodType.In(1), reflect.TypeOf(arg1)) &&
equalType(methodType.In(2), reflect.TypeOf(arg2)) {
args = []reflect.Value{reflect.ValueOf(view), reflect.ValueOf(arg1), reflect.ValueOf(arg2)}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *twoArgListenerBinding[V, E]) rawListener() any {
return data.name
}
func valueToTwoArgEventListeners[V View, E any](value any) ([]twoArgListener[V, E], bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case []twoArgListener[V, E]:
return value, true
case twoArgListener[V, E]:
return []twoArgListener[V, E]{value}, true
case string:
return []twoArgListener[V, E]{newTwoArgListenerBinding[V, E](value)}, true
case func(V, E):
return []twoArgListener[V, E]{newTwoArgListenerVE(value)}, true
case func(V):
return []twoArgListener[V, E]{newTwoArgListenerV[V, E](value)}, true
case func(E):
return []twoArgListener[V, E]{newTwoArgListenerE[V](value)}, true
case func():
return []twoArgListener[V, E]{newTwoArgListener0[V, E](value)}, true
case func(E, E):
return []twoArgListener[V, E]{newTwoArgListenerEE[V](value)}, true
case func(V, E, E):
return []twoArgListener[V, E]{newTwoArgListenerVEE(value)}, true
case []func(V, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerVE(fn))
}
}
return result, len(result) > 0
case []func(E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerE[V](fn))
}
}
return result, len(result) > 0
case []func(V):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerV[V, E](fn))
}
}
return result, len(result) > 0
case []func():
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListener0[V, E](fn))
}
}
return result, len(result) > 0
case []func(E, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerEE[V](fn))
}
}
return result, len(result) > 0
case []func(V, E, E):
result := make([]twoArgListener[V, E], 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newTwoArgListenerVEE(fn))
}
}
return result, len(result) > 0
case []any:
result := make([]twoArgListener[V, E], 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func(V, E):
result = append(result, newTwoArgListenerVE(v))
case func(E):
result = append(result, newTwoArgListenerE[V](v))
case func(V):
result = append(result, newTwoArgListenerV[V, E](v))
case func():
result = append(result, newTwoArgListener0[V, E](v))
case func(E, E):
result = append(result, newTwoArgListenerEE[V](v))
case func(V, E, E):
result = append(result, newTwoArgListenerVEE(v))
case string:
result = append(result, newTwoArgListenerBinding[V, E](v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}
func setTwoArgEventListener[V View, T any](view View, tag PropertyName, value any) []PropertyName {
if listeners, ok := valueToTwoArgEventListeners[V, T](value); ok {
return setArrayPropertyValue(view, tag, listeners)
}
notCompatibleType(tag, value)
return nil
}
func getTwoArgEventListeners[V View, E any](view View, subviewID []string, tag PropertyName) []twoArgListener[V, E] {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(tag); value != nil {
if result, ok := value.([]twoArgListener[V, E]); ok {
return result
}
}
}
return []twoArgListener[V, E]{}
}
func getTwoArgEventRawListeners[V View, E any](view View, subviewID []string, tag PropertyName) []any {
listeners := getTwoArgEventListeners[V, E](view, subviewID, tag)
result := make([]any, len(listeners))
for i, l := range listeners {
result[i] = l.rawListener()
}
return result
}
func getTwoArgBinding[V View, E any](listeners []twoArgListener[V, E]) string {
for _, listener := range listeners {
raw := listener.rawListener()
if text, ok := raw.(string); ok && text != "" {
return text
}
}
return ""
}

View File

@ -147,7 +147,7 @@ func (picker *filePickerData) Files() []FileInfo {
func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []byte)) {
if result != nil {
for i, info := range picker.files {
if info.Name == file.Name && info.Size == file.Size && info.LastModified == file.LastModified {
if info.Name == file.Name && info.Size == file.Size && info.LastModified.Equal(file.LastModified) {
if info.data != nil {
result(info, info.data)
} else {
@ -282,8 +282,8 @@ func (picker *filePickerData) handleCommand(self View, command PropertyName, dat
case "fileSelected":
if files := parseFilesTag(data); files != nil {
picker.files = files
for _, listener := range GetFileSelectedListeners(picker) {
listener(picker, files)
for _, listener := range getOneArgEventListeners[FilePicker, []FileInfo](picker, nil, FileSelectedEvent) {
listener.Run(picker, files)
}
}
return true
@ -317,13 +317,17 @@ func LoadFilePickerFile(view View, subviewID string, file FileInfo, result func(
}
// IsMultipleFilePicker returns "true" if multiple files can be selected in the FilePicker, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsMultipleFilePicker(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Multiple, false)
}
// GetFilePickerAccept returns sets the list of allowed file extensions or MIME types.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetFilePickerAccept(view View, subviewID ...string) []string {
if view = getSubview(view, subviewID); view != nil {
accept, ok := stringProperty(view, Accept, view.Session())
@ -345,7 +349,16 @@ func GetFilePickerAccept(view View, subviewID ...string) []string {
// GetFileSelectedListeners returns the "file-selected-event" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetFileSelectedListeners(view View, subviewID ...string) []func(FilePicker, []FileInfo) {
return getOneArgEventListeners[FilePicker, []FileInfo](view, subviewID, FileSelectedEvent)
//
// Result elements can be of the following types:
// - func(rui.View, []rui.FileInfo),
// - func(rui.View),
// - func([]rui.FileInfo),
// - 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 GetFileSelectedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[FilePicker, []FileInfo](view, subviewID, FileSelectedEvent)
}

View File

@ -49,13 +49,27 @@ func focusEventsHtml(view View, buffer *strings.Builder) {
}
// GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetFocusListeners(view View, subviewID ...string) []func(View) {
return getNoArgEventListeners[View](view, subviewID, FocusEvent)
//
// 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) []any {
return getNoArgEventRawListeners[View](view, subviewID, FocusEvent)
}
// GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetLostFocusListeners(view View, subviewID ...string) []func(View) {
return getNoArgEventListeners[View](view, subviewID, LostFocusEvent)
//
// 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) []any {
return getNoArgEventRawListeners[View](view, subviewID, LostFocusEvent)
}

View File

@ -458,10 +458,7 @@ func (gridLayout *gridLayoutData) UpdateGridContent() {
if gridLayout.created {
updateInnerHTML(gridLayout.htmlID(), gridLayout.session)
}
if listener, ok := gridLayout.changeListener[Content]; ok {
listener(gridLayout, Content)
}
gridLayout.contentChanged()
}
}
@ -506,26 +503,34 @@ func gridCellSizes(properties Properties, tag PropertyName, session Session) []S
}
// GetCellVerticalAlign returns the vertical align of a GridLayout cell content: TopAlign (0), BottomAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCellVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CellVerticalAlign, StretchAlign, false)
}
// GetCellHorizontalAlign returns the vertical align of a GridLayout cell content: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCellHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CellHorizontalAlign, StretchAlign, false)
}
// GetGridAutoFlow returns the value of the "grid-auto-flow" property
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetGridAutoFlow(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, GridAutoFlow, 0, false)
}
// GetCellWidth returns the width of a GridLayout cell. If the result is an empty array, then the width is not set.
// If the result is a single value array, then the width of all cell is equal.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCellWidth(view View, subviewID ...string) []SizeUnit {
if view = getSubview(view, subviewID); view != nil {
return gridCellSizes(view, CellWidth, view.Session())
@ -535,7 +540,9 @@ func GetCellWidth(view View, subviewID ...string) []SizeUnit {
// GetCellHeight returns the height of a GridLayout cell. If the result is an empty array, then the height is not set.
// If the result is a single value array, then the height of all cell is equal.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCellHeight(view View, subviewID ...string) []SizeUnit {
if view = getSubview(view, subviewID); view != nil {
return gridCellSizes(view, CellHeight, view.Session())
@ -544,13 +551,17 @@ func GetCellHeight(view View, subviewID ...string) []SizeUnit {
}
// GetGridRowGap returns the gap between GridLayout rows.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetGridRowGap(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, GridRowGap, false)
}
// GetGridColumnGap returns the gap between GridLayout columns.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetGridColumnGap(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, GridColumnGap, false)
}

View File

@ -4,34 +4,44 @@ import (
"strconv"
)
// ImageLoadingStatus defines type of status of the image loading
type ImageLoadingStatus int
// Constants which represent return values of the LoadingStatus function of an [Image] view
const (
// ImageLoading is the image loading status: in the process of loading
ImageLoading = 0
ImageLoading ImageLoadingStatus = 0
// ImageReady is the image loading status: the image is loaded successfully
ImageReady = 1
ImageReady ImageLoadingStatus = 1
// ImageLoadingError is the image loading status: an error occurred while loading
ImageLoadingError = 2
ImageLoadingError ImageLoadingStatus = 2
)
// Image defines the image that is used for drawing operations on the Canvas.
type Image interface {
// URL returns the url of the image
URL() string
// LoadingStatus returns the status of the image loading: ImageLoading (0), ImageReady (1), ImageLoadingError (2)
LoadingStatus() int
// LoadingStatus returns the status of the image loading:
// - ImageLoading (0) - in the process of loading;
// - ImageReady (1) - the image is loaded successfully;
// - ImageLoadingError (2) - an error occurred while loading.
LoadingStatus() ImageLoadingStatus
// LoadingError: if LoadingStatus() == ImageLoadingError then returns the error text, "" otherwise
LoadingError() string
setLoadingError(err string)
// Width returns the width of the image in pixels. While LoadingStatus() != ImageReady returns 0
Width() float64
// Height returns the height of the image in pixels. While LoadingStatus() != ImageReady returns 0
Height() float64
}
type imageData struct {
url string
loadingStatus int
loadingStatus ImageLoadingStatus
loadingError string
width, height float64
listener func(Image)
@ -45,7 +55,7 @@ func (image *imageData) URL() string {
return image.url
}
func (image *imageData) LoadingStatus() int {
func (image *imageData) LoadingStatus() ImageLoadingStatus {
return image.loadingStatus
}

View File

@ -205,7 +205,7 @@ func imageViewSrcSet(view View, path string) string {
buffer.WriteString(", ")
}
buffer.WriteString(src.path)
buffer.WriteString(fmt.Sprintf(" %gx", src.scale))
fmt.Fprintf(buffer, " %gx", src.scale)
}
return buffer.String()
}
@ -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)
}

View File

@ -434,15 +434,13 @@ func (event *KeyEvent) init(data DataObject) {
}
func keyEventsHtml(view View, buffer *strings.Builder) {
if len(getOneArgEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
if len(getOneArgEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 ||
(view.Focusable() && len(getOneArgEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0) {
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
} else if view.Focusable() {
if len(getOneArgEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
}
}
if listeners := getOneArgEventListeners[View, KeyEvent](view, nil, KeyUpEvent); len(listeners) > 0 {
if len(getOneArgEventListeners[View, KeyEvent](view, nil, KeyUpEvent)) > 0 {
buffer.WriteString(`onkeyup="keyUpEvent(this, event)" `)
}
}
@ -454,7 +452,7 @@ func handleKeyEvents(view View, tag PropertyName, data DataObject) {
if len(listeners) > 0 {
for _, listener := range listeners {
listener(view, event)
listener.Run(view, event)
}
return
}
@ -477,20 +475,38 @@ func handleKeyEvents(view View, tag PropertyName, data DataObject) {
ScreenY: view.Frame().Top + view.Frame().Height/2,
}
for _, listener := range listeners {
listener(view, clickEvent)
listener.Run(view, clickEvent)
}
}
}
}
// GetKeyDownListeners returns the "key-down-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetKeyDownListeners(view View, subviewID ...string) []func(View, KeyEvent) {
return getOneArgEventListeners[View, KeyEvent](view, subviewID, KeyDownEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.KeyEvent),
// - func(rui.View),
// - func(rui.KeyEvent),
// - 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 GetKeyDownListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, KeyEvent](view, subviewID, KeyDownEvent)
}
// GetKeyUpListeners returns the "key-up-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetKeyUpListeners(view View, subviewID ...string) []func(View, KeyEvent) {
return getOneArgEventListeners[View, KeyEvent](view, subviewID, KeyUpEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.KeyEvent),
// - func(rui.View),
// - func(rui.KeyEvent),
// - 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 GetKeyUpListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, KeyEvent](view, subviewID, KeyUpEvent)
}

View File

@ -196,30 +196,33 @@ func (listLayout *listLayoutData) UpdateContent() {
if listLayout.created {
updateInnerHTML(listLayout.htmlID(), listLayout.session)
}
if listener, ok := listLayout.changeListener[Content]; ok {
listener(listLayout, Content)
}
listLayout.contentChanged()
}
}
// GetListVerticalAlign returns the vertical align of a ListLayout or ListView sibview:
// GetListVerticalAlign returns the vertical align of a ListLayout or ListView subview:
// TopAlign (0), BottomAlign (1), CenterAlign (2), or StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, VerticalAlign, TopAlign, false)
}
// GetListHorizontalAlign returns the vertical align of a ListLayout or ListView subview:
// LeftAlign (0), RightAlign (1), CenterAlign (2), or StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, HorizontalAlign, LeftAlign, false)
}
// GetListOrientation returns the orientation of a ListLayout or ListView subview:
// TopDownOrientation (0), StartToEndOrientation (1), BottomUpOrientation (2), or EndToStartOrientation (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListOrientation(view View, subviewID ...string) int {
if view = getSubview(view, subviewID); view != nil {
if orientation, ok := valueToOrientation(view.Get(Orientation), view.Session()); ok {
@ -238,19 +241,25 @@ func GetListOrientation(view View, subviewID ...string) int {
// GetListWrap returns the wrap type of a ListLayout or ListView subview:
// ListWrapOff (0), ListWrapOn (1), or ListWrapReverse (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListWrap(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ListWrap, ListWrapOff, false)
}
// GetListRowGap returns the gap between ListLayout or ListView rows.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListRowGap(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ListRowGap, false)
}
// GetListColumnGap returns the gap between ListLayout or ListView columns.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListColumnGap(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ListColumnGap, false)
}

View File

@ -122,19 +122,13 @@ type ListView interface {
// ReloadListViewData updates ListView content
ReloadListViewData()
//getCheckedItems() []int
getItemFrames() []Frame
}
type listViewData struct {
viewData
//adapter ListAdapter
//clickedListeners []func(ListView, int)
//selectedListeners []func(ListView, int)
//checkedListeners []func(ListView, []int)
items []View
itemFrame []Frame
//checkedItem []int
}
// NewListView creates the new list view
@ -279,7 +273,7 @@ func (listView *listViewData) propertyChanged(tag PropertyName) {
if listeners := getOneArgEventListeners[ListView, int](listView, nil, ListItemSelectedEvent); len(listeners) > 0 {
current := GetCurrent(listView)
for _, listener := range listeners {
listener(listView, current)
listener.Run(listView, current)
}
}
@ -288,7 +282,7 @@ func (listView *listViewData) propertyChanged(tag PropertyName) {
if listeners := getOneArgEventListeners[ListView, []int](listView, nil, ListItemCheckedEvent); len(listeners) > 0 {
checked := GetListViewCheckedItems(listView)
for _, listener := range listeners {
listener(listView, checked)
listener.Run(listView, checked)
}
}
@ -342,7 +336,7 @@ func (listView *listViewData) setItems(value any) []PropertyName {
items := make([]View, len(value))
for i, val := range value {
if val.IsObject() {
if view := CreateViewFromObject(session, val.Object()); view != nil {
if view := CreateViewFromObject(session, val.Object(), nil); view != nil {
items[i] = view
} else {
return nil
@ -540,7 +534,7 @@ func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign i
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
}
func (listView *listViewData) checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign int) string {
func (listView *listViewData) checkboxItemDiv(hCheckboxAlign, vCheckboxAlign int) string {
itemStyleBuilder := allocStringBuilder()
defer freeStringBuilder(itemStyleBuilder)
@ -627,7 +621,7 @@ func (listView *listViewData) checkboxSubviews(adapter ListAdapter, buffer *stri
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
itemDiv := listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign)
itemDiv := listView.checkboxItemDiv(hCheckboxAlign, vCheckboxAlign)
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
current := GetCurrent(listView)
@ -734,7 +728,7 @@ func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign))
buffer.WriteString(listView.checkboxItemDiv(hCheckboxAlign, vCheckboxAlign))
if checked {
buffer.WriteString(onDiv)
} else {
@ -966,10 +960,10 @@ func (listView *listViewData) handleCommand(self View, command PropertyName, dat
func (listView *listViewData) handleCurrent(number int) {
listView.properties[Current] = number
for _, listener := range getOneArgEventListeners[ListView, int](listView, nil, ListItemSelectedEvent) {
listener(listView, number)
listener.Run(listView, number)
}
if listener, ok := listView.changeListener[Current]; ok {
listener(listView, Current)
listener.Run(listView, Current)
}
}
@ -1028,16 +1022,16 @@ func (listView *listViewData) onItemClick(number int) {
setArrayPropertyValue(listView, Checked, checkedItem)
if listener, ok := listView.changeListener[Checked]; ok {
listener(listView, Checked)
listener.Run(listView, Checked)
}
for _, listener := range getOneArgEventListeners[ListView, []int](listView, nil, ListItemCheckedEvent) {
listener(listView, checkedItem)
listener.Run(listView, checkedItem)
}
}
for _, listener := range getOneArgEventListeners[ListView, int](listView, nil, ListItemClickedEvent) {
listener(listView, number)
listener.Run(listView, number)
}
}
@ -1054,58 +1048,97 @@ func (listView *listViewData) onItemResize(self View, index string, x, y, width,
}
// GetVerticalAlign return the vertical align of a list: TopAlign (0), BottomAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, VerticalAlign, TopAlign, false)
}
// GetHorizontalAlign return the vertical align of a list/checkbox: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, HorizontalAlign, LeftAlign, false)
}
// GetListItemClickedListeners returns a ListItemClickedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemClickedListeners(view View, subviewID ...string) []func(ListView, int) {
return getOneArgEventListeners[ListView, int](view, subviewID, ListItemClickedEvent)
//
// Result elements can be of the following types:
// - func(rui.ListView, int),
// - func(rui.ListView),
// - func(int),
// - 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 GetListItemClickedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[ListView, int](view, subviewID, ListItemClickedEvent)
}
// GetListItemSelectedListeners returns a ListItemSelectedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemSelectedListeners(view View, subviewID ...string) []func(ListView, int) {
return getOneArgEventListeners[ListView, int](view, subviewID, ListItemSelectedEvent)
//
// Result elements can be of the following types:
// - func(rui.ListView, int),
// - func(rui.ListView),
// - func(int),
// - 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 GetListItemSelectedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[ListView, int](view, subviewID, ListItemSelectedEvent)
}
// GetListItemCheckedListeners returns a ListItemCheckedListener of the ListView.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetListItemCheckedListeners(view View, subviewID ...string) []func(ListView, []int) {
return getOneArgEventListeners[ListView, []int](view, subviewID, ListItemCheckedEvent)
//
// Result elements can be of the following types:
// - func(rui.ListView, []int),
// - func(rui.ListView),
// - func([]int),
// - 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 GetListItemCheckedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[ListView, []int](view, subviewID, ListItemCheckedEvent)
}
// GetListItemWidth returns the width of a ListView item.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListItemWidth(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ItemWidth, false)
}
// GetListItemHeight returns the height of a ListView item.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListItemHeight(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, ItemHeight, false)
}
// GetListViewCheckbox returns the ListView checkbox type: NoneCheckbox (0), SingleCheckbox (1), or MultipleCheckbox (2).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListViewCheckbox(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemCheckbox, 0, false)
}
// GetListViewCheckedItems returns the array of ListView checked items.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListViewCheckedItems(view View, subviewID ...string) []int {
if view = getSubview(view, subviewID); view != nil {
if value := view.getRaw(Checked); value != nil {
@ -1138,34 +1171,44 @@ func IsListViewCheckedItem(view View, subviewID string, index int) bool {
// GetListViewCheckboxVerticalAlign returns the vertical align of the ListView checkbox:
// TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListViewCheckboxVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxVerticalAlign, TopAlign, false)
}
// GetListViewCheckboxHorizontalAlign returns the horizontal align of the ListView checkbox:
// LeftAlign (0), RightAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListViewCheckboxHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, CheckboxHorizontalAlign, LeftAlign, false)
}
// GetListItemVerticalAlign returns the vertical align of the ListView item content:
// TopAlign (0), BottomAlign (1), CenterAlign (2)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListItemVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemVerticalAlign, TopAlign, false)
}
// ItemHorizontalAlign returns the horizontal align of the ListView item content:
// LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListItemHorizontalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, ItemHorizontalAlign, LeftAlign, false)
}
// GetListItemFrame - returns the location and size of the ListView item in pixels.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListItemFrame(view View, subviewID string, index int) Frame {
if subviewID != "" {
view = ViewByID(view, subviewID)
@ -1182,7 +1225,9 @@ func GetListItemFrame(view View, subviewID string, index int) Frame {
}
// GetListViewAdapter - returns the ListView adapter.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetListViewAdapter(view View, subviewID ...string) ListAdapter {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(Items); value != nil {

View File

@ -3,6 +3,7 @@ package rui
import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
)
@ -488,7 +489,7 @@ const (
// - player - Interface of a player which generated this event,
// - code - Error code. See below,
// - message - Error message,
//
// Error codes:
// - 0 (PlayerErrorUnknown) - Unknown error,
// - 1 (PlayerErrorAborted) - Fetching the associated resource was interrupted by a user request,
@ -610,7 +611,7 @@ func (player *mediaPlayerData) setFunc(tag PropertyName, value any) []PropertyNa
return setOneArgEventListener[MediaPlayer, float64](player, tag, value)
case PlayerErrorEvent:
if listeners, ok := valueToPlayerErrorListeners(value); ok {
if listeners, ok := valueToMediaPlayerErrorListeners(value); ok {
return setArrayPropertyValue(player, tag, listeners)
}
notCompatibleType(tag, value)
@ -677,6 +678,7 @@ func setMediaPlayerSource(properties Properties, value any) []PropertyName {
return []PropertyName{Source}
}
/*
func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), bool) {
if value == nil {
return nil, true
@ -801,7 +803,7 @@ func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), b
return nil, false
}
*/
func mediaPlayerEvents() map[PropertyName]string {
return map[PropertyName]string{
AbortEvent: "onabort",
@ -981,31 +983,23 @@ func (player *mediaPlayerData) handleCommand(self View, command PropertyName, da
PlayingEvent, ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent,
WaitingEvent:
if value := player.getRaw(command); value != nil {
if listeners, ok := value.([]func(MediaPlayer)); ok {
for _, listener := range listeners {
listener(player)
}
}
for _, listener := range getNoArgEventListeners[MediaPlayer](player, nil, command) {
listener.Run(player)
}
case TimeUpdateEvent, DurationChangedEvent, RateChangedEvent, VolumeChangedEvent:
if value := player.getRaw(command); value != nil {
if listeners, ok := value.([]func(MediaPlayer, float64)); ok {
time := dataFloatProperty(data, "value")
for _, listener := range listeners {
listener(player, time)
}
}
for _, listener := range getOneArgEventListeners[MediaPlayer, float64](player, nil, command) {
listener.Run(player, time)
}
case PlayerErrorEvent:
if value := player.getRaw(command); value != nil {
if listeners, ok := value.([]func(MediaPlayer, int, string)); ok {
if listeners, ok := value.([]mediaPlayerErrorListener); ok {
code, _ := dataIntProperty(data, "code")
message, _ := data.PropertyValue("message")
for _, listener := range listeners {
listener(player, code, message)
listener.Run(player, code, message)
}
}
}
@ -1254,3 +1248,393 @@ func IsMediaPlayerPaused(view View, playerID string) bool {
ErrorLog(`The found View is not MediaPlayer`)
return false
}
type mediaPlayerErrorListener interface {
Run(MediaPlayer, int, string)
rawListener() any
}
type mediaPlayerErrorListener0 struct {
fn func()
}
type mediaPlayerErrorListenerP struct {
fn func(MediaPlayer)
}
type mediaPlayerErrorListenerI struct {
fn func(int)
}
type mediaPlayerErrorListenerS struct {
fn func(string)
}
type mediaPlayerErrorListenerPI struct {
fn func(MediaPlayer, int)
}
type mediaPlayerErrorListenerPS struct {
fn func(MediaPlayer, string)
}
type mediaPlayerErrorListenerIS struct {
fn func(int, string)
}
type mediaPlayerErrorListenerPIS struct {
fn func(MediaPlayer, int, string)
}
type mediaPlayerErrorListenerBinding struct {
name string
}
func newMediaPlayerErrorListener0(fn func()) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListener0)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListener0) Run(_ MediaPlayer, _ int, _ string) {
data.fn()
}
func (data *mediaPlayerErrorListener0) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerP(fn func(MediaPlayer)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerP)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerP) Run(player MediaPlayer, _ int, _ string) {
data.fn(player)
}
func (data *mediaPlayerErrorListenerP) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerI(fn func(int)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerI)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerI) Run(_ MediaPlayer, code int, _ string) {
data.fn(code)
}
func (data *mediaPlayerErrorListenerI) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerS(fn func(string)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerS)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerS) Run(_ MediaPlayer, _ int, message string) {
data.fn(message)
}
func (data *mediaPlayerErrorListenerS) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerPI(fn func(MediaPlayer, int)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerPI)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerPI) Run(player MediaPlayer, code int, _ string) {
data.fn(player, code)
}
func (data *mediaPlayerErrorListenerPI) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerPS(fn func(MediaPlayer, string)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerPS)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerPS) Run(player MediaPlayer, _ int, message string) {
data.fn(player, message)
}
func (data *mediaPlayerErrorListenerPS) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerIS(fn func(int, string)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerIS)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerIS) Run(_ MediaPlayer, code int, message string) {
data.fn(code, message)
}
func (data *mediaPlayerErrorListenerIS) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerPIS(fn func(MediaPlayer, int, string)) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerPIS)
obj.fn = fn
return obj
}
func (data *mediaPlayerErrorListenerPIS) Run(player MediaPlayer, code int, message string) {
data.fn(player, code, message)
}
func (data *mediaPlayerErrorListenerPIS) rawListener() any {
return data.fn
}
func newMediaPlayerErrorListenerBinding(name string) mediaPlayerErrorListener {
obj := new(mediaPlayerErrorListenerBinding)
obj.name = name
return obj
}
func (data *mediaPlayerErrorListenerBinding) Run(player MediaPlayer, code int, message string) {
bind := player.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 equalType(inType, reflect.TypeOf(player)) {
args = []reflect.Value{reflect.ValueOf(player)}
} else if equalType(inType, reflect.TypeOf(code)) {
args = []reflect.Value{reflect.ValueOf(code)}
} else if equalType(inType, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(message)}
}
case 2:
in0 := methodType.In(0)
in1 := methodType.In(1)
if equalType(in0, reflect.TypeOf(player)) {
if equalType(in1, reflect.TypeOf(code)) {
args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(code)}
} else if equalType(in1, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(player), reflect.ValueOf(message)}
}
} else if equalType(in0, reflect.TypeOf(code)) && equalType(in1, reflect.TypeOf(message)) {
args = []reflect.Value{reflect.ValueOf(code), reflect.ValueOf(message)}
}
case 3:
if equalType(methodType.In(0), reflect.TypeOf(player)) &&
equalType(methodType.In(1), reflect.TypeOf(code)) &&
equalType(methodType.In(2), reflect.TypeOf(message)) {
args = []reflect.Value{
reflect.ValueOf(player),
reflect.ValueOf(code),
reflect.ValueOf(message),
}
}
}
if args != nil {
method.Call(args)
} else {
ErrorLogF(`Unsupported prototype of "%s" method`, data.name)
}
}
func (data *mediaPlayerErrorListenerBinding) rawListener() any {
return data.name
}
func valueToMediaPlayerErrorListeners(value any) ([]mediaPlayerErrorListener, bool) {
if value == nil {
return nil, true
}
switch value := value.(type) {
case []mediaPlayerErrorListener:
return value, true
case mediaPlayerErrorListener:
return []mediaPlayerErrorListener{value}, true
case string:
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerBinding(value)}, true
case func():
return []mediaPlayerErrorListener{newMediaPlayerErrorListener0(value)}, true
case func(MediaPlayer):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerP(value)}, true
case func(int):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerI(value)}, true
case func(string):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerS(value)}, true
case func(MediaPlayer, int):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerPI(value)}, true
case func(MediaPlayer, string):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerPS(value)}, true
case func(int, string):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerIS(value)}, true
case func(MediaPlayer, int, string):
return []mediaPlayerErrorListener{newMediaPlayerErrorListenerPIS(value)}, true
case []func():
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListener0(fn))
}
}
return result, len(result) > 0
case []func(MediaPlayer):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerP(fn))
}
}
return result, len(result) > 0
case []func(int):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerI(fn))
}
}
return result, len(result) > 0
case []func(string):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerS(fn))
}
}
return result, len(result) > 0
case []func(MediaPlayer, int):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerPI(fn))
}
}
return result, len(result) > 0
case []func(MediaPlayer, string):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerPS(fn))
}
}
return result, len(result) > 0
case []func(int, string):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerIS(fn))
}
}
return result, len(result) > 0
case []func(MediaPlayer, int, string):
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, fn := range value {
if fn != nil {
result = append(result, newMediaPlayerErrorListenerPIS(fn))
}
}
return result, len(result) > 0
case []any:
result := make([]mediaPlayerErrorListener, 0, len(value))
for _, v := range value {
if v != nil {
switch v := v.(type) {
case func():
result = append(result, newMediaPlayerErrorListener0(v))
case func(MediaPlayer):
result = append(result, newMediaPlayerErrorListenerP(v))
case func(int):
result = append(result, newMediaPlayerErrorListenerI(v))
case func(string):
result = append(result, newMediaPlayerErrorListenerS(v))
case func(MediaPlayer, int):
result = append(result, newMediaPlayerErrorListenerPI(v))
case func(MediaPlayer, string):
result = append(result, newMediaPlayerErrorListenerPS(v))
case func(int, string):
result = append(result, newMediaPlayerErrorListenerIS(v))
case func(MediaPlayer, int, string):
result = append(result, newMediaPlayerErrorListenerPIS(v))
case string:
result = append(result, newMediaPlayerErrorListenerBinding(v))
default:
return nil, false
}
}
}
return result, len(result) > 0
}
return nil, false
}
func getMediaPlayerErrorListenerBinding(listeners []mediaPlayerErrorListener) string {
for _, listener := range listeners {
raw := listener.rawListener()
if text, ok := raw.(string); ok && text != "" {
return text
}
}
return ""
}

View File

@ -280,56 +280,128 @@ func handleMouseEvents(view View, tag PropertyName, data DataObject) {
event.init(data)
for _, listener := range listeners {
listener(view, event)
listener.Run(view, event)
}
}
}
// GetClickListeners returns the "click-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetClickListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, ClickEvent)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetClickListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, ClickEvent)
}
// GetDoubleClickListeners returns the "double-click-event" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetDoubleClickListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, DoubleClickEvent)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetDoubleClickListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, DoubleClickEvent)
}
// GetContextMenuListeners returns the "context-menu" listener list.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetContextMenuListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, ContextMenuEvent)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetContextMenuListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, ContextMenuEvent)
}
// GetMouseDownListeners returns the "mouse-down" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMouseDownListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseDown)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetMouseDownListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, MouseDown)
}
// GetMouseUpListeners returns the "mouse-up" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMouseUpListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseUp)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetMouseUpListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, MouseUp)
}
// GetMouseMoveListeners returns the "mouse-move" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMouseMoveListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseMove)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetMouseMoveListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, MouseMove)
}
// GetMouseOverListeners returns the "mouse-over" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMouseOverListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseOver)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetMouseOverListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, MouseOver)
}
// GetMouseOutListeners returns the "mouse-out" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetMouseOutListeners(view View, subviewID ...string) []func(View, MouseEvent) {
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseOut)
//
// Result elements can be of the following types:
// - func(View, MouseEvent),
// - func(View),
// - func(MouseEvent),
// - 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 GetMouseOutListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, MouseEvent](view, subviewID, MouseOut)
}

View File

@ -166,8 +166,8 @@ func (picker *numberPickerData) setFunc(tag PropertyName, value any) []PropertyN
}
func (picker *numberPickerData) numberFormat() string {
if precission := GetNumberPickerPrecision(picker); precission > 0 {
return fmt.Sprintf("%%.%df", precission)
if precision := GetNumberPickerPrecision(picker); precision > 0 {
return fmt.Sprintf("%%.%df", precision)
}
return "%g"
}
@ -201,7 +201,7 @@ func (picker *numberPickerData) propertyChanged(tag PropertyName) {
format := picker.numberFormat()
picker.Session().callFunc("setInputValue", picker.htmlID(), fmt.Sprintf(format, value))
if listeners := GetNumberChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[NumberPicker, float64](picker, nil, NumberChangedEvent); len(listeners) > 0 {
old := 0.0
if val := picker.getRaw("old-number"); val != nil {
if n, ok := val.(float64); ok {
@ -210,7 +210,7 @@ func (picker *numberPickerData) propertyChanged(tag PropertyName) {
}
if old != value {
for _, listener := range listeners {
listener(picker, value, old)
listener.Run(picker, value, old)
}
}
}
@ -244,27 +244,27 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde
min, max := GetNumberPickerMinMax(picker)
if min != math.Inf(-1) {
buffer.WriteString(` min="`)
buffer.WriteString(fmt.Sprintf(format, min))
fmt.Fprintf(buffer, format, min)
buffer.WriteByte('"')
}
if max != math.Inf(1) {
buffer.WriteString(` max="`)
buffer.WriteString(fmt.Sprintf(format, max))
fmt.Fprintf(buffer, format, max)
buffer.WriteByte('"')
}
step := GetNumberPickerStep(picker)
if step != 0 {
buffer.WriteString(` step="`)
buffer.WriteString(fmt.Sprintf(format, step))
fmt.Fprintf(buffer, format, step)
buffer.WriteByte('"')
} else {
buffer.WriteString(` step="any"`)
}
buffer.WriteString(` value="`)
buffer.WriteString(fmt.Sprintf(format, GetNumberPickerValue(picker)))
fmt.Fprintf(buffer, format, GetNumberPickerValue(picker))
buffer.WriteByte('"')
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
@ -280,11 +280,11 @@ func (picker *numberPickerData) handleCommand(self View, command PropertyName, d
oldValue := GetNumberPickerValue(picker)
picker.properties[NumberPickerValue] = text
if value != oldValue {
for _, listener := range GetNumberChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[NumberPicker, float64](picker, nil, NumberChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[NumberPickerValue]; ok {
listener(picker, NumberPickerValue)
listener.Run(picker, NumberPickerValue)
}
}
}
@ -298,13 +298,17 @@ func (picker *numberPickerData) handleCommand(self View, command PropertyName, d
// GetNumberPickerType returns the type of NumberPicker subview. Valid values:
// NumberEditor (0) - NumberPicker is presented by editor (default type);
// NumberSlider (1) - NumberPicker is presented by slider.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetNumberPickerType(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, NumberPickerType, NumberEditor, false)
}
// GetNumberPickerMinMax returns the min and max value of NumberPicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
view = getSubview(view, subviewID)
pickerType := GetNumberPickerType(view)
@ -328,7 +332,9 @@ func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
}
// GetNumberPickerStep returns the value changing step of NumberPicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetNumberPickerStep(view View, subviewID ...string) float64 {
view = getSubview(view, subviewID)
_, max := GetNumberPickerMinMax(view)
@ -341,7 +347,9 @@ func GetNumberPickerStep(view View, subviewID ...string) float64 {
}
// GetNumberPickerValue returns the value of NumberPicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetNumberPickerValue(view View, subviewID ...string) float64 {
view = getSubview(view, subviewID)
min, _ := GetNumberPickerMinMax(view)
@ -350,13 +358,26 @@ func GetNumberPickerValue(view View, subviewID ...string) float64 {
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
return getTwoArgEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
//
// Result elements can be of the following types:
// - func(rui.NumberPicker, float64, float64),
// - func(rui.NumberPicker, float64),
// - func(rui.NumberPicker),
// - func(float64, float64),
// - func(float64),
// - 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 GetNumberChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
}
// GetNumberPickerPrecision returns the precision of displaying fractional part in editor of NumberPicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetNumberPickerPrecision(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, NumberPickerPrecision, 0)
}

View File

@ -192,42 +192,96 @@ func handlePointerEvents(view View, tag PropertyName, data DataObject) {
event.init(data)
for _, listener := range listeners {
listener(view, event)
listener.Run(view, event)
}
}
// GetPointerDownListeners returns the "pointer-down" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerDownListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerDown)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerDownListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerDown)
}
// GetPointerUpListeners returns the "pointer-up" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerUpListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerUp)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerUpListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerUp)
}
// GetPointerMoveListeners returns the "pointer-move" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerMoveListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerMove)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerMoveListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerMove)
}
// GetPointerCancelListeners returns the "pointer-cancel" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerCancelListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerCancel)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerCancelListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerCancel)
}
// GetPointerOverListeners returns the "pointer-over" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerOverListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerOver)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerOverListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerOver)
}
// GetPointerOutListeners returns the "pointer-out" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetPointerOutListeners(view View, subviewID ...string) []func(View, PointerEvent) {
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerOut)
//
// Result elements can be of the following types:
// - func(View, PointerEvent),
// - func(View),
// - func(PointerEvent),
// - 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 GetPointerOutListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, PointerEvent](view, subviewID, PointerOut)
}

200
popup.go
View File

@ -2,6 +2,7 @@ package rui
import (
"fmt"
"reflect"
"strings"
)
@ -150,7 +151,7 @@ const (
//
// Used by Popup.
// Specify start translation, scale and rotation over x, y and z axes as well as a distortion
// for an animated Popup showing/hidding.
// for an animated Popup showing/hiding.
//
// Supported types: TransformProperty, string.
//
@ -278,7 +279,24 @@ type Popup interface {
viewByHTMLID(id string) View
keyEvent(event KeyEvent) bool
showAnimation()
dissmissAnimation(listener func(PropertyName)) bool
dismissAnimation(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 {
@ -287,7 +305,7 @@ type popupData struct {
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](value); ok {
if listeners, ok := valueToPopupEventListeners(value); ok {
if listeners != nil {
popup.dismissListener = listeners
}
@ -831,7 +849,7 @@ func (popup *popupData) showAnimation() {
}
}
func (popup *popupData) dissmissAnimation(listener func(PropertyName)) bool {
func (popup *popupData) dismissAnimation(listener func(PropertyName)) bool {
if popup.showOpacity != 1 || popup.showTransform != nil {
session := popup.Session()
popup.popupView.Set(TransitionEndEvent, listener)
@ -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)
}
}
@ -923,6 +941,25 @@ func NewPopup(view View, param Params) Popup {
return popup
}
/*
func CreatePopupFromObject(session Session, object DataObject, binding any) Popup {
node := object.RemovePropertyByTag(string(Content))
if node == nil {
ErrorLog(`"content" property not found`)
return nil
}
switch node.Type() {
case ObjectNode:
case TextNode:
default:
ErrorLog(`Unsupported data of "content" property`)
return nil
}
}
*/
// ShowPopup creates a new Popup and shows it
func ShowPopup(view View, param Params) Popup {
popup := NewPopup(view, param)
@ -994,23 +1031,162 @@ func (manager *popupManager) dismissPopup(popup Popup) {
session := popup.Session()
listener := func(PropertyName) {
if index == count-1 {
switch index {
case 0:
if count == 1 {
manager.popups = []Popup{}
session.updateCSSProperty("ruiRoot", "pointer-events", "auto")
session.updateCSSProperty("ruiPopupLayer", "visibility", "hidden")
} else {
manager.popups = manager.popups[:count-1]
}
} else if index == 0 {
manager.popups = manager.popups[1:]
} else {
}
case count - 1:
manager.popups = manager.popups[:count-1]
default:
manager.popups = append(manager.popups[:index], manager.popups[index+1:]...)
}
popup.onDismiss()
}
if !popup.dissmissAnimation(listener) {
if !popup.dismissAnimation(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

@ -101,13 +101,17 @@ func (progress *progressBarData) htmlProperties(self View, buffer *strings.Build
}
// GetProgressBarMax returns the max value of ProgressBar subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetProgressBarMax(view View, subviewID ...string) float64 {
return floatStyledProperty(view, subviewID, ProgressBarMax, 1)
}
// GetProgressBarValue returns the value of ProgressBar subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetProgressBarValue(view View, subviewID ...string) float64 {
return floatStyledProperty(view, subviewID, ProgressBarValue, 0)
}

View File

@ -2723,4 +2723,6 @@ const (
//
// Supported types: string.
Tooltip PropertyName = "tooltip"
Binding PropertyName = "binding"
)

View File

@ -118,7 +118,7 @@ func (resizable *resizableData) setFunc(tag PropertyName, value any) []PropertyN
newContent = value
case DataObject:
if newContent = CreateViewFromObject(resizable.Session(), value); newContent == nil {
if newContent = CreateViewFromObject(resizable.Session(), value, nil); newContent == nil {
return nil
}
@ -354,7 +354,7 @@ func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder)
}
writePos := func(x1, x2, y1, y2 int) {
buffer.WriteString(fmt.Sprintf(` grid-column-start: %d; grid-column-end: %d; grid-row-start: %d; grid-row-end: %d;"></div>`, x1, x2, y1, y2))
fmt.Fprintf(buffer, ` grid-column-start: %d; grid-column-end: %d; grid-row-start: %d; grid-row-end: %d;"></div>`, x1, x2, y1, y2)
}
//htmlID := resizable.htmlID()

View File

@ -25,8 +25,8 @@ func (view *viewData) onResize(self View, x, y, width, height float64) {
view.frame.Top = y
view.frame.Width = width
view.frame.Height = height
for _, listener := range GetResizeListeners(view) {
listener(self, view.frame)
for _, listener := range getOneArgEventListeners[View, Frame](view, nil, ResizeEvent) {
listener.Run(self, view.frame)
}
}
@ -73,7 +73,9 @@ func (view *viewData) Frame() Frame {
}
// GetViewFrame returns the size and location of view's viewport.
// If the second argument (subviewID) is not specified or it is "" then the value of the first argument (view) is 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.
func GetViewFrame(view View, subviewID ...string) Frame {
view = getSubview(view, subviewID)
if view == nil {
@ -83,7 +85,16 @@ func GetViewFrame(view View, subviewID ...string) Frame {
}
// GetResizeListeners returns the list of "resize-event" listeners. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then the listeners list of the first argument (view) is returned
func GetResizeListeners(view View, subviewID ...string) []func(View, Frame) {
return getOneArgEventListeners[View, Frame](view, subviewID, ResizeEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.Frame),
// - func(rui.View),
// - func(rui.Frame),
// - 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 GetResizeListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, Frame](view, subviewID, ResizeEvent)
}

View File

@ -401,7 +401,7 @@ func OpenRawResource(filename string) fs.File {
return nil
}
// AllRawResources returns the list of all raw resouces
// AllRawResources returns the list of all raw resources
func AllRawResources() []string {
result := []string{}
@ -448,7 +448,7 @@ func AllRawResources() []string {
return result
}
// AllImageResources returns the list of all image resouces
// AllImageResources returns the list of all image resources
func AllImageResources() []string {
result := make([]string, 0, len(resources.images))
for image := range resources.images {

View File

@ -25,8 +25,8 @@ func (view *viewData) onScroll(self View, x, y, width, height float64) {
view.scroll.Top = y
view.scroll.Width = width
view.scroll.Height = height
for _, listener := range GetScrollListeners(view) {
listener(self, view.scroll)
for _, listener := range getOneArgEventListeners[View, Frame](view, nil, ScrollEvent) {
listener.Run(self, view.scroll)
}
}
@ -42,7 +42,9 @@ func (view *viewData) setScroll(x, y, width, height float64) {
}
// GetViewScroll returns ...
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is 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.
func GetViewScroll(view View, subviewID ...string) Frame {
view = getSubview(view, subviewID)
if view == nil {
@ -52,13 +54,24 @@ func GetViewScroll(view View, subviewID ...string) Frame {
}
// GetScrollListeners returns the list of "scroll-event" listeners. If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then the listeners list of the first argument (view) is returned
func GetScrollListeners(view View, subviewID ...string) []func(View, Frame) {
return getOneArgEventListeners[View, Frame](view, subviewID, ResizeEvent)
//
// Result elements can be of the following types:
// - func(rui.View, rui.Frame),
// - func(rui.View),
// - func(rui.Frame),
// - 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 GetScrollListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, Frame](view, subviewID, ScrollEvent)
}
// ScrollTo scrolls the view's content to the given position.
// If the second argument (subviewID) is "" then the first argument (view) is used
//
// 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 ScrollViewTo(view View, subviewID string, x, y float64) {
if subviewID != "" {
view = ViewByID(view, subviewID)
@ -69,7 +82,9 @@ func ScrollViewTo(view View, subviewID string, x, y float64) {
}
// ScrollViewToEnd scrolls the view's content to the start of view.
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used
//
// 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 ScrollViewToStart(view View, subviewID ...string) {
if view = getSubview(view, subviewID); view != nil {
view.Session().callFunc("scrollToStart", view.htmlID())
@ -77,7 +92,9 @@ func ScrollViewToStart(view View, subviewID ...string) {
}
// ScrollViewToEnd scrolls the view's content to the end of view.
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used
//
// 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 ScrollViewToEnd(view View, subviewID ...string) {
if view = getSubview(view, subviewID); view != nil {
view.Session().callFunc("scrollToEnd", view.htmlID())

View File

@ -266,7 +266,8 @@ func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
func (session *sessionData) close() {
if session.events != nil {
session.events <- ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
obj, _ := ParseDataText(`session-close{session="` + strconv.Itoa(session.sessionID) + `"}`)
session.events <- obj
}
}

View File

@ -208,7 +208,7 @@ func (data *sizeFuncData) writeString(topFunc string, buffer *strings.Builder) {
buffer.WriteString(arg.String())
case float64:
buffer.WriteString(fmt.Sprintf("%g", arg))
fmt.Fprintf(buffer, "%g", arg)
}
}
@ -302,7 +302,7 @@ func (data *sizeFuncData) writeCSS(topFunc string, buffer *strings.Builder, sess
buffer.WriteString(arg.String())
case float64:
buffer.WriteString(fmt.Sprintf("%g", arg))
fmt.Fprintf(buffer, "%g", arg)
}
}

View File

@ -334,7 +334,9 @@ func (layout *stackLayoutData) init(session Session) {
layout.remove = layout.removeFunc
layout.changed = layout.propertyChanged
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.transitionFinished})
layout.setRaw(TransitionEndEvent, []oneArgListener[View, PropertyName]{
newOneArgListenerVE(layout.transitionFinished),
})
if session.TextDirection() == RightToLeftDirection {
layout.setRaw(PushTransform, NewTransformProperty(Params{TranslateX: Percent(-100)}))
} else {
@ -434,7 +436,7 @@ func (layout *stackLayoutData) setFunc(tag PropertyName, value any) []PropertyNa
// TODO
listeners, ok := valueToOneArgEventListeners[View, PropertyName](value)
if ok && listeners != nil {
listeners = append(listeners, layout.transitionFinished)
listeners = append(listeners, newOneArgListenerVE(layout.transitionFinished))
layout.setRaw(TransitionEndEvent, listeners)
return []PropertyName{tag}
}
@ -464,7 +466,9 @@ func (layout *stackLayoutData) propertyChanged(tag PropertyName) {
func (layout *stackLayoutData) removeFunc(tag PropertyName) []PropertyName {
switch tag {
case TransitionEndEvent:
layout.setRaw(TransitionEndEvent, []func(View, PropertyName){layout.transitionFinished})
layout.setRaw(TransitionEndEvent, []oneArgListener[View, PropertyName]{
newOneArgListenerVE(layout.transitionFinished),
})
return []PropertyName{tag}
}
return layout.viewsContainerData.removeFunc(tag)
@ -606,12 +610,6 @@ func (layout *stackLayoutData) moveToFrontByIndex(index int, onShow []func(View)
session.updateCSSProperty(peekPageID, "transform", transformCSS)
}
func (layout *stackLayoutData) contentChanged() {
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
}
func (layout *stackLayoutData) RemovePeek() View {
return layout.RemoveView(len(layout.views) - 1)
}
@ -679,9 +677,7 @@ func (layout *stackLayoutData) Append(view View) {
}
session.appendToInnerHTML(stackID, buffer.String())
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
layout.contentChanged()
}
}
@ -717,9 +713,7 @@ func (layout *stackLayoutData) Insert(view View, index int) {
session := layout.Session()
session.appendToInnerHTML(stackID, buffer.String())
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
layout.contentChanged()
}
// Remove removes view from list and return it
@ -750,10 +744,7 @@ func (layout *stackLayoutData) RemoveView(index int) View {
}
layout.Session().callFunc("removeView", view.htmlID()+"page")
if listener, ok := layout.changeListener[Content]; ok {
listener(layout, Content)
}
layout.contentChanged()
return view
}
@ -914,7 +905,9 @@ func (layout *stackLayoutData) htmlSubviews(self View, buffer *strings.Builder)
}
// IsMoveToFrontAnimation returns "true" if an animation is used when calling the MoveToFront/MoveToFrontByID method of StackLayout interface.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsMoveToFrontAnimation(view View, subviewID ...string) bool {
if view = getSubview(view, subviewID); view != nil {
if value, ok := boolProperty(view, MoveToFrontAnimation, view.Session()); ok {
@ -950,7 +943,9 @@ func GetPushTiming(view View, subviewID ...string) string {
// GetPushTransform returns the start transform (translation, scale and rotation over x, y and z axes as well as a distortion)
// for an animated pushing of a child view.
// The default value is nil (no transform).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetPushTransform(view View, subviewID ...string) TransformProperty {
return transformStyledProperty(view, subviewID, PushTransform)
}

View File

@ -50,8 +50,9 @@ func (resources *resourceManager) scanStringsDir(path string) {
}
func loadStringResources(text string) {
data := ParseDataText(text)
if data == nil {
data, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return
}

View File

@ -592,14 +592,6 @@ func (table *tableViewData) init(session Session) {
table.tag = "TableView"
table.cellViews = []View{}
table.cellFrame = []Frame{}
/*
table.cellSelectedListener = []func(TableView, int, int){}
table.cellClickedListener = []func(TableView, int, int){}
table.rowSelectedListener = []func(TableView, int){}
table.rowClickedListener = []func(TableView, int){}
table.current.Row = -1
table.current.Column = -1
*/
table.normalize = normalizeTableViewTag
table.set = table.setFunc
table.changed = table.propertyChanged
@ -853,8 +845,17 @@ func (table *tableViewData) propertyChanged(tag PropertyName) {
current := tableViewCurrent(table)
session.callFunc("setTableCellCursorByID", htmlID, current.Row, current.Column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) {
listener.Run(table, current.Row, current.Column)
}
case RowSelection:
session.callFunc("setTableRowCursorByID", htmlID, tableViewCurrent(table).Row)
current := tableViewCurrent(table)
session.callFunc("setTableRowCursorByID", htmlID, current.Row)
for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowSelectedEvent) {
listener.Run(table, current.Row)
}
}
case Gap:
@ -955,63 +956,6 @@ func tableViewCurrentInactiveStyle(view View) string {
return "ruiCurrentTableCell"
}
/*
func (table *tableViewData) valueToCellListeners(value any) []func(TableView, int, int) {
if value == nil {
return []func(TableView, int, int){}
}
switch value := value.(type) {
case func(TableView, int, int):
return []func(TableView, int, int){value}
case func(int, int):
fn := func(_ TableView, row, column int) {
value(row, column)
}
return []func(TableView, int, int){fn}
case []func(TableView, int, int):
return value
case []func(int, int):
listeners := make([]func(TableView, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(_ TableView, row, column int) {
val(row, column)
}
}
return listeners
case []any:
listeners := make([]func(TableView, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TableView, int, int):
listeners[i] = val
case func(int, int):
listeners[i] = func(_ TableView, row, column int) {
val(row, column)
}
default:
return nil
}
}
return listeners
}
return nil
}
*/
func (table *tableViewData) htmlTag() string {
return "table"
}
@ -1326,10 +1270,10 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
buffer.WriteString(string(value))
case float32:
buffer.WriteString(fmt.Sprintf("%g", float64(value)))
fmt.Fprintf(buffer, "%g", float64(value))
case float64:
buffer.WriteString(fmt.Sprintf("%g", value))
fmt.Fprintf(buffer, "%g", value)
case bool:
if value {
@ -1340,7 +1284,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
default:
if n, ok := isInt(value); ok {
buffer.WriteString(fmt.Sprintf("%d", n))
fmt.Fprintf(buffer, "%d", n)
} else {
buffer.WriteString("<Unsupported value>")
}
@ -1359,7 +1303,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
if columnStyle := GetTableColumnStyle(table); columnStyle != nil {
buffer.WriteString("<colgroup>")
for column := 0; column < columnCount; column++ {
for column := range columnCount {
cssBuilder.buffer.Reset()
if styles := columnStyle.ColumnStyle(column); styles != nil {
view.Clear()
@ -1593,10 +1537,10 @@ func (table *tableViewData) writeCellHtml(adapter TableAdapter, row, column int,
buffer.WriteString(string(value))
case float32:
buffer.WriteString(fmt.Sprintf("%g", float64(value)))
fmt.Fprintf(buffer, "%g", float64(value))
case float64:
buffer.WriteString(fmt.Sprintf("%g", value))
fmt.Fprintf(buffer, "%g", value)
case bool:
accentColor := Color(0)
@ -1611,7 +1555,7 @@ func (table *tableViewData) writeCellHtml(adapter TableAdapter, row, column int,
default:
if n, ok := isInt(value); ok {
buffer.WriteString(fmt.Sprintf("%d", n))
fmt.Fprintf(buffer, "%d", n)
} else {
buffer.WriteString("<Unsupported value>")
}
@ -1732,11 +1676,11 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
current.Row = row
table.setRaw(Current, current.Row)
if listener, ok := table.changeListener[Current]; ok {
listener(table, Current)
listener.Run(table, Current)
}
for _, listener := range GetTableRowSelectedListeners(table) {
listener(table, row)
for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowSelectedEvent) {
listener.Run(table, row)
}
}
@ -1749,11 +1693,11 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
current.Column = column
table.setRaw(Current, current.Row)
if listener, ok := table.changeListener[Current]; ok {
listener(table, Current)
listener.Run(table, Current)
}
for _, listener := range GetTableCellSelectedListeners(table) {
listener(table, row, column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellSelectedEvent) {
listener.Run(table, row, column)
}
}
}
@ -1761,16 +1705,16 @@ func (table *tableViewData) handleCommand(self View, command PropertyName, data
case "rowClick":
if row, ok := dataIntProperty(data, "row"); ok {
for _, listener := range GetTableRowClickedListeners(table) {
listener(table, row)
for _, listener := range getOneArgEventListeners[TableView, int](table, nil, TableRowClickedEvent) {
listener.Run(table, row)
}
}
case "cellClick":
if row, ok := dataIntProperty(data, "row"); ok {
if column, ok := dataIntProperty(data, "column"); ok {
for _, listener := range GetTableCellClickedListeners(table) {
listener(table, row, column)
for _, listener := range getTwoArgEventListeners[TableView, int](table, nil, TableCellClickedEvent) {
listener.Run(table, row, column)
}
}
}

View File

@ -26,7 +26,9 @@ func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
}
// GetTableContent returns a TableAdapter which defines the TableView content.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableContent(view View, subviewID ...string) TableAdapter {
if view = getSubview(view, subviewID); view != nil {
if content := view.getRaw(Content); content != nil {
@ -40,7 +42,9 @@ func GetTableContent(view View, subviewID ...string) TableAdapter {
}
// GetTableRowStyle returns a TableRowStyle which defines styles of TableView rows.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
if view = getSubview(view, subviewID); view != nil {
for _, tag := range []PropertyName{RowStyle, Content} {
@ -56,7 +60,9 @@ func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
}
// GetTableColumnStyle returns a TableColumnStyle which defines styles of TableView columns.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
if view = getSubview(view, subviewID); view != nil {
for _, tag := range []PropertyName{ColumnStyle, Content} {
@ -72,7 +78,9 @@ func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
}
// GetTableCellStyle returns a TableCellStyle which defines styles of TableView cells.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableCellStyle(view View, subviewID ...string) TableCellStyle {
if view = getSubview(view, subviewID); view != nil {
for _, tag := range []PropertyName{CellStyle, Content} {
@ -90,26 +98,34 @@ func GetTableCellStyle(view View, subviewID ...string) TableCellStyle {
// GetTableSelectionMode returns the mode of the TableView elements selection.
// Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableSelectionMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false)
}
// GetTableVerticalAlign returns a vertical align in a TableView cell. Returns one of next values:
// TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableVerticalAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TableVerticalAlign, TopAlign, false)
}
// GetTableHeadHeight returns the number of rows in the table header.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableHeadHeight(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, HeadHeight, 0)
}
// GetTableFootHeight returns the number of rows in the table footer.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableFootHeight(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, FootHeight, 0)
}
@ -118,7 +134,9 @@ func GetTableFootHeight(view View, subviewID ...string) int {
// If there is no selected cell/row or the selection mode is NoneSelection (0),
// then a value of the row and column index less than 0 is returned.
// If the selection mode is RowSelection (2) then the returned column index is less than 0.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTableCurrent(view View, subviewID ...string) CellIndex {
if view = getSubview(view, subviewID); view != nil {
if selectionMode := GetTableSelectionMode(view); selectionMode != NoneSelection {
@ -130,30 +148,70 @@ func GetTableCurrent(view View, subviewID ...string) CellIndex {
// GetTableCellClickedListeners returns listeners of event which occurs when the user clicks on a table cell.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableCellClickedListeners(view View, subviewID ...string) []func(TableView, int, int) {
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellClickedEvent)
//
// Result elements can be of the following types:
// - func(rui.TableView, int, int),
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int, int),
// - func(int),
// - 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 GetTableCellClickedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TableView, int](view, subviewID, TableCellClickedEvent)
}
// GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableCellSelectedListeners(view View, subviewID ...string) []func(TableView, int, int) {
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
//
// Result elements can be of the following types:
// - func(rui.TableView, int, int),
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int, int),
// - func(int),
// - 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 GetTableCellSelectedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
}
// GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableRowClickedListeners(view View, subviewID ...string) []func(TableView, int) {
return getOneArgEventListeners[TableView, int](view, subviewID, TableRowClickedEvent)
//
// Result elements can be of the following types:
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int),
// - 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 GetTableRowClickedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[TableView, int](view, subviewID, TableRowClickedEvent)
}
// GetTableRowSelectedListeners returns listeners of event which occurs when a table row becomes selected.
// If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableView, int) {
return getOneArgEventListeners[TableView, int](view, subviewID, TableRowSelectedEvent)
//
// Result elements can be of the following types:
// - func(rui.TableView, int),
// - func(rui.TableView),
// - func(int),
// - 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 GetTableRowSelectedListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[TableView, int](view, subviewID, TableRowSelectedEvent)
}
// ReloadTableViewData updates TableView

View File

@ -211,7 +211,7 @@ func (tabsLayout *tabsLayoutData) propertyChanged(tag PropertyName) {
if listeners := getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent); len(listeners) > 0 {
oldCurrent, _ := intProperty(tabsLayout, "old-current", session, -1)
for _, listener := range listeners {
listener(tabsLayout, current, oldCurrent)
listener.Run(tabsLayout, current, oldCurrent)
}
}
@ -238,155 +238,6 @@ func (tabsLayout *tabsLayoutData) propertyChanged(tag PropertyName) {
}
}
/*
func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) {
if value == nil {
return []func(TabsLayout, int, int){}
}
switch value := value.(type) {
case func(TabsLayout, int, int):
return []func(TabsLayout, int, int){value}
case func(TabsLayout, int):
fn := func(view TabsLayout, current, _ int) {
value(view, current)
}
return []func(TabsLayout, int, int){fn}
case func(TabsLayout):
fn := func(view TabsLayout, _, _ int) {
value(view)
}
return []func(TabsLayout, int, int){fn}
case func(int, int):
fn := func(_ TabsLayout, current, old int) {
value(current, old)
}
return []func(TabsLayout, int, int){fn}
case func(int):
fn := func(_ TabsLayout, current, _ int) {
value(current)
}
return []func(TabsLayout, int, int){fn}
case func():
fn := func(TabsLayout, int, int) {
value()
}
return []func(TabsLayout, int, int){fn}
case []func(TabsLayout, int, int):
return value
case []func(TabsLayout, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, _ int) {
val(view, current)
}
}
return listeners
case []func(TabsLayout):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, _, _ int) {
val(view)
}
}
return listeners
case []func(int, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(_ TabsLayout, current, old int) {
val(current, old)
}
}
return listeners
case []func(int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(_ TabsLayout, current, _ int) {
val(current)
}
}
return listeners
case []func():
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(TabsLayout, int, int) {
val()
}
}
return listeners
case []any:
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TabsLayout, int, int):
listeners[i] = val
case func(TabsLayout, int):
listeners[i] = func(view TabsLayout, current, _ int) {
val(view, current)
}
case func(TabsLayout):
listeners[i] = func(view TabsLayout, _, _ int) {
val(view)
}
case func(int, int):
listeners[i] = func(_ TabsLayout, current, old int) {
val(current, old)
}
case func(int):
listeners[i] = func(_ TabsLayout, current, _ int) {
val(current)
}
case func():
listeners[i] = func(TabsLayout, int, int) {
val()
}
default:
return nil
}
}
return listeners
}
return nil
}
*/
func (tabsLayout *tabsLayoutData) tabsLocation() int {
tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0)
return tabs
@ -504,7 +355,7 @@ func (tabsLayout *tabsLayoutData) ListItem(index int, session Session) View {
Content: "✕",
ClickEvent: func() {
for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
listener(tabsLayout, index)
listener.Run(tabsLayout, index)
}
},
}))
@ -552,9 +403,7 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
if tabsLayout.created {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session())
if listener, ok := tabsLayout.changeListener[Content]; ok {
listener(tabsLayout, Content)
}
tabsLayout.contentChanged()
tabsLayout.Set(Current, len(tabsLayout.views)-1)
}
}
@ -576,11 +425,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
func (tabsLayout *tabsLayoutData) currentChanged(newCurrent, oldCurrent int) {
for _, listener := range getTwoArgEventListeners[TabsLayout, int](tabsLayout, nil, CurrentTabChangedEvent) {
listener(tabsLayout, newCurrent, oldCurrent)
}
if listener, ok := tabsLayout.changeListener[Current]; ok {
listener(tabsLayout, Current)
listener.Run(tabsLayout, newCurrent, oldCurrent)
}
tabsLayout.contentChanged()
}
// Remove removes view from list and return it
@ -606,9 +453,7 @@ func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
tabsLayout.Set(Current, newCurrent)
}
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session())
if listener, ok := tabsLayout.changeListener[Content]; ok {
listener(tabsLayout, Content)
}
tabsLayout.contentChanged()
} else if newCurrent != oldCurrent {
tabsLayout.setRaw(Current, newCurrent)
}
@ -892,7 +737,7 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName,
if numberText, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil {
for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
listener(tabsLayout, number)
listener.Run(tabsLayout, number)
}
}
}
@ -900,3 +745,35 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName,
}
return tabsLayout.viewsContainerData.handleCommand(self, command, data)
}
// GetTabCloseEventListeners returns the "tab-close-event" listener list. If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.TabsLayout, int),
// - func(rui.TabsLayout),
// - func(int),
// - 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 GetTabCloseEventListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[TabsLayout, int](view, subviewID, TabCloseEvent)
}
// GetCurrentTabChangedEventListeners returns the "current-tab-changed" listener list. If there are no listeners then the empty list is returned.
//
// Result elements can be of the following types:
// - func(rui.TabsLayout, int, int),
// - func(rui.TabsLayout, int),
// - func(rui.TabsLayout),
// - func(int, int),
// - func(int),
// - 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 GetCurrentTabChangedEventListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TabsLayout, int](view, subviewID, CurrentTabChangedEvent)
}

View File

@ -111,7 +111,9 @@ func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) {
// GetTextOverflow returns a value of the "text-overflow" property:
// TextOverflowClip (0) or TextOverflowEllipsis (1).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextOverflow(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextOverflow, SingleLineText, false)
}

View File

@ -686,8 +686,11 @@ func (theme *theme) addText(themeText string) bool {
theme.init()
}
data := ParseDataText(themeText)
if data == nil || !data.IsObject() || data.Tag() != "theme" {
data, err := ParseDataText(themeText)
if err != nil {
ErrorLog(err.Error())
return false
} else if !data.IsObject() || data.Tag() != "theme" {
return false
}
@ -975,10 +978,10 @@ func (theme *theme) String() string {
buffer.WriteString(":landscape")
}
if maxWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
fmt.Fprintf(buffer, ":width%d", maxWidth)
}
if maxHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d", maxHeight))
fmt.Fprintf(buffer, ":height%d", maxHeight)
}
buffer.WriteString(" = [\n")
@ -1014,21 +1017,21 @@ func (theme *theme) String() string {
}
if media.MinWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d-", media.MinWidth))
fmt.Fprintf(buffer, ":width%d-", media.MinWidth)
if media.MaxWidth > 0 {
buffer.WriteString(strconv.Itoa(media.MaxWidth))
}
} else if media.MaxWidth > 0 {
buffer.WriteString(fmt.Sprintf(":width%d", media.MaxWidth))
fmt.Fprintf(buffer, ":width%d", media.MaxWidth)
}
if media.MinHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d-", media.MinHeight))
fmt.Fprintf(buffer, ":height%d-", media.MinHeight)
if media.MaxHeight > 0 {
buffer.WriteString(strconv.Itoa(media.MaxHeight))
}
} else if media.MaxHeight > 0 {
buffer.WriteString(fmt.Sprintf(":height%d", media.MaxHeight))
fmt.Fprintf(buffer, ":height%d", media.MaxHeight)
}
buffer.WriteString(" = [\n")

View File

@ -246,7 +246,7 @@ func (picker *timePickerData) propertyChanged(tag PropertyName) {
value := GetTimePickerValue(picker)
session.callFunc("setInputValue", picker.htmlID(), value.Format(timeFormat))
if listeners := GetTimeChangedListeners(picker); len(listeners) > 0 {
if listeners := getTwoArgEventListeners[TimePicker, time.Time](picker, nil, TimeChangedEvent); len(listeners) > 0 {
oldTime := time.Now()
if val := picker.getRaw("old-time"); val != nil {
if time, ok := val.(time.Time); ok {
@ -254,7 +254,7 @@ func (picker *timePickerData) propertyChanged(tag PropertyName) {
}
}
for _, listener := range listeners {
listener(picker, value, oldTime)
listener.Run(picker, value, oldTime)
}
}
@ -320,11 +320,11 @@ func (picker *timePickerData) handleCommand(self View, command PropertyName, dat
oldValue := GetTimePickerValue(picker)
picker.properties[TimePickerValue] = value
if value != oldValue {
for _, listener := range GetTimeChangedListeners(picker) {
listener(picker, value, oldValue)
for _, listener := range getTwoArgEventListeners[TimePicker, time.Time](picker, nil, TimeChangedEvent) {
listener.Run(picker, value, oldValue)
}
if listener, ok := picker.changeListener[TimePickerValue]; ok {
listener(picker, TimePickerValue)
listener.Run(picker, TimePickerValue)
}
}
@ -373,7 +373,9 @@ func getTimeProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool
// GetTimePickerMin returns the min time of TimePicker subview and "true" as the second value if the min time is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) {
if view = getSubview(view, subviewID); view != nil {
return getTimeProperty(view, TimePickerMin, Min)
@ -383,7 +385,9 @@ func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) {
// GetTimePickerMax returns the max time of TimePicker subview and "true" as the second value if the min time is set,
// "false" as the second value otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) {
if view = getSubview(view, subviewID); view != nil {
return getTimeProperty(view, TimePickerMax, Max)
@ -392,13 +396,17 @@ func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) {
}
// GetTimePickerStep returns the time changing step in seconds of TimePicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTimePickerStep(view View, subviewID ...string) int {
return intStyledProperty(view, subviewID, TimePickerStep, 60)
}
// GetTimePickerValue returns the time of TimePicker subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTimePickerValue(view View, subviewID ...string) time.Time {
if view = getSubview(view, subviewID); view == nil {
return time.Now()
@ -409,7 +417,18 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
// GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview.
// If there are no listeners then the empty list is returned
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
return getTwoArgEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
//
// Result elements can be of the following types:
// - func(rui.TimePicker, time.Time, time.Time),
// - func(rui.TimePicker, time.Time),
// - func(rui.TimePicker),
// - func(time.Time, time.Time),
// - func(time.Time),
// - 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 GetTimeChangedListeners(view View, subviewID ...string) []any {
return getTwoArgEventRawListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
}

View File

@ -193,30 +193,66 @@ func handleTouchEvents(view View, tag PropertyName, data DataObject) {
event.init(data)
for _, listener := range listeners {
listener(view, event)
listener.Run(view, event)
}
}
// GetTouchStartListeners returns the "touch-start" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTouchStartListeners(view View, subviewID ...string) []func(View, TouchEvent) {
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchStart)
//
// Result elements can be of the following types:
// - func(rui.View, rui.TouchEvent),
// - func(rui.View),
// - func(rui.TouchEvent),
// - 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 GetTouchStartListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, TouchEvent](view, subviewID, TouchStart)
}
// GetTouchEndListeners returns the "touch-end" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTouchEndListeners(view View, subviewID ...string) []func(View, TouchEvent) {
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchEnd)
//
// Result elements can be of the following types:
// - func(rui.View, rui.TouchEvent),
// - func(rui.View),
// - func(rui.TouchEvent),
// - 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 GetTouchEndListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, TouchEvent](view, subviewID, TouchEnd)
}
// GetTouchMoveListeners returns the "touch-move" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTouchMoveListeners(view View, subviewID ...string) []func(View, TouchEvent) {
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchMove)
//
// Result elements can be of the following types:
// - func(rui.View, rui.TouchEvent),
// - func(rui.View),
// - func(rui.TouchEvent),
// - 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 GetTouchMoveListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, TouchEvent](view, subviewID, TouchMove)
}
// GetTouchCancelListeners returns the "touch-cancel" listener list. If there are no listeners then the empty list is returned.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
func GetTouchCancelListeners(view View, subviewID ...string) []func(View, TouchEvent) {
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchCancel)
//
// Result elements can be of the following types:
// - func(rui.View, rui.TouchEvent),
// - func(rui.View),
// - func(rui.TouchEvent),
// - 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 GetTouchCancelListeners(view View, subviewID ...string) []any {
return getOneArgEventRawListeners[View, TouchEvent](view, subviewID, TouchCancel)
}

View File

@ -398,7 +398,10 @@ func valueToTransformProperty(value any) TransformProperty {
}
case string:
if obj := ParseDataText(value); obj != nil {
obj, err := ParseDataText(value)
if err != nil {
ErrorLog(err.Error())
} else {
return parseObject(obj)
}
}
@ -647,7 +650,9 @@ func transformOriginCSS(x, y, z SizeUnit, session Session) string {
// GetTransform returns a view transform: translation, scale and rotation over x, y and z axes as well as a distortion of a view along x and y axes.
// The default value is nil (no transform)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTransform(view View, subviewID ...string) TransformProperty {
return transformStyledProperty(view, subviewID, Transform)
}
@ -655,14 +660,18 @@ func GetTransform(view View, subviewID ...string) TransformProperty {
// GetPerspective returns a distance between the z = 0 plane and the user in order to give a 3D-positioned
// element some perspective. Each 3D element with z > 0 becomes larger; each 3D-element with z < 0 becomes smaller.
// The default value is 0 (no 3D effects).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetPerspective(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, Perspective, false)
}
// GetPerspectiveOrigin returns a x- and y-coordinate of the position at which the viewer is looking.
// It is used as the vanishing point by the Perspective property. The default value is (50%, 50%).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) {
view = getSubview(view, subviewID)
if view == nil {
@ -675,14 +684,18 @@ func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) {
// visible when turned towards the user. Values:
// true - the back face is visible when turned towards the user (default value).
// false - the back face is hidden, effectively making the element invisible when turned away from the user.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackfaceVisible(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, BackfaceVisible, false)
}
// GetTransformOrigin returns a x-, y-, and z-coordinate of the point around which a view transformation is applied.
// The default value is (50%, 50%, 50%).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTransformOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
view = getSubview(view, subviewID)
if view == nil {
@ -692,7 +705,9 @@ func GetTransformOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, Siz
}
// GetTranslate returns a x-, y-, and z-axis translation value of a 2D/3D translation
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
if transform := GetTransform(view, subviewID...); transform != nil {
return transform.getTranslate(view.Session())
@ -702,7 +717,9 @@ func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit)
// GetSkew returns a angles to use to distort the element along the abscissa (x-axis)
// and the ordinate (y-axis). The default value is 0.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) {
if transform := GetTransform(view, subviewID...); transform != nil {
x, y, _ := transform.getSkew(view.Session())
@ -712,7 +729,9 @@ func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) {
}
// GetScale returns a x-, y-, and z-axis scaling value of a 2D/3D scale. The default value is 1.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetScale(view View, subviewID ...string) (float64, float64, float64) {
if transform := GetTransform(view, subviewID...); transform != nil {
session := view.Session()
@ -725,7 +744,9 @@ func GetScale(view View, subviewID ...string) (float64, float64, float64) {
}
// GetRotate returns a x-, y, z-coordinate of the vector denoting the axis of rotation, and the angle of the view rotation
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetRotate(view View, subviewID ...string) (float64, float64, float64, AngleUnit) {
if transform := GetTransform(view, subviewID...); transform != nil {
session := view.Session()

137
view.go
View File

@ -30,6 +30,8 @@ func (frame Frame) Bottom() float64 {
return frame.Top + frame.Height
}
const changeListeners PropertyName = "change-listeners"
// View represents a base view interface
type View interface {
ViewStyle
@ -66,8 +68,18 @@ type View interface {
// a description of the error is written to the log
SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool
// SetChangeListener set the function to track the change of the View property
SetChangeListener(tag PropertyName, listener func(View, PropertyName))
// SetChangeListener set the function (the second argument) to track the change of the View property (the first argument).
//
// Allowed listener function formats:
//
// func(view rui.View, property rui.PropertyName)
// func(view rui.View)
// func(property rui.PropertyName)
// func()
// string
//
// If the second argument is given as a string, it specifies the name of the binding function.
SetChangeListener(tag PropertyName, listener any) bool
// HasFocus returns 'true' if the view has focus
HasFocus() bool
@ -90,8 +102,9 @@ type View interface {
htmlProperties(self View, buffer *strings.Builder)
cssStyle(self View, builder cssBuilder)
addToCSSStyle(addCSS map[string]string)
exscludeTags() []PropertyName
excludeTags() []PropertyName
htmlDisabledProperty() bool
binding() any
onResize(self View, x, y, width, height float64)
onItemResize(self View, index string, x, y, width, height float64)
@ -109,7 +122,7 @@ type viewData struct {
_htmlID string
parentID string
systemClass string
changeListener map[PropertyName]func(View, PropertyName)
changeListener map[PropertyName]oneArgListener[View, PropertyName]
singleTransition map[PropertyName]AnimationProperty
addCSS map[string]string
frame Frame
@ -161,7 +174,7 @@ func (view *viewData) init(session Session) {
view.changed = view.propertyChanged
view.tag = "View"
view.session = session
view.changeListener = map[PropertyName]func(View, PropertyName){}
view.changeListener = map[PropertyName]oneArgListener[View, PropertyName]{}
view.addCSS = map[string]string{}
//view.animation = map[string]AnimationEndListener{}
view.singleTransition = map[PropertyName]AnimationProperty{}
@ -195,6 +208,18 @@ func (view *viewData) ID() string {
return view.viewID
}
func (view *viewData) binding() any {
if result := view.getRaw(Binding); result != nil {
return result
}
if parent := view.Parent(); parent != nil {
return parent.binding()
}
return nil
}
func (view *viewData) ViewByID(id string) View {
if id == view.ID() {
if v := view.session.viewByHTMLID(view.htmlID()); v != nil {
@ -229,11 +254,8 @@ func (view *viewData) Remove(tag PropertyName) {
if view.created && len(changedTags) > 0 {
for _, tag := range changedTags {
view.changed(tag)
}
for _, tag := range changedTags {
if listener, ok := view.changeListener[tag]; ok {
listener(view, tag)
listener.Run(view, tag)
}
}
}
@ -260,11 +282,8 @@ func (view *viewData) Set(tag PropertyName, value any) bool {
if view.created && len(changedTags) > 0 {
for _, tag := range changedTags {
view.changed(tag)
}
for _, tag := range changedTags {
if listener, ok := view.changeListener[tag]; ok {
listener(view, tag)
listener.Run(view, tag)
}
}
}
@ -282,7 +301,8 @@ func normalizeViewTag(tag PropertyName) PropertyName {
}
func (view *viewData) getFunc(tag PropertyName) any {
if tag == ID {
switch tag {
case ID:
if id := view.ID(); id != "" {
return id
} else {
@ -304,6 +324,14 @@ func (view *viewData) removeFunc(tag PropertyName) []PropertyName {
changedTags = []PropertyName{}
}
case Binding:
if view.getRaw(Binding) != nil {
view.setRaw(Binding, nil)
changedTags = []PropertyName{Binding}
} else {
changedTags = []PropertyName{}
}
case Animation:
if val := view.getRaw(Animation); val != nil {
if animations, ok := val.([]AnimationProperty); ok {
@ -336,6 +364,33 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
notCompatibleType(ID, value)
return nil
case Binding:
view.setRaw(Binding, value)
return []PropertyName{Binding}
case changeListeners:
switch value := value.(type) {
case DataObject:
for i := range value.PropertyCount() {
node := value.Property(i)
if node.Type() == TextNode {
if text := node.Text(); text != "" {
view.changeListener[PropertyName(node.Tag())] = newOneArgListenerBinding[View, PropertyName](text)
}
}
}
if len(view.changeListener) > 0 {
view.setRaw(changeListeners, view.changeListener)
}
return []PropertyName{tag}
case DataNode:
if value.Type() == ObjectNode {
return view.setFunc(tag, value.Object())
}
}
return []PropertyName{}
case Animation:
oldAnimations := []AnimationProperty{}
if val := view.getRaw(Animation); val != nil {
@ -386,20 +441,15 @@ func (view *viewData) setFunc(tag PropertyName, value any) []PropertyName {
case TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent:
result := setOneArgEventListener[View, PropertyName](view, tag, value)
if result == nil {
result = setOneArgEventListener[View, string](view, tag, value)
if result != nil {
if listeners, ok := view.getRaw(tag).([]func(View, string)); ok {
newListeners := make([]func(View, PropertyName), len(listeners))
if listeners, ok := valueToOneArgEventListeners[View, string](view); ok && len(listeners) > 0 {
newListeners := make([]oneArgListener[View, PropertyName], len(listeners))
for i, listener := range listeners {
newListeners[i] = func(view View, name PropertyName) {
listener(view, string(name))
}
newListeners[i] = newOneArgListenerVE(func(view View, name PropertyName) {
listener.Run(view, string(name))
})
}
view.setRaw(tag, newListeners)
return result
}
view.setRaw(tag, nil)
return nil
result = []PropertyName{tag}
}
}
return result
@ -909,7 +959,7 @@ func (view *viewData) propertyChanged(tag PropertyName) {
case DropEffectAllowed:
effect := GetDropEffectAllowed(view)
if effect >= DropEffectCopy && effect >= DropEffectAll {
values := []string{"undifined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"}
values := []string{"undefined", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"}
session.updateProperty(htmlID, "data-drop-effect-allowed", values[effect])
} else {
session.removeProperty(htmlID, "data-drop-effect-allowed")
@ -1016,8 +1066,8 @@ func (view *viewData) htmlProperties(self View, buffer *strings.Builder) {
}
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"`,
view.frame.Left, view.frame.Top, view.frame.Width, view.frame.Height))
fmt.Fprintf(buffer, ` data-left="%g" data-top="%g" data-width="%g" data-height="%g"`,
view.frame.Left, view.frame.Top, view.frame.Width, view.frame.Height)
}
}
@ -1142,13 +1192,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:
@ -1231,12 +1281,33 @@ func (view *viewData) handleCommand(self View, command PropertyName, data DataOb
}
func (view *viewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
func (view *viewData) SetChangeListener(tag PropertyName, listener any) bool {
if listener == nil {
delete(view.changeListener, tag)
} else {
view.changeListener[tag] = listener
switch listener := listener.(type) {
case func():
view.changeListener[tag] = newOneArgListener0[View, PropertyName](listener)
case func(View):
view.changeListener[tag] = newOneArgListenerV[View, PropertyName](listener)
case func(PropertyName):
view.changeListener[tag] = newOneArgListenerE[View](listener)
case func(View, PropertyName):
view.changeListener[tag] = newOneArgListenerVE(listener)
case string:
view.changeListener[tag] = newOneArgListenerBinding[View, PropertyName](listener)
default:
return false
}
view.setRaw(changeListeners, view.changeListener)
}
return true
}
func (view *viewData) HasFocus() bool {
@ -1250,6 +1321,6 @@ func (view *viewData) String() string {
return buffer.String()
}
func (view *viewData) exscludeTags() []PropertyName {
func (view *viewData) excludeTags() []PropertyName {
return nil
}

View File

@ -6,7 +6,7 @@ import (
"strings"
)
var viewCreators = map[string]func(Session) View{
var systemViewCreators = map[string]func(Session) View{
"View": newView,
"ColumnLayout": newColumnLayout,
"ListLayout": newListLayout,
@ -36,54 +36,63 @@ var viewCreators = map[string]func(Session) View{
"VideoPlayer": newVideoPlayer,
}
// RegisterViewCreator register function of creating view
func RegisterViewCreator(tag string, creator func(Session) View) bool {
builtinViews := []string{
"View",
"ViewsContainer",
"ColumnLayout",
"ListLayout",
"GridLayout",
"StackLayout",
"TabsLayout",
"AbsoluteLayout",
"Resizable",
"DetailsView",
"TextView",
"Button",
"Checkbox",
"DropDownList",
"ProgressBar",
"NumberPicker",
"ColorPicker",
"DatePicker",
"TimePicker",
"EditView",
"ListView",
"CanvasView",
"ImageView",
"TableView",
// ViewCreateListener is the listener interface of a view create event
type ViewCreateListener interface {
// OnCreate is a function of binding object that is called by the CreateViewFromText, CreateViewFromResources,
// and CreateViewFromObject functions after the creation of a view
OnCreate(view View)
}
for _, name := range builtinViews {
if name == tag {
var viewCreate map[string]func(Session) View = nil
func viewCreators() map[string]func(Session) View {
if viewCreate == nil {
viewCreate = map[string]func(Session) View{}
for tag, fn := range systemViewCreators {
viewCreate[strings.ToLower(tag)] = fn
}
}
return viewCreate
}
// RegisterViewCreator register function of creating view
func RegisterViewCreator(tag string, creator func(Session) View) bool {
loTag := strings.ToLower(tag)
for name := range systemViewCreators {
if name == loTag {
ErrorLog(`It is forbidden to override the function of ` + tag + ` creating`)
return false
}
}
viewCreators[tag] = creator
viewCreators()[loTag] = creator
return true
}
// CreateViewFromObject create new View and initialize it by Node data
func CreateViewFromObject(session Session, object DataObject) View {
tag := object.Tag()
// CreateViewFromObject create new View and initialize it by DataObject data. Parameters:
// - session - the session to which the view will be attached (should not be nil);
// - object - data describing View;
// - binding - object assigned to the Binding property (may be nil).
//
// If the function fails, it returns nil and an error message is written to the log.
func CreateViewFromObject(session Session, object DataObject, binding any) View {
if session == nil {
ErrorLog(`Session must not be nil`)
return nil
}
tag := object.Tag()
creator, ok := viewCreators()[strings.ToLower(tag)]
if !ok {
ErrorLog(`Unknown view type "` + tag + `"`)
return nil
}
if creator, ok := viewCreators[tag]; ok {
if !session.ignoreViewUpdates() {
session.setIgnoreViewUpdates(true)
defer session.setIgnoreViewUpdates(false)
}
view := creator(session)
view.init(session)
if customView, ok := view.(CustomView); ok {
@ -92,28 +101,52 @@ func CreateViewFromObject(session Session, object DataObject) View {
}
}
parseProperties(view, object)
if binding != nil {
view.setRaw(Binding, binding)
if listener, ok := binding.(ViewCreateListener); ok {
listener.OnCreate(view)
}
}
return view
}
ErrorLog(`Unknown view type "` + object.Tag() + `"`)
// CreateViewFromText create new View and initialize it by content of text. Parameters:
// - session - the session to which the view will be attached (should not be nil);
// - text - text describing View;
// - binding - object assigned to the Binding property (optional parameter).
//
// If the function fails, it returns nil and an error message is written to the log.
func CreateViewFromText(session Session, text string, binding ...any) View {
data, err := ParseDataText(text)
if err != nil {
ErrorLog(err.Error())
return nil
}
// CreateViewFromText create new View and initialize it by content of text
func CreateViewFromText(session Session, text string) View {
if data := ParseDataText(text); data != nil {
return CreateViewFromObject(session, data)
var b any = nil
if len(binding) > 0 {
b = binding[0]
}
return nil
return CreateViewFromObject(session, data, b)
}
// CreateViewFromResources create new View and initialize it by the content of
// the resource file from "views" directory
func CreateViewFromResources(session Session, name string) View {
// the resource file from "views" directory. Parameters:
// - session - the session to which the view will be attached (should not be nil);
// - name - file name in the views folder of the application resources (it is not necessary to specify the .rui extension, it is added automatically);
// - binding - object assigned to the Binding property (optional parameter).
//
// If the function fails, it returns nil and an error message is written to the log.
func CreateViewFromResources(session Session, name string, binding ...any) View {
if strings.ToLower(filepath.Ext(name)) != ".rui" {
name += ".rui"
}
var b any = nil
if len(binding) > 0 {
b = binding[0]
}
for _, fs := range resources.embedFS {
rootDirs := resources.embedRootDirs(fs)
for _, dir := range rootDirs {
@ -123,15 +156,21 @@ func CreateViewFromResources(session Session, name string) View {
case viewDir:
if data, err := fs.ReadFile(dir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
return CreateViewFromObject(session, data)
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}
default:
if data, err := fs.ReadFile(dir + "/" + viewDir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
return CreateViewFromObject(session, data)
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}
}
@ -140,8 +179,11 @@ func CreateViewFromResources(session Session, name string) View {
if resources.path != "" {
if data, err := os.ReadFile(resources.path + viewDir + "/" + name); err == nil {
if data := ParseDataText(string(data)); data != nil {
return CreateViewFromObject(session, data)
data, err := ParseDataText(string(data))
if err != nil {
ErrorLog(err.Error())
} else {
return CreateViewFromObject(session, data, b)
}
}
}

View File

@ -2,9 +2,10 @@ package rui
import (
"fmt"
"sort"
"slices"
"strconv"
"strings"
"time"
)
// ViewStyle interface of the style of view
@ -551,26 +552,126 @@ func viewStyleGet(style Properties, tag PropertyName) any {
}
func supportedPropertyValue(value any) bool {
switch value.(type) {
case string:
switch value := value.(type) {
case string, bool, float32, float64, int, stringWriter, fmt.Stringer:
return true
case []string:
case bool:
case float32:
case float64:
case int:
case stringWriter:
case fmt.Stringer:
return len(value) > 0
case []ShadowProperty:
return len(value) > 0
case []View:
return len(value) > 0
case []any:
return len(value) > 0
case []BackgroundElement:
return len(value) > 0
case []BackgroundGradientPoint:
return len(value) > 0
case []BackgroundGradientAngle:
return len(value) > 0
case map[PropertyName]AnimationProperty:
return len(value) > 0
case []noArgListener[View]:
return getNoArgBinding(value) != ""
case []noArgListener[ImageView]:
return getNoArgBinding(value) != ""
case []noArgListener[MediaPlayer]:
return getNoArgBinding(value) != ""
case []oneArgListener[View, KeyEvent]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, MouseEvent]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, TouchEvent]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, PointerEvent]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, PropertyName]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, string]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, Frame]:
return getOneArgBinding(value) != ""
case []oneArgListener[View, DragAndDropEvent]:
return getOneArgBinding(value) != ""
case []oneArgListener[Checkbox, bool]:
return getOneArgBinding(value) != ""
case []oneArgListener[FilePicker, []FileInfo]:
return getOneArgBinding(value) != ""
case []oneArgListener[ListView, int]:
return getOneArgBinding(value) != ""
case []oneArgListener[ListView, []int]:
return getOneArgBinding(value) != ""
case []oneArgListener[MediaPlayer, float64]:
return getOneArgBinding(value) != ""
case []oneArgListener[TableView, int]:
return getOneArgBinding(value) != ""
case []oneArgListener[TabsLayout, int]:
return getOneArgBinding(value) != ""
case []twoArgListener[ColorPicker, Color]:
return getTwoArgBinding(value) != ""
case []twoArgListener[DatePicker, time.Time]:
return getTwoArgBinding(value) != ""
case []twoArgListener[TimePicker, time.Time]:
return getTwoArgBinding(value) != ""
case []twoArgListener[DropDownList, int]:
return getTwoArgBinding(value) != ""
case []twoArgListener[EditView, string]:
return getTwoArgBinding(value) != ""
case []twoArgListener[NumberPicker, float64]:
return getTwoArgBinding(value) != ""
case []twoArgListener[TableView, int]:
return getTwoArgBinding(value) != ""
case []twoArgListener[TabsLayout, int]:
return getTwoArgBinding(value) != ""
case []mediaPlayerErrorListener:
return getMediaPlayerErrorListenerBinding(value) != ""
case map[PropertyName]oneArgListener[View, PropertyName]:
for _, listener := range value {
if text, ok := listener.rawListener().(string); ok && text != "" {
return true
}
}
return false
default:
return false
}
return true
}
func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, indent string) {
@ -640,8 +741,8 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
writeString(text)
buffer.WriteString(",\n")
}
}
buffer.WriteString(indent)
}
buffer.WriteRune(']')
}
@ -653,10 +754,10 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
}
case float32:
buffer.WriteString(fmt.Sprintf("%g", float64(value)))
fmt.Fprintf(buffer, "%g", float64(value))
case float64:
buffer.WriteString(fmt.Sprintf("%g", value))
fmt.Fprintf(buffer, "%g", value)
case int:
if prop, ok := enumProperties[tag]; ok && value >= 0 && value < len(prop.values) {
@ -698,14 +799,14 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
buffer.WriteString("[]")
case 1:
writeViewStyle(value[0].Tag(), value[0], buffer, indent, value[0].exscludeTags())
writeViewStyle(value[0].Tag(), value[0], buffer, indent, value[0].excludeTags())
default:
buffer.WriteString("[\n")
indent2 := indent + "\t"
for _, v := range value {
buffer.WriteString(indent2)
writeViewStyle(v.Tag(), v, buffer, indent2, v.exscludeTags())
writeViewStyle(v.Tag(), v, buffer, indent2, v.excludeTags())
buffer.WriteString(",\n")
}
@ -791,9 +892,7 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
for tag := range value {
tags = append(tags, tag)
}
sort.Slice(tags, func(i, j int) bool {
return tags[i] < tags[j]
})
slices.Sort(tags)
buffer.WriteString("[\n")
indent2 := indent + "\t"
for _, tag := range tags {
@ -806,6 +905,104 @@ func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, in
buffer.WriteString(indent)
buffer.WriteRune(']')
}
case []noArgListener[View]:
buffer.WriteString(getNoArgBinding(value))
case []noArgListener[ImageView]:
buffer.WriteString(getNoArgBinding(value))
case []noArgListener[MediaPlayer]:
buffer.WriteString(getNoArgBinding(value))
case []oneArgListener[View, KeyEvent]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, MouseEvent]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, TouchEvent]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, PointerEvent]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, PropertyName]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, string]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, Frame]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[View, DragAndDropEvent]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[Checkbox, bool]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[FilePicker, []FileInfo]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[ListView, int]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[ListView, []int]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[MediaPlayer, float64]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[TableView, int]:
buffer.WriteString(getOneArgBinding(value))
case []oneArgListener[TabsLayout, int]:
buffer.WriteString(getOneArgBinding(value))
case []twoArgListener[ColorPicker, Color]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[DatePicker, time.Time]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[TimePicker, time.Time]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[DropDownList, int]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[EditView, string]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[NumberPicker, float64]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[TableView, int]:
buffer.WriteString(getTwoArgBinding(value))
case []twoArgListener[TabsLayout, int]:
buffer.WriteString(getTwoArgBinding(value))
case []mediaPlayerErrorListener:
buffer.WriteString(getMediaPlayerErrorListenerBinding(value))
case map[PropertyName]oneArgListener[View, PropertyName]:
buffer.WriteString("_{")
for key, listener := range value {
if text, ok := listener.rawListener().(string); ok && text != "" {
buffer.WriteRune('\n')
buffer.WriteString(indent)
buffer.WriteRune('\t')
writeString(string(key))
buffer.WriteString(" = ")
writeString(text)
buffer.WriteRune(',')
}
buffer.WriteRune('\n')
buffer.WriteString(indent)
buffer.WriteRune('}')
}
}
}
@ -815,12 +1012,7 @@ func writeViewStyle(name string, view Properties, buffer *strings.Builder, inden
indent += "\t"
writeProperty := func(tag PropertyName, value any) {
for _, exclude := range excludeTags {
if exclude == tag {
return
}
}
if !slices.Contains(excludeTags, tag) {
if supportedPropertyValue(value) {
buffer.WriteString(indent)
buffer.WriteString(string(tag))
@ -829,6 +1021,7 @@ func writeViewStyle(name string, view Properties, buffer *strings.Builder, inden
buffer.WriteString(",\n")
}
}
}
tags := view.AllTags()
removeTag := func(tag PropertyName) {

View File

@ -355,31 +355,41 @@ func GetTextShadows(view View, subviewID ...string) []ShadowProperty {
}
// GetBackgroundColor returns a background color of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackgroundColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, BackgroundColor, false)
}
// GetAccentColor returns the accent color for UI controls generated by some elements.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetAccentColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, AccentColor, false)
}
// GetFontName returns the subview font.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetFontName(view View, subviewID ...string) string {
return stringStyledProperty(view, nil, FontName, true)
}
// GetTextColor returns a text color of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, TextColor, true)
}
// GetTextSize returns a text size of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextSize(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, TextSize, true)
}
@ -392,7 +402,9 @@ func GetTabSize(view View, subviewID ...string) int {
// GetTextWeight returns a text weight of the subview. Returns one of next values:
// 1, 2, 3, 4 (normal text), 5, 6, 7 (bold text), 8 and 9
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextWeight(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextWeight, NormalFont, true)
}
@ -401,7 +413,8 @@ func GetTextWeight(view View, subviewID ...string) int {
//
// LeftAlign = 0, RightAlign = 1, CenterAlign = 2, JustifyAlign = 3
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextAlign(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextAlign, LeftAlign, true)
}
@ -410,89 +423,116 @@ func GetTextAlign(view View, subviewID ...string) int {
//
// TextWrapOn = 0, TextWrapOff = 1, TextWrapBalance = 3
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextWrap(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextWrap, TextWrapOn, true)
}
// GetTextIndent returns a text indent of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextIndent(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, TextIndent, true)
}
// GetLetterSpacing returns a letter spacing of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetLetterSpacing(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, LetterSpacing, true)
}
// GetWordSpacing returns a word spacing of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetWordSpacing(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, WordSpacing, true)
}
// GetLineHeight returns a height of a text line of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetLineHeight(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, LineHeight, true)
}
// IsItalic returns "true" if a text font of the subview is displayed in italics, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsItalic(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Italic, true)
}
// IsSmallCaps returns "true" if a text font of the subview is displayed in small caps, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsSmallCaps(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, SmallCaps, true)
}
// IsStrikethrough returns "true" if a text font of the subview is displayed strikethrough, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsStrikethrough(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Strikethrough, true)
}
// IsOverline returns "true" if a text font of the subview is displayed overlined, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsOverline(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Overline, true)
}
// IsUnderline returns "true" if a text font of the subview is displayed underlined, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsUnderline(view View, subviewID ...string) bool {
return boolStyledProperty(view, subviewID, Underline, true)
}
// GetTextLineThickness returns the stroke thickness of the decoration line that
// is used on text in an element, such as a line-through, underline, or overline.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextLineThickness(view View, subviewID ...string) SizeUnit {
return sizeStyledProperty(view, subviewID, TextLineThickness, true)
}
// GetTextLineStyle returns the stroke style of the decoration line that
// is used on text in an element, such as a line-through, underline, or overline.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextLineStyle(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextLineStyle, SolidLine, true)
}
// GetTextLineColor returns the stroke color of the decoration line that
// is used on text in an element, such as a line-through, underline, or overline.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextLineColor(view View, subviewID ...string) Color {
return colorStyledProperty(view, subviewID, TextLineColor, true)
}
// GetTextTransform returns a text transform of the subview. Return one of next values:
// NoneTextTransform (0), CapitalizeTextTransform (1), LowerCaseTextTransform (2) or UpperCaseTextTransform (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextTransform(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, TextTransform, NoneTextTransform, true)
}
@ -500,14 +540,18 @@ func GetTextTransform(view View, subviewID ...string) int {
// GetWritingMode returns whether lines of text are laid out horizontally or vertically, as well as
// the direction in which blocks progress. Valid values are HorizontalTopToBottom (0),
// HorizontalBottomToTop (1), VerticalRightToLeft (2) and VerticalLeftToRight (3)
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetWritingMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, WritingMode, HorizontalTopToBottom, true)
}
// GetTextDirection - returns a direction of text, table columns, and horizontal overflow.
// Valid values are SystemTextDirection (0), LeftToRightDirection (1), and RightToLeftDirection (2).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTextDirection(view View, subviewID ...string) int {
if view == nil {
return SystemTextDirection
@ -519,7 +563,9 @@ func GetTextDirection(view View, subviewID ...string) int {
// GetVerticalTextOrientation returns a orientation of the text characters in a line. It only affects text
// in vertical mode (when "writing-mode" is "vertical-right-to-left" or "vertical-left-to-right").
// Valid values are MixedTextOrientation (0), UprightTextOrientation (1), and SidewaysTextOrientation (2).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetVerticalTextOrientation(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, VerticalTextOrientation, MixedTextOrientation, true)
}
@ -761,7 +807,9 @@ func BlurViewByID(viewID string, session Session) {
}
// GetCurrent returns the index of the selected item (<0 if there is no a selected item) or the current view index (StackLayout, TabsLayout).
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetCurrent(view View, subviewID ...string) int {
defaultValue := -1
if view = getSubview(view, subviewID); view != nil {
@ -775,7 +823,9 @@ func GetCurrent(view View, subviewID ...string) int {
}
// IsUserSelect returns "true" if the user can select text, "false" otherwise.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func IsUserSelect(view View, subviewID ...string) bool {
if view = getSubview(view, subviewID); view != nil {
value, _ := isUserSelect(view)
@ -821,7 +871,8 @@ func isUserSelect(view View) (bool, bool) {
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetMixBlendMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, MixBlendMode, BlendNormal, true)
}
@ -833,13 +884,16 @@ func GetMixBlendMode(view View, subviewID ...string) int {
// BlendSoftLight (9), BlendDifference (10), BlendExclusion (11), BlendHue (12),
// BlendSaturation (13), BlendColor (14), BlendLuminosity (15)
//
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetBackgroundBlendMode(view View, subviewID ...string) int {
return enumStyledProperty(view, subviewID, BackgroundBlendMode, BlendNormal, true)
}
// GetTooltip returns a tooltip text of the subview.
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is 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.
func GetTooltip(view View, subviewID ...string) string {
if view = getSubview(view, subviewID); view != nil {
if value := view.Get(Tooltip); value != nil {

View File

@ -85,11 +85,14 @@ func (container *viewsContainerData) Append(view View) {
viewHTML(view, buffer, "")
container.Session().appendToInnerHTML(container.htmlID(), buffer.String())
if listener, ok := container.changeListener[Content]; ok {
listener(container, Content)
container.contentChanged()
}
}
func (container *viewsContainerData) contentChanged() {
if listener, ok := container.changeListener[Content]; ok {
listener.Run(container, Content)
}
}
func (container *viewsContainerData) insert(view View, index int) bool {
@ -113,9 +116,7 @@ func (container *viewsContainerData) insert(view View, index int) bool {
func (container *viewsContainerData) Insert(view View, index int) {
if container.insert(view, index) && container.created {
updateInnerHTML(container.htmlID(), container.Session())
if listener, ok := container.changeListener[Content]; ok {
listener(container, Content)
}
container.contentChanged()
}
}
@ -131,11 +132,12 @@ func (container *viewsContainerData) removeView(index int) View {
}
view := container.views[index]
if index == 0 {
switch index {
case 0:
container.views = container.views[1:]
} else if index == count-1 {
case count - 1:
container.views = container.views[:index]
} else {
default:
container.views = append(container.views[:index], container.views[index+1:]...)
}
@ -148,9 +150,7 @@ func (container *viewsContainerData) RemoveView(index int) View {
view := container.removeView(index)
if view != nil && container.created {
container.Session().callFunc("removeView", view.htmlID())
if listener, ok := container.changeListener[Content]; ok {
listener(container, Content)
}
container.contentChanged()
}
return view
}
@ -173,14 +173,11 @@ func (container *viewsContainerData) htmlSubviews(self View, buffer *strings.Bui
}
func viewFromTextValue(text string, session Session) View {
if strings.Contains(text, "{") && strings.Contains(text, "}") {
data := ParseDataText(text)
if data != nil {
if view := CreateViewFromObject(session, data); view != nil {
if data, err := ParseDataText(text); err == nil {
if view := CreateViewFromObject(session, data, nil); view != nil {
return view
}
}
}
return NewTextView(session, Params{Text: text})
}
@ -277,7 +274,7 @@ func (container *viewsContainerData) setContent(value any) bool {
container.views = views
case DataObject:
if view := CreateViewFromObject(session, value); view != nil {
if view := CreateViewFromObject(session, value, nil); view != nil {
container.views = []View{view}
} else {
return false
@ -287,7 +284,7 @@ func (container *viewsContainerData) setContent(value any) bool {
views := []View{}
for _, data := range value {
if data.IsObject() {
if view := CreateViewFromObject(session, data.Object()); view != nil {
if view := CreateViewFromObject(session, data.Object(), nil); view != nil {
views = append(views, view)
}
} else {

View File

@ -190,7 +190,7 @@ func (bridge *webBridge) argToString(arg any) (string, bool) {
for _, val := range arg {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
fmt.Fprintf(buffer, "%g", val)
}
buffer.WriteRune(']')
return buffer.String(), true