mirror of https://github.com/anoshenko/rui.git
Merge branch 'v0.18'
This commit is contained in:
commit
6c0980c46e
59
CHANGELOG.md
59
CHANGELOG.md
|
|
@ -1,3 +1,62 @@
|
||||||
|
# v0.18.0
|
||||||
|
|
||||||
|
* Property name type changed from string to PropertyName.
|
||||||
|
|
||||||
|
* Renamed:
|
||||||
|
Transform interface -> TransformProperty
|
||||||
|
NewTransform function -> NewTransformProperty
|
||||||
|
TransformTag constant -> Transform.
|
||||||
|
"origin-x" property -> "transform-origin-x"
|
||||||
|
"origin-y" property -> "transform-origin-y"
|
||||||
|
"origin-z" property -> "transform-origin-z"
|
||||||
|
GetOrigin function -> GetTransformOrigin.
|
||||||
|
BorderBoxClip constant -> BorderBox
|
||||||
|
PaddingBoxClip constant -> PaddingBox
|
||||||
|
ContentBoxClip constant -> ContentBox.
|
||||||
|
ViewShadow interface -> ShadowProperty
|
||||||
|
NewViewShadow function -> NewShadow
|
||||||
|
NewInsetViewShadow function -> NewInsetShadow
|
||||||
|
NewShadowWithParams function -> NewShadowProperty
|
||||||
|
NewColumnSeparator function -> NewColumnSeparatorProperty
|
||||||
|
ClipShape interface -> ClipShapeProperty
|
||||||
|
InsetClip function -> NewInsetClip
|
||||||
|
CircleClip function -> NewCircleClip
|
||||||
|
EllipseClip function -> NewEllipseClip
|
||||||
|
PolygonClip function -> NewPolygonClip
|
||||||
|
PolygonPointsClip function -> NewPolygonPointsClip
|
||||||
|
ViewFilter interface -> FilterProperty
|
||||||
|
NewViewFilter function -> NewFilterProperty
|
||||||
|
Animation interface -> AnimationProperty
|
||||||
|
AnimationTag constant -> Animation
|
||||||
|
NewAnimation function -> NewAnimationProperty
|
||||||
|
|
||||||
|
* Added functions: NewBounds, NewEllipticRadius, NewRadii, NewLinearGradient, NewCircleRadialGradient,
|
||||||
|
NewEllipseRadialGradient, GetPushTransform, GetPushDuration, GetPushTiming, IsMoveToFrontAnimation,
|
||||||
|
GetBackground, GetMask, GetBackgroundClip,GetBackgroundOrigin, GetMaskClip, GetMaskOrigin, NewColumnSeparator,
|
||||||
|
NewClipShapeProperty, NewTransitionAnimation, NewAnimation, IsSummaryMarkerHidden.
|
||||||
|
|
||||||
|
* Changed ViewByID functions
|
||||||
|
|
||||||
|
* Added SetConicGradientFillStyle and SetConicGradientStrokeStyle methods to Canvas interface.
|
||||||
|
|
||||||
|
* Changed Push, Pop, MoveToFront, and MoveToFrontByID methods of StackLayout interface.
|
||||||
|
|
||||||
|
* Removed DefaultAnimation, StartToEndAnimation, EndToStartAnimation, TopDownAnimation, and BottomUpAnimation constants.
|
||||||
|
|
||||||
|
* Added StackLayout properties: "push-transform", "push-duration", "push-timing", "move-to-front-animation", "push-perspective",
|
||||||
|
"push-rotate-x", "push-rotate-y", "push-rotate-z", "push-rotate", "push-skew-x", "push-skew-y",
|
||||||
|
"push-scale-x", "push-scale-y", "push-scale-z", "push-translate-x", "push-translate-y", "push-translate-z".
|
||||||
|
|
||||||
|
* Added "show-opacity", "show-transform", "show-duration", and "show-timing" Popup properties.
|
||||||
|
|
||||||
|
* Added "mask", "mask-clip", "mask-origin", and "background-origin" properties.
|
||||||
|
|
||||||
|
* Added "hide-summary-marker" DetailsView property.
|
||||||
|
|
||||||
|
* Added LineJoin type. Type of constants MiterJoin, RoundJoin, and BevelJoin changed to LineJoin. Type of Canvas.SetLineJoin function argument changed to LineJoin.
|
||||||
|
|
||||||
|
* Added LineCap type. Type of constants ButtCap, RoundCap, and SquareCap changed to LineCap. Type of Canvas.SetLineCap function argument changed to LineCap.
|
||||||
|
|
||||||
# v0.17.3
|
# v0.17.3
|
||||||
Added SetParams method to View interface
|
Added SetParams method to View interface
|
||||||
|
|
||||||
|
|
|
||||||
105
README-ru.md
105
README-ru.md
|
|
@ -425,7 +425,7 @@ View имеет ряд свойств, таких как высота, шири
|
||||||
(View реализует данный интерфейс):
|
(View реализует данный интерфейс):
|
||||||
|
|
||||||
type Properties interface {
|
type Properties interface {
|
||||||
Get(tag string) any
|
Get(tag PropertyName) any
|
||||||
Set(tag string, value any) bool
|
Set(tag string, value any) bool
|
||||||
Remove(tag string)
|
Remove(tag string)
|
||||||
Clear()
|
Clear()
|
||||||
|
|
@ -1047,7 +1047,7 @@ RadiusProperty, а не структура BoxRadius. Получить стру
|
||||||
### Свойство "shadow"
|
### Свойство "shadow"
|
||||||
|
|
||||||
Свойство "shadow" позволяет задать тени для View. Теней может быть несколько. Тень описывается
|
Свойство "shadow" позволяет задать тени для View. Теней может быть несколько. Тень описывается
|
||||||
с помощью интерфейса ViewShadow расширяющего интерфейс Properties (см. выше). У тени имеются следующие свойства:
|
с помощью интерфейса ShadowProperty расширяющего интерфейс Properties (см. выше). У тени имеются следующие свойства:
|
||||||
|
|
||||||
| Свойство | Константа | Тип | Описание |
|
| Свойство | Константа | Тип | Описание |
|
||||||
|-----------------|---------------|----------|---------------------------------------------------------------|
|
|-----------------|---------------|----------|---------------------------------------------------------------|
|
||||||
|
|
@ -1058,13 +1058,13 @@ RadiusProperty, а не структура BoxRadius. Получить стру
|
||||||
| "blur" | BlurRadius | float | Радиус размытия тени. Значение должно быть >= 0 |
|
| "blur" | BlurRadius | float | Радиус размытия тени. Значение должно быть >= 0 |
|
||||||
| "spread-radius" | SpreadRadius | float | Увеличение тени. Значение > 0 увеличивает тень, < 0 уменьшает |
|
| "spread-radius" | SpreadRadius | float | Увеличение тени. Значение > 0 увеличивает тень, < 0 уменьшает |
|
||||||
|
|
||||||
Для создания ViewShadow используются три функции:
|
Для создания ShadowProperty используются три функции:
|
||||||
|
|
||||||
func NewViewShadow(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ViewShadow
|
func NewShadowProperty(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ShadowProperty
|
||||||
func NewInsetViewShadow(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ViewShadow
|
func NewInsetShadowProperty(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ShadowProperty
|
||||||
func NewShadowWithParams(params Params) ViewShadow
|
func NewShadowWithParams(params Params) ShadowProperty
|
||||||
|
|
||||||
Функция NewViewShadow создает внешнюю тень (Inset == false), NewInsetViewShadow - внутреннюю
|
Функция NewShadowProperty создает внешнюю тень (Inset == false), NewInsetShadowProperty - внутреннюю
|
||||||
(Inset == true).
|
(Inset == true).
|
||||||
Функция NewShadowWithParams используется когда в качестве параметров необходимо использовать
|
Функция NewShadowWithParams используется когда в качестве параметров необходимо использовать
|
||||||
константы. Например:
|
константы. Например:
|
||||||
|
|
@ -1075,10 +1075,10 @@ RadiusProperty, а не структура BoxRadius. Получить стру
|
||||||
rui.Dilation : 16.0,
|
rui.Dilation : 16.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
В качестве значения свойству "shadow" может быть присвоено ViewShadow, массив ViewShadow,
|
В качестве значения свойству "shadow" может быть присвоено ShadowProperty, массив ShadowProperty,
|
||||||
текстовое представление ViewShadow.
|
текстовое представление ShadowProperty.
|
||||||
|
|
||||||
Текстовое представление ViewShadow имеет следующий формат:
|
Текстовое представление ShadowProperty имеет следующий формат:
|
||||||
|
|
||||||
_{ color = <цвет> [, x-offset = <смещение>] [, y-offset = <смещение>] [, blur = <радиус>]
|
_{ color = <цвет> [, x-offset = <смещение>] [, y-offset = <смещение>] [, blur = <радиус>]
|
||||||
[, spread-radius = <увеличение>] [, inset = <тип>] }
|
[, spread-radius = <увеличение>] [, inset = <тип>] }
|
||||||
|
|
@ -1086,7 +1086,7 @@ RadiusProperty, а не структура BoxRadius. Получить стру
|
||||||
|
|
||||||
Получить значение данного свойства можно с помощью функции
|
Получить значение данного свойства можно с помощью функции
|
||||||
|
|
||||||
func GetViewShadows(view View, subviewID ...string) []ViewShadow
|
func GetShadowPropertys(view View, subviewID ...string) []ShadowProperty
|
||||||
|
|
||||||
Если тень не задана, то данная функция вернет пустой массив
|
Если тень не задана, то данная функция вернет пустой массив
|
||||||
|
|
||||||
|
|
@ -1322,14 +1322,14 @@ AngleUnit или string (угловая константа или текстов
|
||||||
|
|
||||||
### Свойство "clip"
|
### Свойство "clip"
|
||||||
|
|
||||||
Свойство "clip" (константа Clip) типа ClipShape задает задает область образки.
|
Свойство "clip" (константа Clip) типа ClipShapeProperty задает задает область образки.
|
||||||
Есть 4 типа областей обрезки
|
Есть 4 типа областей обрезки
|
||||||
|
|
||||||
#### inset
|
#### inset
|
||||||
|
|
||||||
Прямоугольная область обрезки. Создается с помощью функции:
|
Прямоугольная область обрезки. Создается с помощью функции:
|
||||||
|
|
||||||
func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShape
|
func NewInsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShapeProperty
|
||||||
|
|
||||||
где top, right, bottom, left это расстояние от соответственно верхней, правой, нижней и левой границы
|
где top, right, bottom, left это расстояние от соответственно верхней, правой, нижней и левой границы
|
||||||
View до одноименной границы обрезки; radius - задает радиусы скругления углов области обрезки
|
View до одноименной границы обрезки; radius - задает радиусы скругления углов области обрезки
|
||||||
|
|
@ -1346,7 +1346,7 @@ radius необходимо передать nil
|
||||||
|
|
||||||
Круглая область обрезки. Создается с помощью функции:
|
Круглая область обрезки. Создается с помощью функции:
|
||||||
|
|
||||||
func CircleClip(x, y, radius SizeUnit) ClipShape
|
func NewCircleClip(x, y, radius SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
где x, y - координаты центра окружности; radius - радиус
|
где x, y - координаты центра окружности; radius - радиус
|
||||||
|
|
||||||
|
|
@ -1358,7 +1358,7 @@ radius необходимо передать nil
|
||||||
|
|
||||||
Эллиптическая область обрезки. Создается с помощью функции:
|
Эллиптическая область обрезки. Создается с помощью функции:
|
||||||
|
|
||||||
func EllipseClip(x, y, rx, ry SizeUnit) ClipShape
|
func NewEllipseClip(x, y, rx, ry SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
где x, y - координаты центра эллипса; rх - радиус эллипса по оси X; ry - радиус эллипса по оси Y.
|
где x, y - координаты центра эллипса; rх - радиус эллипса по оси X; ry - радиус эллипса по оси Y.
|
||||||
|
|
||||||
|
|
@ -1370,8 +1370,8 @@ radius необходимо передать nil
|
||||||
|
|
||||||
Многоугольная область обрезки. Создается с помощью функций:
|
Многоугольная область обрезки. Создается с помощью функций:
|
||||||
|
|
||||||
func PolygonClip(points []any) ClipShape
|
func NewPolygonClip(points []any) ClipShapeProperty
|
||||||
func PolygonPointsClip(points []SizeUnit) ClipShape
|
func NewPolygonPointsClip(points []SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
в качестве аргумента передается массив угловых точек многоугольника в следующем порядке: x1, y1, x2, y2, …
|
в качестве аргумента передается массив угловых точек многоугольника в следующем порядке: x1, y1, x2, y2, …
|
||||||
В качестве элементов аргумента функции PolygonClip могут быть или текстовые константы, или
|
В качестве элементов аргумента функции PolygonClip могут быть или текстовые константы, или
|
||||||
|
|
@ -1435,10 +1435,10 @@ radius необходимо передать nil
|
||||||
Свойство "filter" (константа Filter) применяет ко View такие графические эффекты, как размытие, смещение цвета, изменение яркости/контрастности и т.п.
|
Свойство "filter" (константа Filter) применяет ко View такие графические эффекты, как размытие, смещение цвета, изменение яркости/контрастности и т.п.
|
||||||
Свойства "backdrop-filter" (константа BackdropFilter) применяет такие же эффекты но к содержимому располагающемся ниже View.
|
Свойства "backdrop-filter" (константа BackdropFilter) применяет такие же эффекты но к содержимому располагающемся ниже View.
|
||||||
|
|
||||||
В качестве значения свойств "filter" и "backdrop-filter" используется только интерфейс ViewFilter. ViewFilter создается с помощью
|
В качестве значения свойств "filter" и "backdrop-filter" используется только интерфейс FilterProperty. FilterProperty создается с помощью
|
||||||
функции
|
функции
|
||||||
|
|
||||||
func NewViewFilter(params Params) ViewFilter
|
func NewFilterProperty(params Params) FilterProperty
|
||||||
|
|
||||||
В аргументе перечисляются применяемые эффекты. Возможны следующие эффекты:
|
В аргументе перечисляются применяемые эффекты. Возможны следующие эффекты:
|
||||||
|
|
||||||
|
|
@ -1447,7 +1447,7 @@ radius необходимо передать nil
|
||||||
| "blur" | Blur | float64 0…10000px | Размытие по Гауссу |
|
| "blur" | Blur | float64 0…10000px | Размытие по Гауссу |
|
||||||
| "brightness" | Brightness | float64 0…10000% | Изменение яркости |
|
| "brightness" | Brightness | float64 0…10000% | Изменение яркости |
|
||||||
| "contrast" | Contrast | float64 0…10000% | Изменение контрастности |
|
| "contrast" | Contrast | float64 0…10000% | Изменение контрастности |
|
||||||
| "drop-shadow" | DropShadow | []ViewShadow | Добавление тени |
|
| "drop-shadow" | DropShadow | []ShadowProperty | Добавление тени |
|
||||||
| "grayscale" | Grayscale | float64 0…100% | Преобразование к оттенкам серого |
|
| "grayscale" | Grayscale | float64 0…100% | Преобразование к оттенкам серого |
|
||||||
| "hue-rotate" | HueRotate | AngleUnit | Вращение оттенка |
|
| "hue-rotate" | HueRotate | AngleUnit | Вращение оттенка |
|
||||||
| "invert" | Invert | float64 0…100% | Инвертирование цветов |
|
| "invert" | Invert | float64 0…100% | Инвертирование цветов |
|
||||||
|
|
@ -1457,8 +1457,8 @@ radius необходимо передать nil
|
||||||
|
|
||||||
Получить значение текущего фильтра можно с помощью функций
|
Получить значение текущего фильтра можно с помощью функций
|
||||||
|
|
||||||
func GetFilter(view View, subviewID ...string) ViewFilter
|
func GetFilter(view View, subviewID ...string) FilterProperty
|
||||||
func GetBackdropFilter(view View, subviewID ...string) ViewFilter
|
func GetBackdropFilter(view View, subviewID ...string) FilterProperty
|
||||||
|
|
||||||
### Свойство "semantics"
|
### Свойство "semantics"
|
||||||
|
|
||||||
|
|
@ -1704,14 +1704,14 @@ radius необходимо передать nil
|
||||||
#### Свойство "text-shadow"
|
#### Свойство "text-shadow"
|
||||||
|
|
||||||
Свойство "text-shadow" позволяет задать тени для текста. Теней может быть несколько. Тень описывается
|
Свойство "text-shadow" позволяет задать тени для текста. Теней может быть несколько. Тень описывается
|
||||||
с помощью интерфейса ViewShadow (см. выше, раздел "Свойство 'shadow'"). Для тени текста используются только
|
с помощью интерфейса ShadowProperty (см. выше, раздел "Свойство 'shadow'"). Для тени текста используются только
|
||||||
Свойства "color", "x-offset", "y-offset" и "blur". Свойства "inset" и "spread-radius" игнорируются (т.е. их
|
Свойства "color", "x-offset", "y-offset" и "blur". Свойства "inset" и "spread-radius" игнорируются (т.е. их
|
||||||
задание не является ошибкой, просто никакого влияния на тень текста они не имеют).
|
задание не является ошибкой, просто никакого влияния на тень текста они не имеют).
|
||||||
|
|
||||||
Для создания ViewShadow для тени текста используются функции:
|
Для создания ShadowProperty для тени текста используются функции:
|
||||||
|
|
||||||
func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ViewShadow
|
func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ShadowProperty
|
||||||
func NewShadowWithParams(params Params) ViewShadow
|
func NewShadowWithParams(params Params) ShadowProperty
|
||||||
|
|
||||||
Функция NewShadowWithParams используется когда в качестве параметров необходимо использовать
|
Функция NewShadowWithParams используется когда в качестве параметров необходимо использовать
|
||||||
константы. Например:
|
константы. Например:
|
||||||
|
|
@ -1721,12 +1721,12 @@ radius необходимо передать nil
|
||||||
rui.BlurRadius : 8.0,
|
rui.BlurRadius : 8.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
В качестве значения свойству "text-shadow" может быть присвоено ViewShadow, массив ViewShadow,
|
В качестве значения свойству "text-shadow" может быть присвоено ShadowProperty, массив ShadowProperty,
|
||||||
текстовое представление ViewShadow (см. выше, раздел "Свойство 'shadow'").
|
текстовое представление ShadowProperty (см. выше, раздел "Свойство 'shadow'").
|
||||||
|
|
||||||
Получить значение данного свойства можно с помощью функции
|
Получить значение данного свойства можно с помощью функции
|
||||||
|
|
||||||
func GetTextShadows(view View, subviewID ...string) []ViewShadow
|
func GetTextShadows(view View, subviewID ...string) []ShadowProperty
|
||||||
|
|
||||||
Если тень не задана, то данная функция вернет пустой массив
|
Если тень не задана, то данная функция вернет пустой массив
|
||||||
|
|
||||||
|
|
@ -2980,13 +2980,14 @@ DetailsView переключается между состояниями по к
|
||||||
"expanded" (константа Expanded). Соответственно значение "true" показывает дочерние
|
"expanded" (константа Expanded). Соответственно значение "true" показывает дочерние
|
||||||
View, "false" - скрывает.
|
View, "false" - скрывает.
|
||||||
|
|
||||||
Получить значение свойства "expanded" можно с помощью функции
|
По умолчанию в начале элемента "summary" отображается маркер ▶︎/▼. Его можно спрятать.
|
||||||
|
Для этого используется свойство "hide-summary-marker" (константа DetailsView) типа bool.
|
||||||
|
|
||||||
func IsDetailsExpanded(view View, subviewID ...string) bool
|
Получить значение свойств "summary", "expanded" и "hide-summary-marker" можно с помощью функций
|
||||||
|
|
||||||
а значение свойства "summary" можно получить с помощью функции
|
|
||||||
|
|
||||||
func GetDetailsSummary(view View, subviewID ...string) View
|
func GetDetailsSummary(view View, subviewID ...string) View
|
||||||
|
func IsDetailsExpanded(view View, subviewID ...string) bool
|
||||||
|
func IsSummaryMarkerHidden(view View, subviewID ...string) bool
|
||||||
|
|
||||||
## Resizable
|
## Resizable
|
||||||
|
|
||||||
|
|
@ -5038,14 +5039,14 @@ onNo или onCancel (если она не nil).
|
||||||
* Анимированное изменения значения свойства (далее "анимация перехода")
|
* Анимированное изменения значения свойства (далее "анимация перехода")
|
||||||
* Сценарий анимированного изменения одного или нескольких свойств (далее просто "сценарий анимации")
|
* Сценарий анимированного изменения одного или нескольких свойств (далее просто "сценарий анимации")
|
||||||
|
|
||||||
### Интерфейс Animation
|
### Интерфейс AnimationProperty
|
||||||
|
|
||||||
Для задания параметров анимации используется интерфейс Animation. Он расширяет интерфейс Properties.
|
Для задания параметров анимации используется интерфейс AnimationProperty. Он расширяет интерфейс Properties.
|
||||||
Интерфейс создается с помощью функции:
|
Интерфейс создается с помощью функции:
|
||||||
|
|
||||||
func NewAnimation(params Params) Animation
|
func NewAnimationProperty(params Params) AnimationProperty
|
||||||
|
|
||||||
Часть свойств интерфейса Animation используется в обоих типах анимации, остальные используются
|
Часть свойств интерфейса AnimationProperty используется в обоих типах анимации, остальные используются
|
||||||
только в сценариях анимации.
|
только в сценариях анимации.
|
||||||
|
|
||||||
Общими свойствами являются
|
Общими свойствами являются
|
||||||
|
|
@ -5082,13 +5083,13 @@ onNo или onCancel (если она не nil).
|
||||||
|
|
||||||
Например
|
Например
|
||||||
|
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.TimingFunction: rui.StepsTiming(10),
|
rui.TimingFunction: rui.StepsTiming(10),
|
||||||
})
|
})
|
||||||
|
|
||||||
эквивалентно
|
эквивалентно
|
||||||
|
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.TimingFunction: "steps(10)",
|
rui.TimingFunction: "steps(10)",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -5112,31 +5113,31 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете
|
||||||
Однократная анимация запускается с помощью функции SetAnimated интерфейса View. Данная функция имеет следующее
|
Однократная анимация запускается с помощью функции SetAnimated интерфейса View. Данная функция имеет следующее
|
||||||
описание:
|
описание:
|
||||||
|
|
||||||
SetAnimated(tag string, value any, animation Animation) bool
|
SetAnimated(tag string, value any, animation AnimationProperty) bool
|
||||||
|
|
||||||
Она присваивает свойству новое значение, при этом изменение происходит с использованием заданной анимации.
|
Она присваивает свойству новое значение, при этом изменение происходит с использованием заданной анимации.
|
||||||
Например,
|
Например,
|
||||||
|
|
||||||
view.SetAnimated(rui.Width, rui.Px(400), rui.NewAnimation(rui.Params{
|
view.SetAnimated(rui.Width, rui.Px(400), rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 0.75,
|
rui.Duration: 0.75,
|
||||||
rui.TimingFunction: rui.EaseOutTiming,
|
rui.TimingFunction: rui.EaseOutTiming,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
Есть также глобальная функция для анимированного однократного изменения значения свойства дочернего View
|
Есть также глобальная функция для анимированного однократного изменения значения свойства дочернего View
|
||||||
|
|
||||||
func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool
|
func SetAnimated(rootView View, viewID, tag string, value any, animation AnimationProperty) bool
|
||||||
|
|
||||||
Постоянная анимация запускается каждый раз когда изменяется значение свойства. Для задания постоянной
|
Постоянная анимация запускается каждый раз когда изменяется значение свойства. Для задания постоянной
|
||||||
анимации перехода используется свойство "transition" (константа Transition). В качества значения данному
|
анимации перехода используется свойство "transition" (константа Transition). В качества значения данному
|
||||||
Свойству присваивается rui.Params, где в качестве ключа должно быть имя свойства, а значение - интерфейс Animation.
|
Свойству присваивается rui.Params, где в качестве ключа должно быть имя свойства, а значение - интерфейс AnimationProperty.
|
||||||
Например,
|
Например,
|
||||||
|
|
||||||
view.Set(rui.Transition, rui.Params{
|
view.Set(rui.Transition, rui.Params{
|
||||||
rui.Height: rui.NewAnimation(rui.Params{
|
rui.Height: rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 0.75,
|
rui.Duration: 0.75,
|
||||||
rui.TimingFunction: rui.EaseOutTiming,
|
rui.TimingFunction: rui.EaseOutTiming,
|
||||||
},
|
},
|
||||||
rui.BackgroundColor: rui.NewAnimation(rui.Params{
|
rui.BackgroundColor: rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 1.5,
|
rui.Duration: 1.5,
|
||||||
rui.Delay: 0.5,
|
rui.Delay: 0.5,
|
||||||
rui.TimingFunction: rui.Linear,
|
rui.TimingFunction: rui.Linear,
|
||||||
|
|
@ -5151,7 +5152,7 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете
|
||||||
|
|
||||||
Добавлять новые анимации перехода рекомендуется с помощью функции
|
Добавлять новые анимации перехода рекомендуется с помощью функции
|
||||||
|
|
||||||
func AddTransition(view View, subviewID, tag string, animation Animation) bool
|
func AddTransition(view View, subviewID, tag string, animation AnimationProperty) bool
|
||||||
|
|
||||||
Вызов данной функции эквивалентен следующему коду
|
Вызов данной функции эквивалентен следующему коду
|
||||||
|
|
||||||
|
|
@ -5192,7 +5193,7 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете
|
||||||
### Сценарий анимации
|
### Сценарий анимации
|
||||||
|
|
||||||
Сценарий анимации описывает более сложную анимацию, по сравнению с анимацией перехода. Для этого
|
Сценарий анимации описывает более сложную анимацию, по сравнению с анимацией перехода. Для этого
|
||||||
в Animation добавляются дополнительные свойства:
|
в AnimationProperty добавляются дополнительные свойства:
|
||||||
|
|
||||||
#### Свойство "property"
|
#### Свойство "property"
|
||||||
|
|
||||||
|
|
@ -5256,11 +5257,11 @@ KeyFrames - промежуточные значения свойства (клю
|
||||||
|
|
||||||
#### Запуск анимации
|
#### Запуск анимации
|
||||||
|
|
||||||
Для запуска сценария анимации необходимо созданный Animation интерфейс присвоить свойству "animation"
|
Для запуска сценария анимации необходимо созданный AnimationProperty интерфейс присвоить свойству "animation"
|
||||||
(константа AnimationTag). Если View уже отображается на экране, то анимация запускается сразу (с учетом
|
(константа Animation). Если View уже отображается на экране, то анимация запускается сразу (с учетом
|
||||||
заданной задержки), в противоположном случае анимация запускается как только View отобразится на экране.
|
заданной задержки), в противоположном случае анимация запускается как только View отобразится на экране.
|
||||||
|
|
||||||
Свойству "animation" можно присваивать Animation и []Animation, т.е. можно запускать несколько анимаций
|
Свойству "animation" можно присваивать AnimationProperty и []AnimationProperty, т.е. можно запускать несколько анимаций
|
||||||
одновременно для одного View
|
одновременно для одного View
|
||||||
|
|
||||||
Пример,
|
Пример,
|
||||||
|
|
@ -5273,12 +5274,12 @@ KeyFrames - промежуточные значения свойства (клю
|
||||||
90: rui.Px(220),
|
90: rui.Px(220),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.PropertyTag: []rui.AnimatedProperty{prop},
|
rui.PropertyTag: []rui.AnimatedProperty{prop},
|
||||||
rui.Duration: 2,
|
rui.Duration: 2,
|
||||||
rui.TimingFunction: LinearTiming,
|
rui.TimingFunction: LinearTiming,
|
||||||
})
|
})
|
||||||
rui.Set(view, "subview", rui.AnimationTag, animation)
|
rui.Set(view, "subview", rui.Animation, animation)
|
||||||
|
|
||||||
#### Свойство "animation-paused"
|
#### Свойство "animation-paused"
|
||||||
|
|
||||||
|
|
|
||||||
105
README.md
105
README.md
|
|
@ -429,7 +429,7 @@ View has a number of properties like height, width, color, text parameters, etc.
|
||||||
The Properties interface is used to read and write the property value (View implements this interface):
|
The Properties interface is used to read and write the property value (View implements this interface):
|
||||||
|
|
||||||
type Properties interface {
|
type Properties interface {
|
||||||
Get(tag string) any
|
Get(tag PropertyName) any
|
||||||
Set(tag string, value any) bool
|
Set(tag string, value any) bool
|
||||||
Remove(tag string)
|
Remove(tag string)
|
||||||
Clear()
|
Clear()
|
||||||
|
|
@ -1024,7 +1024,7 @@ equivalent to
|
||||||
### "shadow" property
|
### "shadow" property
|
||||||
|
|
||||||
The "shadow" property allows you to set shadows for the View. There may be several shadows.
|
The "shadow" property allows you to set shadows for the View. There may be several shadows.
|
||||||
The shadow is described using the ViewShadow interface extending the Properties interface (see above).
|
The shadow is described using the ShadowProperty interface extending the Properties interface (see above).
|
||||||
The shadow has the following properties:
|
The shadow has the following properties:
|
||||||
|
|
||||||
| Property | Constant | Type | Description |
|
| Property | Constant | Type | Description |
|
||||||
|
|
@ -1036,14 +1036,14 @@ The shadow has the following properties:
|
||||||
| "blur" | BlurRadius | float | Shadow blur radius. The value must be >= 0 |
|
| "blur" | BlurRadius | float | Shadow blur radius. The value must be >= 0 |
|
||||||
| "spread-radius" | SpreadRadius | float | Increase the shadow. Value > 0 increases shadow, < 0 decreases shadow |
|
| "spread-radius" | SpreadRadius | float | Increase the shadow. Value > 0 increases shadow, < 0 decreases shadow |
|
||||||
|
|
||||||
Three functions are used to create a ViewShadow:
|
Three functions are used to create a ShadowProperty:
|
||||||
|
|
||||||
func NewViewShadow(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ViewShadow
|
func NewShadowProperty(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ShadowProperty
|
||||||
func NewInsetViewShadow(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ViewShadow
|
func NewInsetShadowProperty(offsetX, offsetY, blurRadius, spread-radius SizeUnit, color Color) ShadowProperty
|
||||||
func NewShadowWithParams(params Params) ViewShadow
|
func NewShadowWithParams(params Params) ShadowProperty
|
||||||
|
|
||||||
The NewViewShadow function creates an outer shadow (Inset == false),
|
The NewShadowProperty function creates an outer shadow (Inset == false),
|
||||||
NewInsetViewShadow - an inner one (Inset == true).
|
NewInsetShadowProperty - an inner one (Inset == true).
|
||||||
The NewShadowWithParams function is used when constants must be used as parameters.
|
The NewShadowWithParams function is used when constants must be used as parameters.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
@ -1053,16 +1053,16 @@ For example:
|
||||||
rui.Dilation : 16.0,
|
rui.Dilation : 16.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
ViewShadow, ViewShadow array, and ViewShadow textual representation can be assigned as a value to the "shadow" property.
|
ShadowProperty, ShadowProperty array, and ShadowProperty textual representation can be assigned as a value to the "shadow" property.
|
||||||
|
|
||||||
The ViewShadow text representation has the following format:
|
The ShadowProperty text representation has the following format:
|
||||||
|
|
||||||
_{ color = <color> [, x-offset = <offset>] [, y-offset = <offset>] [, blur = <radius>]
|
_{ color = <color> [, x-offset = <offset>] [, y-offset = <offset>] [, blur = <radius>]
|
||||||
[, spread-radius = <increase>] [, inset = <type>] }
|
[, spread-radius = <increase>] [, inset = <type>] }
|
||||||
|
|
||||||
You can get the value of "shadow" property using the function
|
You can get the value of "shadow" property using the function
|
||||||
|
|
||||||
func GetViewShadows(view View, subviewID ...string) []ViewShadow
|
func GetShadowPropertys(view View, subviewID ...string) []ShadowProperty
|
||||||
|
|
||||||
If no shadow is specified, then this function will return an empty array
|
If no shadow is specified, then this function will return an empty array
|
||||||
|
|
||||||
|
|
@ -1301,14 +1301,14 @@ You can get the value of this property using the function
|
||||||
|
|
||||||
### "clip" property
|
### "clip" property
|
||||||
|
|
||||||
The "clip" property (Clip constant) of the ClipShape type specifies the crop area.
|
The "clip" property (Clip constant) of the ClipShapeProperty type specifies the crop area.
|
||||||
There are 4 types of crop areas
|
There are 4 types of crop areas
|
||||||
|
|
||||||
#### inset
|
#### inset
|
||||||
|
|
||||||
Rectangular cropping area. Created with the function:
|
Rectangular cropping area. Created with the function:
|
||||||
|
|
||||||
func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShape
|
func NewInsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShapeProperty
|
||||||
|
|
||||||
where top, right, bottom, left are the distance from respectively the top, right, bottom and left borders of the View
|
where top, right, bottom, left are the distance from respectively the top, right, bottom and left borders of the View
|
||||||
to the cropping border of the same name; radius - sets the radii of the corners of the cropping area
|
to the cropping border of the same name; radius - sets the radii of the corners of the cropping area
|
||||||
|
|
@ -1324,7 +1324,7 @@ The textual description of the rectangular cropping area is in the following for
|
||||||
|
|
||||||
Round cropping area. Created with the function:
|
Round cropping area. Created with the function:
|
||||||
|
|
||||||
func CircleClip(x, y, radius SizeUnit) ClipShape
|
func NewCircleClip(x, y, radius SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
where x, y - coordinates of the center of the circle; radius - radius
|
where x, y - coordinates of the center of the circle; radius - radius
|
||||||
|
|
||||||
|
|
@ -1336,7 +1336,7 @@ The textual description of the circular cropping area is in the following format
|
||||||
|
|
||||||
Elliptical cropping area. Created with the function:
|
Elliptical cropping area. Created with the function:
|
||||||
|
|
||||||
func EllipseClip(x, y, rx, ry SizeUnit) ClipShape
|
func NewEllipseClip(x, y, rx, ry SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
where x, y - coordinates of the center of the ellipse; rх - radius of the ellipse along the X axis; ry is the radius of the ellipse along the Y axis.
|
where x, y - coordinates of the center of the ellipse; rх - radius of the ellipse along the X axis; ry is the radius of the ellipse along the Y axis.
|
||||||
|
|
||||||
|
|
@ -1348,8 +1348,8 @@ The textual description of the elliptical clipping region is in the following fo
|
||||||
|
|
||||||
Polygonal cropping area. Created using functions:
|
Polygonal cropping area. Created using functions:
|
||||||
|
|
||||||
func PolygonClip(points []any) ClipShape
|
func NewPolygonClip(points []any) ClipShapeProperty
|
||||||
func PolygonPointsClip(points []SizeUnit) ClipShape
|
func NewPolygonPointsClip(points []SizeUnit) ClipShapeProperty
|
||||||
|
|
||||||
an array of corner points of the polygon is passed as an argument in the following order: x1, y1, x2, y2, …
|
an array of corner points of the polygon is passed as an argument in the following order: x1, y1, x2, y2, …
|
||||||
The elements of the argument to the PolygonClip function can be either text constants,
|
The elements of the argument to the PolygonClip function can be either text constants,
|
||||||
|
|
@ -1413,10 +1413,10 @@ You can get the value of this property using the function
|
||||||
The "filter" property (Filter constant) applies graphical effects to the View, such as blurring, color shifting, changing brightness/contrast, etc.
|
The "filter" property (Filter constant) applies graphical effects to the View, such as blurring, color shifting, changing brightness/contrast, etc.
|
||||||
The "backdrop-filter" property (BackdropFilter constant) applies the same effects but to the area behind a View.
|
The "backdrop-filter" property (BackdropFilter constant) applies the same effects but to the area behind a View.
|
||||||
|
|
||||||
Only the ViewFilter interface is used as the value of the "filter" properties.
|
Only the FilterProperty interface is used as the value of the "filter" properties.
|
||||||
ViewFilter is created using the function
|
FilterProperty is created using the function
|
||||||
|
|
||||||
func NewViewFilter(params Params) ViewFilter
|
func NewFilterProperty(params Params) FilterProperty
|
||||||
|
|
||||||
The argument lists the effects to apply. The following effects are possible:
|
The argument lists the effects to apply. The following effects are possible:
|
||||||
|
|
||||||
|
|
@ -1425,7 +1425,7 @@ The argument lists the effects to apply. The following effects are possible:
|
||||||
| "blur" | Blur | float64 0…10000px | Gaussian blur |
|
| "blur" | Blur | float64 0…10000px | Gaussian blur |
|
||||||
| "brightness" | Brightness | float64 0…10000% | Brightness change |
|
| "brightness" | Brightness | float64 0…10000% | Brightness change |
|
||||||
| "contrast" | Contrast | float64 0…10000% | Contrast change |
|
| "contrast" | Contrast | float64 0…10000% | Contrast change |
|
||||||
| "drop-shadow" | DropShadow | []ViewShadow | Adding shadow |
|
| "drop-shadow" | DropShadow | []ShadowProperty | Adding shadow |
|
||||||
| "grayscale" | Grayscale | float64 0…100% | Converting to grayscale |
|
| "grayscale" | Grayscale | float64 0…100% | Converting to grayscale |
|
||||||
| "hue-rotate" | HueRotate | AngleUnit | Hue rotation |
|
| "hue-rotate" | HueRotate | AngleUnit | Hue rotation |
|
||||||
| "invert" | Invert | float64 0…100% | Invert colors |
|
| "invert" | Invert | float64 0…100% | Invert colors |
|
||||||
|
|
@ -1442,8 +1442,8 @@ Example
|
||||||
|
|
||||||
You can get the value of the current filter using functions
|
You can get the value of the current filter using functions
|
||||||
|
|
||||||
func GetFilter(view View, subviewID ...string) ViewFilter
|
func GetFilter(view View, subviewID ...string) FilterProperty
|
||||||
func GetBackdropFilter(view View, subviewID ...string) ViewFilter
|
func GetBackdropFilter(view View, subviewID ...string) FilterProperty
|
||||||
|
|
||||||
### "semantics" property
|
### "semantics" property
|
||||||
|
|
||||||
|
|
@ -1686,14 +1686,14 @@ You can get the value of this property using the function
|
||||||
#### "text-shadow" property
|
#### "text-shadow" property
|
||||||
|
|
||||||
The "text-shadow" property allows you to set shadows for the text. There may be several shadows.
|
The "text-shadow" property allows you to set shadows for the text. There may be several shadows.
|
||||||
The shadow is described using the ViewShadow interface (see above, section "The 'shadow' property").
|
The shadow is described using the ShadowProperty interface (see above, section "The 'shadow' property").
|
||||||
For text shadow, only the "color", "x-offset", "y-offset" and "blur" properties are used.
|
For text shadow, only the "color", "x-offset", "y-offset" and "blur" properties are used.
|
||||||
The "inset" and "spread-radius" properties are ignored (i.e. setting them is not an error, they just have no effect on the text shadow).
|
The "inset" and "spread-radius" properties are ignored (i.e. setting them is not an error, they just have no effect on the text shadow).
|
||||||
|
|
||||||
To create a ViewShadow for the text shadow, the following functions are used:
|
To create a ShadowProperty for the text shadow, the following functions are used:
|
||||||
|
|
||||||
func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ViewShadow
|
func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ShadowProperty
|
||||||
func NewShadowWithParams(params Params) ViewShadow
|
func NewShadowWithParams(params Params) ShadowProperty
|
||||||
|
|
||||||
The NewShadowWithParams function is used when constants must be used as parameters. For example:
|
The NewShadowWithParams function is used when constants must be used as parameters. For example:
|
||||||
|
|
||||||
|
|
@ -1702,11 +1702,11 @@ The NewShadowWithParams function is used when constants must be used as paramete
|
||||||
rui.BlurRadius: 8.0,
|
rui.BlurRadius: 8.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
ViewShadow, ViewShadow array, ViewShadow textual representation can be assigned as a value to the "text-shadow" property (see above, section "The 'shadow' property").
|
ShadowProperty, ShadowProperty array, ShadowProperty textual representation can be assigned as a value to the "text-shadow" property (see above, section "The 'shadow' property").
|
||||||
|
|
||||||
You can get the value of this property using the function
|
You can get the value of this property using the function
|
||||||
|
|
||||||
func GetTextShadows(view View, subviewID ...string) []ViewShadow
|
func GetTextShadows(view View, subviewID ...string) []ShadowProperty
|
||||||
|
|
||||||
If no shadow is specified, then this function will return an empty array
|
If no shadow is specified, then this function will return an empty array
|
||||||
|
|
||||||
|
|
@ -2963,13 +2963,14 @@ DetailsView switches between states by clicking on "summary" view.
|
||||||
For forced switching of the DetailsView states, the bool property "expanded" (Expanded constant) is used.
|
For forced switching of the DetailsView states, the bool property "expanded" (Expanded constant) is used.
|
||||||
Accordingly, the value "true" shows child Views, "false" - hides.
|
Accordingly, the value "true" shows child Views, "false" - hides.
|
||||||
|
|
||||||
You can get the value of the "expanded" property using the function
|
By default, a ▶︎/▼ marker is displayed at the beginning of the "summary" element. It can be hidden.
|
||||||
|
For this, the "hide-summary-marker" bool property (DetailsView constant) is used.
|
||||||
|
|
||||||
func IsDetailsExpanded(view View, subviewID ...string) bool
|
The value of the "summary", "expanded" and "hide-summary-marker" properties can be obtained using the functions
|
||||||
|
|
||||||
and the value of the "summary" property can be obtained using the function
|
|
||||||
|
|
||||||
func GetDetailsSummary(view View, subviewID ...string) View
|
func GetDetailsSummary(view View, subviewID ...string) View
|
||||||
|
func IsDetailsExpanded(view View, subviewID ...string) bool
|
||||||
|
func IsSummaryMarkerHidden(view View, subviewID ...string) bool
|
||||||
|
|
||||||
## Resizable
|
## Resizable
|
||||||
|
|
||||||
|
|
@ -5009,14 +5010,14 @@ The library supports two types of animation:
|
||||||
* Animated property value changes (hereinafter "transition animation")
|
* Animated property value changes (hereinafter "transition animation")
|
||||||
* Script animated change of one or more properties (hereinafter simply "animation script")
|
* Script animated change of one or more properties (hereinafter simply "animation script")
|
||||||
|
|
||||||
### Animation interface
|
### AnimationProperty interface
|
||||||
|
|
||||||
The Animation interface is used to set animation parameters. It extends the Properties interface.
|
The AnimationProperty interface is used to set animation parameters. It extends the Properties interface.
|
||||||
The interface is created using the function:
|
The interface is created using the function:
|
||||||
|
|
||||||
func NewAnimation(params Params) Animation
|
func NewAnimationProperty(params Params) AnimationProperty
|
||||||
|
|
||||||
Some of the properties of the Animation interface are used in both types of animation, the rest are used only
|
Some of the properties of the AnimationProperty interface are used in both types of animation, the rest are used only
|
||||||
in animation scripts.
|
in animation scripts.
|
||||||
|
|
||||||
Common properties are
|
Common properties are
|
||||||
|
|
@ -5053,13 +5054,13 @@ You can specify this function either as text or using the function:
|
||||||
|
|
||||||
For example
|
For example
|
||||||
|
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.TimingFunction: rui.StepsTiming(10),
|
rui.TimingFunction: rui.StepsTiming(10),
|
||||||
})
|
})
|
||||||
|
|
||||||
equivalent to
|
equivalent to
|
||||||
|
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.TimingFunction: "steps(10)",
|
rui.TimingFunction: "steps(10)",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -5082,32 +5083,32 @@ There are two types of transition animations:
|
||||||
A one-time animation is triggered using the SetAnimated function of the View interface.
|
A one-time animation is triggered using the SetAnimated function of the View interface.
|
||||||
This function has the following description:
|
This function has the following description:
|
||||||
|
|
||||||
SetAnimated(tag string, value any, animation Animation) bool
|
SetAnimated(tag string, value any, animation AnimationProperty) bool
|
||||||
|
|
||||||
It assigns a new value to the property, and the change occurs using the specified animation.
|
It assigns a new value to the property, and the change occurs using the specified animation.
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
view.SetAnimated(rui.Width, rui.Px(400), rui.NewAnimation(rui.Params{
|
view.SetAnimated(rui.Width, rui.Px(400), rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 0.75,
|
rui.Duration: 0.75,
|
||||||
rui.TimingFunction: rui.EaseOutTiming,
|
rui.TimingFunction: rui.EaseOutTiming,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
There is also a global function for animated one-time change of the property value of the child View
|
There is also a global function for animated one-time change of the property value of the child View
|
||||||
|
|
||||||
func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool
|
func SetAnimated(rootView View, viewID, tag string, value any, animation AnimationProperty) bool
|
||||||
|
|
||||||
A persistent animation runs every time the property value changes.
|
A persistent animation runs every time the property value changes.
|
||||||
To set the constant animation of the transition, use the "transition" property (the Transition constant).
|
To set the constant animation of the transition, use the "transition" property (the Transition constant).
|
||||||
As a value, this property is assigned rui.Params, where the property name should be the key,
|
As a value, this property is assigned rui.Params, where the property name should be the key,
|
||||||
and the value should be the Animation interface.
|
and the value should be the AnimationProperty interface.
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
view.Set(rui.Transition, rui.Params{
|
view.Set(rui.Transition, rui.Params{
|
||||||
rui.Height: rui.NewAnimation(rui.Params{
|
rui.Height: rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 0.75,
|
rui.Duration: 0.75,
|
||||||
rui.TimingFunction: rui.EaseOutTiming,
|
rui.TimingFunction: rui.EaseOutTiming,
|
||||||
},
|
},
|
||||||
rui.BackgroundColor: rui.NewAnimation(rui.Params{
|
rui.BackgroundColor: rui.NewAnimationProperty(rui.Params{
|
||||||
rui.Duration: 1.5,
|
rui.Duration: 1.5,
|
||||||
rui.Delay: 0.5,
|
rui.Delay: 0.5,
|
||||||
rui.TimingFunction: rui.Linear,
|
rui.TimingFunction: rui.Linear,
|
||||||
|
|
@ -5122,7 +5123,7 @@ To get the current list of permanent transition animations, use the function
|
||||||
|
|
||||||
It is recommended to add new transition animations using the function
|
It is recommended to add new transition animations using the function
|
||||||
|
|
||||||
func AddTransition(view View, subviewID, tag string, animation Animation) bool
|
func AddTransition(view View, subviewID, tag string, animation AnimationProperty) bool
|
||||||
|
|
||||||
Calling this function is equivalent to the following code
|
Calling this function is equivalent to the following code
|
||||||
|
|
||||||
|
|
@ -5162,7 +5163,7 @@ Get lists of listeners for transition animation events using functions:
|
||||||
|
|
||||||
### Animation script
|
### Animation script
|
||||||
|
|
||||||
An animation script describes a more complex animation than a transition animation. To do this, additional properties are added to Animation:
|
An animation script describes a more complex animation than a transition animation. To do this, additional properties are added to AnimationProperty:
|
||||||
|
|
||||||
#### "property" property
|
#### "property" property
|
||||||
|
|
||||||
|
|
@ -5228,12 +5229,12 @@ backward playback of the sequence. It can take the following values:
|
||||||
|
|
||||||
#### Animation start
|
#### Animation start
|
||||||
|
|
||||||
To start the animation script, you must assign the interface created by Animation to the "animation" property
|
To start the animation script, you must assign the interface created by AnimationProperty to the "animation" property
|
||||||
(the AnimationTag constant). If the View is already displayed on the screen, then the animation starts immediately
|
(the Animation constant). If the View is already displayed on the screen, then the animation starts immediately
|
||||||
(taking into account the specified delay), otherwise the animation starts as soon as the View is displayed
|
(taking into account the specified delay), otherwise the animation starts as soon as the View is displayed
|
||||||
on the screen.
|
on the screen.
|
||||||
|
|
||||||
The "animation" property can be assigned Animation and [] Animation, ie. you can run several animations
|
The "animation" property can be assigned AnimationProperty and [] AnimationProperty, ie. you can run several animations
|
||||||
at the same time for one View
|
at the same time for one View
|
||||||
|
|
||||||
Example,
|
Example,
|
||||||
|
|
@ -5246,12 +5247,12 @@ Example,
|
||||||
90: rui.Px(220),
|
90: rui.Px(220),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
animation := rui.NewAnimation(rui.Params{
|
animation := rui.NewAnimationProperty(rui.Params{
|
||||||
rui.PropertyTag: []rui.AnimatedProperty{prop},
|
rui.PropertyTag: []rui.AnimatedProperty{prop},
|
||||||
rui.Duration: 2,
|
rui.Duration: 2,
|
||||||
rui.TimingFunction: LinearTiming,
|
rui.TimingFunction: LinearTiming,
|
||||||
})
|
})
|
||||||
rui.Set(view, "subview", rui.AnimationTag, animation)
|
rui.Set(view, "subview", rui.Animation, animation)
|
||||||
|
|
||||||
#### "animation-paused" property
|
#### "animation-paused" property
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ func NewAbsoluteLayout(session Session, params Params) AbsoluteLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAbsoluteLayout(session Session) View {
|
func newAbsoluteLayout(session Session) View {
|
||||||
return NewAbsoluteLayout(session, nil)
|
//return NewAbsoluteLayout(session, nil)
|
||||||
|
return new(absoluteLayoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of ViewsContainer by default values
|
// Init initialize fields of ViewsContainer by default values
|
||||||
|
|
@ -34,7 +35,7 @@ func (layout *absoluteLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
if layout.views != nil {
|
if layout.views != nil {
|
||||||
for _, view := range layout.views {
|
for _, view := range layout.views {
|
||||||
view.addToCSSStyle(map[string]string{`position`: `absolute`})
|
view.addToCSSStyle(map[string]string{`position`: `absolute`})
|
||||||
viewHTML(view, buffer)
|
viewHTML(view, buffer, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
angleUnit.go
20
angleUnit.go
|
|
@ -15,12 +15,16 @@ type AngleUnitType uint8
|
||||||
const (
|
const (
|
||||||
// Radian - angle in radians
|
// Radian - angle in radians
|
||||||
Radian AngleUnitType = 0
|
Radian AngleUnitType = 0
|
||||||
|
|
||||||
// Radian - angle in radians * π
|
// Radian - angle in radians * π
|
||||||
PiRadian AngleUnitType = 1
|
PiRadian AngleUnitType = 1
|
||||||
|
|
||||||
// Degree - angle in degrees
|
// Degree - angle in degrees
|
||||||
Degree AngleUnitType = 2
|
Degree AngleUnitType = 2
|
||||||
|
|
||||||
// Gradian - angle in gradian (1⁄400 of a full circle)
|
// Gradian - angle in gradian (1⁄400 of a full circle)
|
||||||
Gradian AngleUnitType = 3
|
Gradian AngleUnitType = 3
|
||||||
|
|
||||||
// Turn - angle in turns (1 turn = 360 degree)
|
// Turn - angle in turns (1 turn = 360 degree)
|
||||||
Turn AngleUnitType = 4
|
Turn AngleUnitType = 4
|
||||||
)
|
)
|
||||||
|
|
@ -35,23 +39,23 @@ type AngleUnit struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deg creates AngleUnit with Degree type
|
// Deg creates AngleUnit with Degree type
|
||||||
func Deg(value float64) AngleUnit {
|
func Deg[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) AngleUnit {
|
||||||
return AngleUnit{Type: Degree, Value: value}
|
return AngleUnit{Type: Degree, Value: float64(value)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rad create AngleUnit with Radian type
|
// Rad create AngleUnit with Radian type
|
||||||
func Rad(value float64) AngleUnit {
|
func Rad[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) AngleUnit {
|
||||||
return AngleUnit{Type: Radian, Value: value}
|
return AngleUnit{Type: Radian, Value: float64(value)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PiRad create AngleUnit with PiRadian type
|
// PiRad create AngleUnit with PiRadian type
|
||||||
func PiRad(value float64) AngleUnit {
|
func PiRad[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) AngleUnit {
|
||||||
return AngleUnit{Type: PiRadian, Value: value}
|
return AngleUnit{Type: PiRadian, Value: float64(value)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grad create AngleUnit with Gradian type
|
// Grad create AngleUnit with Gradian type
|
||||||
func Grad(value float64) AngleUnit {
|
func Grad[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) AngleUnit {
|
||||||
return AngleUnit{Type: Gradian, Value: value}
|
return AngleUnit{Type: Gradian, Value: float64(value)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal compare two AngleUnit. Return true if AngleUnit are equal
|
// Equal compare two AngleUnit. Return true if AngleUnit are equal
|
||||||
|
|
|
||||||
548
animation.go
548
animation.go
|
|
@ -9,134 +9,148 @@ import (
|
||||||
|
|
||||||
// Constants which related to view's animation
|
// Constants which related to view's animation
|
||||||
const (
|
const (
|
||||||
// AnimationTag is the constant for "animation" property tag.
|
// Animation is the constant for "animation" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Sets and starts animations.
|
// Sets and starts animations.
|
||||||
//
|
//
|
||||||
// Supported types: `Animation`, `[]Animation`.
|
// Supported types: AnimationProperty, []AnimationProperty.
|
||||||
//
|
//
|
||||||
// Internal type is `[]Animation`, other types converted to it during assignment.
|
// Internal type is []AnimationProperty, other types converted to it during assignment.
|
||||||
// See `Animation` description for more details.
|
// See AnimationProperty description for more details.
|
||||||
AnimationTag = "animation"
|
Animation PropertyName = "animation"
|
||||||
|
|
||||||
// AnimationPaused is the constant for "animation-paused" property tag.
|
// AnimationPaused is the constant for "animation-paused" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
// Controls whether the animation is running or paused.
|
// Controls whether the animation is running or paused.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Animation is paused.
|
// - true, 1, "true", "yes", "on", or "1" - Animation is paused.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Animation is playing.
|
// - false, 0, "false", "no", "off", or "0" - Animation is playing.
|
||||||
AnimationPaused = "animation-paused"
|
AnimationPaused PropertyName = "animation-paused"
|
||||||
|
|
||||||
// Transition is the constant for "transition" property tag.
|
// Transition is the constant for "transition" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Sets transition animation of view properties. Each provided property must contain `Animation` which describe how
|
//
|
||||||
|
// Sets transition animation of view properties. Each provided property must contain AnimationProperty which describe how
|
||||||
// particular property will be animated on property value change. Transition animation can be applied to properties of the
|
// particular property will be animated on property value change. Transition animation can be applied to properties of the
|
||||||
// type `SizeUnit`, `Color`, `AngleUnit`, `float64` and composite properties that contain elements of the listed types(for
|
// type SizeUnit, Color, AngleUnit, float64 and composite properties that contain elements of the listed types(for
|
||||||
// example, "shadow", "border", etc.). If we'll try to animate other properties with internal type like `bool` or
|
// example, "shadow", "border", etc.). If we'll try to animate other properties with internal type like bool or
|
||||||
// `string` no error will occur, simply there will be no animation.
|
// string no error will occur, simply there will be no animation.
|
||||||
//
|
//
|
||||||
// Supported types: `Params`.
|
// Supported types: Params.
|
||||||
//
|
//
|
||||||
// See `Params` description for more details.
|
// See Params description for more details.
|
||||||
Transition = "transition"
|
Transition PropertyName = "transition"
|
||||||
|
|
||||||
// PropertyTag is the constant for "property" property tag.
|
// PropertyTag is the constant for "property" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
// Describes a scenario for changing a `View`'s property. Used only for animation script.
|
|
||||||
//
|
//
|
||||||
// Supported types: `[]AnimatedProperty`, `AnimatedProperty`.
|
// Describes a scenario for changing a View's property. Used only for animation script.
|
||||||
//
|
//
|
||||||
// Internal type is `[]AnimatedProperty`, other types converted to it during assignment.
|
// Supported types: []AnimatedProperty, AnimatedProperty.
|
||||||
// See `AnimatedProperty` description for more details.
|
//
|
||||||
PropertyTag = "property"
|
// Internal type is []AnimatedProperty, other types converted to it during assignment.
|
||||||
|
// See AnimatedProperty description for more details.
|
||||||
|
PropertyTag PropertyName = "property"
|
||||||
|
|
||||||
// Duration is the constant for "duration" property tag.
|
// Duration is the constant for "duration" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
|
//
|
||||||
// Sets the length of time in seconds that an animation takes to complete one cycle.
|
// Sets the length of time in seconds that an animation takes to complete one cycle.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
Duration = "duration"
|
Duration PropertyName = "duration"
|
||||||
|
|
||||||
// Delay is the constant for "delay" property tag.
|
// Delay is the constant for "delay" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
|
//
|
||||||
// Specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
|
// Specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
|
||||||
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the
|
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the
|
||||||
// animation.
|
// animation.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
Delay = "delay"
|
Delay PropertyName = "delay"
|
||||||
|
|
||||||
// TimingFunction is the constant for "timing-function" property tag.
|
// TimingFunction is the constant for "timing-function" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
|
//
|
||||||
// Set how an animation progresses through the duration of each cycle.
|
// Set how an animation progresses through the duration of each cycle.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// "ease"(`EaseTiming`) - Speed increases towards the middle and slows down at the end.
|
// - "ease" (EaseTiming) - Speed increases towards the middle and slows down at the end.
|
||||||
// "ease-in"(`EaseInTiming`) - Speed is slow at first, but increases in the end.
|
// - "ease-in" (EaseInTiming) - Speed is slow at first, but increases in the end.
|
||||||
// "ease-out"(`EaseOutTiming`) - Speed is fast at first, but decreases in the end.
|
// - "ease-out" (EaseOutTiming) - Speed is fast at first, but decreases in the end.
|
||||||
// "ease-in-out"(`EaseInOutTiming`) - Speed is slow at first, but quickly increases and at the end it decreases again.
|
// - "ease-in-out" (EaseInOutTiming) - Speed is slow at first, but quickly increases and at the end it decreases again.
|
||||||
// "linear"(`LinearTiming`) - Constant speed.
|
// - "linear" (LinearTiming) - Constant speed.
|
||||||
TimingFunction = "timing-function"
|
// - "step(n)" (StepTiming(n int) function) - Timing function along stepCount stops along the transition, displaying each stop for equal lengths of time.
|
||||||
|
// - "cubic-bezier(x1, y1, x2, y2)" (CubicBezierTiming(x1, y1, x2, y2 float64) function) - Cubic-Bezier curve timing function. x1 and x2 must be in the range [0, 1].
|
||||||
|
TimingFunction PropertyName = "timing-function"
|
||||||
|
|
||||||
// IterationCount is the constant for "iteration-count" property tag.
|
// IterationCount is the constant for "iteration-count" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
|
//
|
||||||
// Sets the number of times an animation sequence should be played before stopping. Used only for animation script.
|
// Sets the number of times an animation sequence should be played before stopping. Used only for animation script.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `int`, other types converted to it during assignment.
|
// Internal type is int, other types converted to it during assignment.
|
||||||
IterationCount = "iteration-count"
|
IterationCount PropertyName = "iteration-count"
|
||||||
|
|
||||||
// AnimationDirection is the constant for "animation-direction" property tag.
|
// AnimationDirection is the constant for "animation-direction" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Animation`.
|
// Used by AnimationProperty.
|
||||||
|
//
|
||||||
// Whether an animation should play forward, backward, or alternate back and forth between playing the sequence forward
|
// Whether an animation should play forward, backward, or alternate back and forth between playing the sequence forward
|
||||||
// and backward. Used only for animation script.
|
// and backward. Used only for animation script.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NormalAnimation`) or "normal" - The animation plays forward every iteration, that is, when the animation ends, it is immediately reset to its starting position and played again.
|
// - 0 (NormalAnimation) or "normal" - The animation plays forward every iteration, that is, when the animation ends, it is immediately reset to its starting position and played again.
|
||||||
// `1`(`ReverseAnimation`) or "reverse" - The animation plays backwards, from the last position to the first, and then resets to the final position and plays again.
|
// - 1 (ReverseAnimation) or "reverse" - The animation plays backwards, from the last position to the first, and then resets to the final position and plays again.
|
||||||
// `2`(`AlternateAnimation`) or "alternate" - The animation changes direction in each cycle, that is, in the first cycle, it starts from the start position, reaches the end position, and in the second cycle, it continues from the end position and reaches the start position, and so on.
|
// - 2 (AlternateAnimation) or "alternate" - The animation changes direction in each cycle, that is, in the first cycle, it starts from the start position, reaches the end position, and in the second cycle, it continues from the end position and reaches the start position, and so on.
|
||||||
// `3`(`AlternateReverseAnimation`) or "alternate-reverse" - The animation starts playing from the end position and reaches the start position, and in the next cycle, continuing from the start position, it goes to the end position.
|
// - 3 (AlternateReverseAnimation) or "alternate-reverse" - The animation starts playing from the end position and reaches the start position, and in the next cycle, continuing from the start position, it goes to the end position.
|
||||||
AnimationDirection = "animation-direction"
|
AnimationDirection PropertyName = "animation-direction"
|
||||||
|
|
||||||
// NormalAnimation is value of the "animation-direction" property.
|
// NormalAnimation is value of the "animation-direction" property.
|
||||||
|
//
|
||||||
// The animation plays forwards each cycle. In other words, each time the animation cycles,
|
// The animation plays forwards each cycle. In other words, each time the animation cycles,
|
||||||
// the animation will reset to the beginning state and start over again. This is the default value.
|
// the animation will reset to the beginning state and start over again. This is the default value.
|
||||||
NormalAnimation = 0
|
NormalAnimation = 0
|
||||||
|
|
||||||
// ReverseAnimation is value of the "animation-direction" property.
|
// ReverseAnimation is value of the "animation-direction" property.
|
||||||
|
//
|
||||||
// The animation plays backwards each cycle. In other words, each time the animation cycles,
|
// The animation plays backwards each cycle. In other words, each time the animation cycles,
|
||||||
// the animation will reset to the end state and start over again. Animation steps are performed
|
// the animation will reset to the end state and start over again. Animation steps are performed
|
||||||
// backwards, and timing functions are also reversed.
|
// backwards, and timing functions are also reversed.
|
||||||
|
//
|
||||||
// For example, an "ease-in" timing function becomes "ease-out".
|
// For example, an "ease-in" timing function becomes "ease-out".
|
||||||
ReverseAnimation = 1
|
ReverseAnimation = 1
|
||||||
|
|
||||||
// AlternateAnimation is value of the "animation-direction" property.
|
// AlternateAnimation is value of the "animation-direction" property.
|
||||||
|
//
|
||||||
// The animation reverses direction each cycle, with the first iteration being played forwards.
|
// The animation reverses direction each cycle, with the first iteration being played forwards.
|
||||||
// The count to determine if a cycle is even or odd starts at one.
|
// The count to determine if a cycle is even or odd starts at one.
|
||||||
AlternateAnimation = 2
|
AlternateAnimation = 2
|
||||||
|
|
||||||
// AlternateReverseAnimation is value of the "animation-direction" property.
|
// AlternateReverseAnimation is value of the "animation-direction" property.
|
||||||
|
//
|
||||||
// The animation reverses direction each cycle, with the first iteration being played backwards.
|
// The animation reverses direction each cycle, with the first iteration being played backwards.
|
||||||
// The count to determine if a cycle is even or odd starts at one.
|
// The count to determine if a cycle is even or odd starts at one.
|
||||||
AlternateReverseAnimation = 3
|
AlternateReverseAnimation = 3
|
||||||
|
|
@ -180,7 +194,7 @@ func CubicBezierTiming(x1, y1, x2, y2 float64) string {
|
||||||
// AnimatedProperty describes the change script of one property
|
// AnimatedProperty describes the change script of one property
|
||||||
type AnimatedProperty struct {
|
type AnimatedProperty struct {
|
||||||
// Tag is the name of the property
|
// Tag is the name of the property
|
||||||
Tag string
|
Tag PropertyName
|
||||||
// From is the initial value of the property
|
// From is the initial value of the property
|
||||||
From any
|
From any
|
||||||
// To is the final value of the property
|
// To is the final value of the property
|
||||||
|
|
@ -190,24 +204,25 @@ type AnimatedProperty struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type animationData struct {
|
type animationData struct {
|
||||||
propertyList
|
dataProperty
|
||||||
keyFramesName string
|
keyFramesName string
|
||||||
usageCounter int
|
usageCounter int
|
||||||
view View
|
view View
|
||||||
listener func(view View, animation Animation, event string)
|
listener func(view View, animation AnimationProperty, event PropertyName)
|
||||||
oldListeners map[string][]func(View, string)
|
oldListeners map[PropertyName][]func(View, PropertyName)
|
||||||
oldAnimation []Animation
|
oldAnimation []AnimationProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animation interface is used to set animation parameters. Used properties:
|
// AnimationProperty interface is used to set animation parameters. Used properties:
|
||||||
|
//
|
||||||
// "property", "id", "duration", "delay", "timing-function", "iteration-count", and "animation-direction"
|
// "property", "id", "duration", "delay", "timing-function", "iteration-count", and "animation-direction"
|
||||||
type Animation interface {
|
type AnimationProperty interface {
|
||||||
Properties
|
Properties
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
|
|
||||||
// Start starts the animation for the view specified by the first argument.
|
// Start starts the animation for the view specified by the first argument.
|
||||||
// The second argument specifies the animation event listener (can be nil)
|
// The second argument specifies the animation event listener (can be nil)
|
||||||
Start(view View, listener func(view View, animation Animation, event string)) bool
|
Start(view View, listener func(view View, animation AnimationProperty, event PropertyName)) bool
|
||||||
// Stop stops the animation
|
// Stop stops the animation
|
||||||
Stop()
|
Stop()
|
||||||
// Pause pauses the animation
|
// Pause pauses the animation
|
||||||
|
|
@ -215,7 +230,7 @@ type Animation interface {
|
||||||
// Resume resumes an animation that was stopped using the Pause method
|
// Resume resumes an animation that was stopped using the Pause method
|
||||||
Resume()
|
Resume()
|
||||||
|
|
||||||
writeTransitionString(tag string, buffer *strings.Builder)
|
writeTransitionString(tag PropertyName, buffer *strings.Builder)
|
||||||
animationCSS(session Session) string
|
animationCSS(session Session) string
|
||||||
transitionCSS(buffer *strings.Builder, session Session)
|
transitionCSS(buffer *strings.Builder, session Session)
|
||||||
hasAnimatedProperty() bool
|
hasAnimatedProperty() bool
|
||||||
|
|
@ -224,24 +239,36 @@ type Animation interface {
|
||||||
unused(session Session)
|
unused(session Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAnimation(obj DataObject) Animation {
|
func parseAnimation(obj DataObject) AnimationProperty {
|
||||||
animation := new(animationData)
|
animation := new(animationData)
|
||||||
animation.init()
|
animation.init()
|
||||||
|
|
||||||
for i := 0; i < obj.PropertyCount(); i++ {
|
for i := 0; i < obj.PropertyCount(); i++ {
|
||||||
if node := obj.Property(i); node != nil {
|
if node := obj.Property(i); node != nil {
|
||||||
|
tag := PropertyName(node.Tag())
|
||||||
if node.Type() == TextNode {
|
if node.Type() == TextNode {
|
||||||
animation.Set(node.Tag(), node.Text())
|
animation.Set(tag, node.Text())
|
||||||
} else {
|
} else {
|
||||||
animation.Set(node.Tag(), node)
|
animation.Set(tag, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAnimation creates a new animation object and return its interface
|
// NewAnimationProperty creates a new animation object and return its interface
|
||||||
func NewAnimation(params Params) Animation {
|
//
|
||||||
|
// The following properties can be used:
|
||||||
|
// - "id" (ID) - specifies the animation identifier. Used only for animation script.
|
||||||
|
// - "duration" (Duration) - specifies the length of time in seconds that an animation takes to complete one cycle;
|
||||||
|
// - "delay" (Delay) - specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
|
||||||
|
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the animation;
|
||||||
|
// - "timing-function" (TimingFunction) - specifies how an animation progresses through the duration of each cycle;
|
||||||
|
// - "iteration-count" (IterationCount) - specifies the number of times an animation sequence should be played before stopping. Used only for animation script;
|
||||||
|
// - "animation-direction" (AnimationDirection) - specifies whether an animation should play forward, backward,
|
||||||
|
// or alternate back and forth between playing the sequence forward and backward. Used only for animation script;
|
||||||
|
// - "property" (PropertyTag) - describes a scenario for changing a View's property. Used only for animation script.
|
||||||
|
func NewAnimationProperty(params Params) AnimationProperty {
|
||||||
animation := new(animationData)
|
animation := new(animationData)
|
||||||
animation.init()
|
animation.init()
|
||||||
|
|
||||||
|
|
@ -251,6 +278,97 @@ func NewAnimation(params Params) Animation {
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTransitionAnimation creates animation data for the transition.
|
||||||
|
// - timingFunc - specifies how an animation progresses through the duration of each cycle. If it is "" then "easy" function is used;
|
||||||
|
// - duration - specifies the length of time in seconds that an animation takes to complete one cycle. Must be > 0;
|
||||||
|
// - delay - specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
|
||||||
|
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the animation.
|
||||||
|
func NewTransitionAnimation(timingFunc string, duration float64, delay float64) AnimationProperty {
|
||||||
|
animation := new(animationData)
|
||||||
|
animation.init()
|
||||||
|
|
||||||
|
if duration <= 0 {
|
||||||
|
ErrorLog("Animation duration must be greater than 0")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !animation.Set(Duration, duration) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if timingFunc != "" {
|
||||||
|
if !animation.Set(TimingFunction, timingFunc) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay != 0 {
|
||||||
|
animation.Set(Delay, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return animation
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransitionAnimation creates the animation scenario.
|
||||||
|
// - id - specifies the animation identifier.
|
||||||
|
// - timingFunc - specifies how an animation progresses through the duration of each cycle. If it is "" then "easy" function is used;
|
||||||
|
// - duration - specifies the length of time in seconds that an animation takes to complete one cycle. Must be > 0;
|
||||||
|
// - delay - specifies the amount of time in seconds to wait from applying the animation to an element before beginning to perform
|
||||||
|
// the animation. The animation can start later, immediately from its beginning or immediately and partway through the animation.
|
||||||
|
// - direction - specifies whether an animation should play forward, backward,
|
||||||
|
// or alternate back and forth between playing the sequence forward and backward. Only the following values can be used:
|
||||||
|
// 0 (NormalAnimation), 1 (ReverseAnimation), 2 (AlternateAnimation), and 3 (AlternateReverseAnimation);
|
||||||
|
// - iterationCount - specifies the number of times an animation sequence should be played before stopping. A negative value specifies infinite repetition;
|
||||||
|
// - property, properties - describes a scenario for changing a View's property.
|
||||||
|
func NewAnimation(id string, timingFunc string, duration float64, delay float64, direction int, iterationCount int, property AnimatedProperty, properties ...AnimatedProperty) AnimationProperty {
|
||||||
|
animation := new(animationData)
|
||||||
|
animation.init()
|
||||||
|
|
||||||
|
if duration <= 0 {
|
||||||
|
ErrorLog("Animation duration must be greater than 0")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !animation.Set(Duration, duration) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
animation.Set(ID, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timingFunc != "" {
|
||||||
|
animation.Set(TimingFunction, timingFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if delay != 0 {
|
||||||
|
animation.Set(Delay, delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
if direction > 0 {
|
||||||
|
animation.Set(AnimationDirection, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if iterationCount != 0 {
|
||||||
|
animation.Set(IterationCount, iterationCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(properties) > 0 {
|
||||||
|
animation.Set(PropertyTag, append([]AnimatedProperty{property}, properties...))
|
||||||
|
} else {
|
||||||
|
animation.Set(PropertyTag, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
return animation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (animation *animationData) init() {
|
||||||
|
animation.dataProperty.init()
|
||||||
|
animation.normalize = normalizeAnimation
|
||||||
|
animation.set = animationSet
|
||||||
|
animation.supportedProperties = []PropertyName{ID, PropertyTag, Duration, Delay, TimingFunction, IterationCount, AnimationDirection}
|
||||||
|
}
|
||||||
|
|
||||||
func (animation *animationData) animatedProperties() []AnimatedProperty {
|
func (animation *animationData) animatedProperties() []AnimatedProperty {
|
||||||
value := animation.getRaw(PropertyTag)
|
value := animation.getRaw(PropertyTag)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
|
@ -291,33 +409,28 @@ func (animation *animationData) unused(session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) normalizeTag(tag string) string {
|
func normalizeAnimation(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
if tag == Direction {
|
if tag == Direction {
|
||||||
return AnimationDirection
|
return AnimationDirection
|
||||||
}
|
}
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) Set(tag string, value any) bool {
|
func animationSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if value == nil {
|
switch tag {
|
||||||
animation.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag = animation.normalizeTag(tag); tag {
|
|
||||||
case ID:
|
case ID:
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
text = strings.Trim(text, " \t\n\r")
|
text = strings.Trim(text, " \t\n\r")
|
||||||
if text == "" {
|
if text == "" {
|
||||||
delete(animation.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
} else {
|
} else {
|
||||||
animation.properties[tag] = text
|
properties.setRaw(tag, text)
|
||||||
}
|
}
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
|
|
||||||
case PropertyTag:
|
case PropertyTag:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -340,8 +453,8 @@ func (animation *animationData) Set(tag string, value any) bool {
|
||||||
} else if value.To == nil {
|
} else if value.To == nil {
|
||||||
ErrorLog("AnimatedProperty.To is nil")
|
ErrorLog("AnimatedProperty.To is nil")
|
||||||
} else {
|
} else {
|
||||||
animation.properties[tag] = []AnimatedProperty{value}
|
properties.setRaw(tag, []AnimatedProperty{value})
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
case []AnimatedProperty:
|
case []AnimatedProperty:
|
||||||
|
|
@ -369,8 +482,8 @@ func (animation *animationData) Set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(props) > 0 {
|
if len(props) > 0 {
|
||||||
animation.properties[tag] = props
|
properties.setRaw(tag, props)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
} else {
|
} else {
|
||||||
ErrorLog("[]AnimatedProperty is empty")
|
ErrorLog("[]AnimatedProperty is empty")
|
||||||
}
|
}
|
||||||
|
|
@ -417,8 +530,8 @@ func (animation *animationData) Set(tag string, value any) bool {
|
||||||
switch value.Type() {
|
switch value.Type() {
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
if prop, ok := parseObject(value.Object()); ok {
|
if prop, ok := parseObject(value.Object()); ok {
|
||||||
animation.properties[tag] = []AnimatedProperty{prop}
|
properties.setRaw(tag, []AnimatedProperty{prop})
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ArrayNode:
|
case ArrayNode:
|
||||||
|
|
@ -433,8 +546,8 @@ func (animation *animationData) Set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(props) > 0 {
|
if len(props) > 0 {
|
||||||
animation.properties[tag] = props
|
properties.setRaw(tag, props)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -446,36 +559,28 @@ func (animation *animationData) Set(tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Duration:
|
case Duration:
|
||||||
return animation.setFloatProperty(tag, value, 0, math.MaxFloat64)
|
return setFloatProperty(properties, tag, value, 0, math.MaxFloat64)
|
||||||
|
|
||||||
case Delay:
|
case Delay:
|
||||||
return animation.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64)
|
return setFloatProperty(properties, tag, value, -math.MaxFloat64, math.MaxFloat64)
|
||||||
|
|
||||||
case TimingFunction:
|
case TimingFunction:
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
animation.properties[tag] = text
|
properties.setRaw(tag, text)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
case IterationCount:
|
case IterationCount:
|
||||||
return animation.setIntProperty(tag, value)
|
return setIntProperty(properties, tag, value)
|
||||||
|
|
||||||
case AnimationDirection:
|
case AnimationDirection:
|
||||||
return animation.setEnumProperty(AnimationDirection, value, enumProperties[AnimationDirection].values)
|
return setEnumProperty(properties, AnimationDirection, value, enumProperties[AnimationDirection].values)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ErrorLogF(`The "%s" property is not supported by Animation`, tag)
|
ErrorLogF(`The "%s" property is not supported by Animation`, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func (animation *animationData) Remove(tag string) {
|
|
||||||
delete(animation.properties, animation.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (animation *animationData) Get(tag string) any {
|
|
||||||
return animation.getRaw(animation.normalizeTag(tag))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) String() string {
|
func (animation *animationData) String() string {
|
||||||
|
|
@ -488,7 +593,7 @@ func (animation *animationData) String() string {
|
||||||
if tag != PropertyTag {
|
if tag != PropertyTag {
|
||||||
if value, ok := animation.properties[tag]; ok && value != nil {
|
if value, ok := animation.properties[tag]; ok && value != nil {
|
||||||
buffer.WriteString("\n\t")
|
buffer.WriteString("\n\t")
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, "\t")
|
writePropertyValue(buffer, tag, value, "\t")
|
||||||
buffer.WriteRune(',')
|
buffer.WriteRune(',')
|
||||||
|
|
@ -497,7 +602,7 @@ func (animation *animationData) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
writeProperty := func(prop AnimatedProperty, indent string) {
|
writeProperty := func(prop AnimatedProperty, indent string) {
|
||||||
buffer.WriteString(prop.Tag)
|
buffer.WriteString(string(prop.Tag))
|
||||||
buffer.WriteString("{\n")
|
buffer.WriteString("{\n")
|
||||||
buffer.WriteString(indent)
|
buffer.WriteString(indent)
|
||||||
buffer.WriteString("from = ")
|
buffer.WriteString("from = ")
|
||||||
|
|
@ -512,7 +617,7 @@ func (animation *animationData) String() string {
|
||||||
tag := strconv.Itoa(key) + "%"
|
tag := strconv.Itoa(key) + "%"
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(tag)
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
writePropertyValue(buffer, PropertyName(tag), value, indent)
|
||||||
}
|
}
|
||||||
buffer.WriteString("\n")
|
buffer.WriteString("\n")
|
||||||
buffer.WriteString(indent[1:])
|
buffer.WriteString(indent[1:])
|
||||||
|
|
@ -522,7 +627,7 @@ func (animation *animationData) String() string {
|
||||||
if props := animation.animatedProperties(); len(props) > 0 {
|
if props := animation.animatedProperties(); len(props) > 0 {
|
||||||
|
|
||||||
buffer.WriteString("\n\t")
|
buffer.WriteString("\n\t")
|
||||||
buffer.WriteString(PropertyTag)
|
buffer.WriteString(string(PropertyTag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
if len(props) > 1 {
|
if len(props) > 1 {
|
||||||
buffer.WriteString("[\n")
|
buffer.WriteString("[\n")
|
||||||
|
|
@ -561,7 +666,7 @@ func (animation *animationData) animationCSS(session Session) string {
|
||||||
buffer.WriteString(" 1s ")
|
buffer.WriteString(" 1s ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(animation.timingFunctionCSS(session))
|
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
|
||||||
|
|
||||||
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
||||||
|
|
@ -599,22 +704,22 @@ func (animation *animationData) transitionCSS(buffer *strings.Builder, session S
|
||||||
buffer.WriteString(" 1s ")
|
buffer.WriteString(" 1s ")
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(animation.timingFunctionCSS(session))
|
buffer.WriteString(timingFunctionCSS(animation, TimingFunction, session))
|
||||||
|
|
||||||
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 {
|
||||||
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
buffer.WriteString(fmt.Sprintf(" %gs", delay))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) writeTransitionString(tag string, buffer *strings.Builder) {
|
func (animation *animationData) writeTransitionString(tag PropertyName, buffer *strings.Builder) {
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString("{")
|
buffer.WriteString("{")
|
||||||
lead := " "
|
lead := " "
|
||||||
|
|
||||||
writeFloatProperty := func(name string) bool {
|
writeFloatProperty := func(name PropertyName) bool {
|
||||||
if value := animation.getRaw(name); value != nil {
|
if value := animation.getRaw(name); value != nil {
|
||||||
buffer.WriteString(lead)
|
buffer.WriteString(lead)
|
||||||
buffer.WriteString(name)
|
buffer.WriteString(string(name))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, name, value, "")
|
writePropertyValue(buffer, name, value, "")
|
||||||
lead = ", "
|
lead = ", "
|
||||||
|
|
@ -633,7 +738,7 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string
|
||||||
if value := animation.getRaw(TimingFunction); value != nil {
|
if value := animation.getRaw(TimingFunction); value != nil {
|
||||||
if timingFunction, ok := value.(string); ok && timingFunction != "" {
|
if timingFunction, ok := value.(string); ok && timingFunction != "" {
|
||||||
buffer.WriteString(lead)
|
buffer.WriteString(lead)
|
||||||
buffer.WriteString(TimingFunction)
|
buffer.WriteString(string(TimingFunction))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
if strings.ContainsAny(timingFunction, " ,()") {
|
if strings.ContainsAny(timingFunction, " ,()") {
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
|
|
@ -648,8 +753,8 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string
|
||||||
buffer.WriteString(" }")
|
buffer.WriteString(" }")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) timingFunctionCSS(session Session) string {
|
func timingFunctionCSS(properties Properties, tag PropertyName, session Session) string {
|
||||||
if timingFunction, ok := stringProperty(animation, TimingFunction, session); ok {
|
if timingFunction, ok := stringProperty(properties, tag, session); ok {
|
||||||
if timingFunction, ok = session.resolveConstants(timingFunction); ok && isTimingFunctionValid(timingFunction) {
|
if timingFunction, ok = session.resolveConstants(timingFunction); ok && isTimingFunctionValid(timingFunction) {
|
||||||
return timingFunction
|
return timingFunction
|
||||||
}
|
}
|
||||||
|
|
@ -767,7 +872,7 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) SetAnimated(tag string, value any, animation Animation) bool {
|
func (view *viewData) SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool {
|
||||||
if animation == nil {
|
if animation == nil {
|
||||||
return view.Set(tag, value)
|
return view.Set(tag, value)
|
||||||
}
|
}
|
||||||
|
|
@ -779,28 +884,31 @@ func (view *viewData) SetAnimated(tag string, value any, animation Animation) bo
|
||||||
session.updateProperty(htmlID, "ontransitionend", "transitionEndEvent(this, event)")
|
session.updateProperty(htmlID, "ontransitionend", "transitionEndEvent(this, event)")
|
||||||
session.updateProperty(htmlID, "ontransitioncancel", "transitionCancelEvent(this, event)")
|
session.updateProperty(htmlID, "ontransitioncancel", "transitionCancelEvent(this, event)")
|
||||||
|
|
||||||
if prevAnimation, ok := view.transitions[tag]; ok {
|
transitions := getTransitionProperty(view)
|
||||||
view.singleTransition[tag] = prevAnimation
|
var prevAnimation AnimationProperty = nil
|
||||||
} else {
|
if transitions != nil {
|
||||||
view.singleTransition[tag] = nil
|
if prev, ok := transitions[tag]; ok {
|
||||||
|
prevAnimation = prev
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.transitions[tag] = animation
|
view.singleTransition[tag] = prevAnimation
|
||||||
view.updateTransitionCSS()
|
setTransition(view, tag, animation)
|
||||||
|
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
|
||||||
|
|
||||||
session.finishUpdateScript(htmlID)
|
session.finishUpdateScript(htmlID)
|
||||||
|
|
||||||
result := view.Set(tag, value)
|
result := view.Set(tag, value)
|
||||||
if !result {
|
if !result {
|
||||||
delete(view.singleTransition, tag)
|
delete(view.singleTransition, tag)
|
||||||
view.updateTransitionCSS()
|
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) animationCSS(session Session) string {
|
func animationCSS(properties Properties, session Session) string {
|
||||||
if value := style.getRaw(AnimationTag); value != nil {
|
if value := properties.getRaw(Animation); value != nil {
|
||||||
if animations, ok := value.([]Animation); ok {
|
if animations, ok := value.([]AnimationProperty); ok {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
|
@ -820,78 +928,154 @@ func (style *viewStyle) animationCSS(session Session) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) transitionCSS(session Session) string {
|
func transitionCSS(properties Properties, session Session) string {
|
||||||
buffer := allocStringBuilder()
|
if transitions := getTransitionProperty(properties); len(transitions) > 0 {
|
||||||
defer freeStringBuilder(buffer)
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
convert := map[string]string{
|
convert := map[PropertyName]string{
|
||||||
CellHeight: "grid-template-rows",
|
CellHeight: "grid-template-rows",
|
||||||
CellWidth: "grid-template-columns",
|
CellWidth: "grid-template-columns",
|
||||||
Row: "grid-row",
|
Row: "grid-row",
|
||||||
Column: "grid-column",
|
Column: "grid-column",
|
||||||
Clip: "clip-path",
|
Clip: "clip-path",
|
||||||
Shadow: "box-shadow",
|
Shadow: "box-shadow",
|
||||||
ColumnSeparator: "column-rule",
|
ColumnSeparator: "column-rule",
|
||||||
FontName: "font",
|
FontName: "font",
|
||||||
TextSize: "font-size",
|
TextSize: "font-size",
|
||||||
TextLineThickness: "text-decoration-thickness",
|
TextLineThickness: "text-decoration-thickness",
|
||||||
}
|
|
||||||
|
|
||||||
for tag, animation := range style.transitions {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cssTag, ok := convert[tag]; ok {
|
for tag, animation := range transitions {
|
||||||
buffer.WriteString(cssTag)
|
if buffer.Len() > 0 {
|
||||||
} else {
|
buffer.WriteString(", ")
|
||||||
buffer.WriteString(tag)
|
}
|
||||||
|
|
||||||
|
if cssTag, ok := convert[tag]; ok {
|
||||||
|
buffer.WriteString(cssTag)
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
}
|
||||||
|
animation.transitionCSS(buffer, session)
|
||||||
}
|
}
|
||||||
animation.transitionCSS(buffer, session)
|
return buffer.String()
|
||||||
}
|
}
|
||||||
return buffer.String()
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (view *viewData) updateTransitionCSS() {
|
func (view *viewData) updateTransitionCSS() {
|
||||||
view.session.updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.session))
|
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (style *viewStyle) Transition(tag string) Animation {
|
func (style *viewStyle) Transition(tag PropertyName) AnimationProperty {
|
||||||
if style.transitions != nil {
|
if transitions := getTransitionProperty(style); transitions != nil {
|
||||||
if anim, ok := style.transitions[tag]; ok {
|
if anim, ok := transitions[tag]; ok {
|
||||||
return anim
|
return anim
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) Transitions() map[string]Animation {
|
func (style *viewStyle) Transitions() map[PropertyName]AnimationProperty {
|
||||||
result := map[string]Animation{}
|
result := map[PropertyName]AnimationProperty{}
|
||||||
for tag, animation := range style.transitions {
|
for tag, animation := range getTransitionProperty(style) {
|
||||||
result[tag] = animation
|
result[tag] = animation
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) SetTransition(tag string, animation Animation) {
|
func (style *viewStyle) SetTransition(tag PropertyName, animation AnimationProperty) {
|
||||||
if animation == nil {
|
setTransition(style, style.normalize(tag), animation)
|
||||||
delete(style.transitions, tag)
|
}
|
||||||
} else {
|
|
||||||
style.transitions[tag] = animation
|
func (view *viewData) SetTransition(tag PropertyName, animation AnimationProperty) {
|
||||||
|
setTransition(view, view.normalize(tag), animation)
|
||||||
|
if view.created {
|
||||||
|
view.session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, view.session))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) SetTransition(tag string, animation Animation) {
|
func setTransition(properties Properties, tag PropertyName, animation AnimationProperty) {
|
||||||
view.viewStyle.SetTransition(tag, animation)
|
transitions := getTransitionProperty(properties)
|
||||||
if view.created {
|
|
||||||
view.session.updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.session))
|
if animation == nil {
|
||||||
|
if transitions != nil {
|
||||||
|
delete(transitions, tag)
|
||||||
|
if len(transitions) == 0 {
|
||||||
|
properties.setRaw(Transition, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if transitions != nil {
|
||||||
|
transitions[tag] = animation
|
||||||
|
} else {
|
||||||
|
properties.setRaw(Transition, map[PropertyName]AnimationProperty{tag: animation})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTransitionProperty(properties Properties) map[PropertyName]AnimationProperty {
|
||||||
|
if value := properties.getRaw(Transition); value != nil {
|
||||||
|
if transitions, ok := value.(map[PropertyName]AnimationProperty); ok {
|
||||||
|
return transitions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAnimationProperty(properties Properties, tag PropertyName, value any) bool {
|
||||||
|
|
||||||
|
set := func(animations []AnimationProperty) {
|
||||||
|
properties.setRaw(tag, animations)
|
||||||
|
for _, animation := range animations {
|
||||||
|
animation.used()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case AnimationProperty:
|
||||||
|
set([]AnimationProperty{value})
|
||||||
|
return true
|
||||||
|
|
||||||
|
case []AnimationProperty:
|
||||||
|
set(value)
|
||||||
|
return true
|
||||||
|
|
||||||
|
case DataObject:
|
||||||
|
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
|
||||||
|
set([]AnimationProperty{animation})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataNode:
|
||||||
|
animations := []AnimationProperty{}
|
||||||
|
result := true
|
||||||
|
for i := 0; i < value.ArraySize(); i++ {
|
||||||
|
if obj := value.ArrayElement(i).Object(); obj != nil {
|
||||||
|
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
|
||||||
|
animations = append(animations, anim)
|
||||||
|
} else {
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notCompatibleType(tag, value.ArrayElement(i))
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result && len(animations) > 0 {
|
||||||
|
set(animations)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// SetAnimated sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
|
// SetAnimated sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
|
||||||
// true - success,
|
// true - success,
|
||||||
// false - error (incompatible type or invalid format of a string value, see AppLog).
|
// false - error (incompatible type or invalid format of a string value, see AppLog).
|
||||||
func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool {
|
func SetAnimated(rootView View, viewID string, tag PropertyName, value any, animation AnimationProperty) bool {
|
||||||
if view := ViewByID(rootView, viewID); view != nil {
|
if view := ViewByID(rootView, viewID); view != nil {
|
||||||
return view.SetAnimated(tag, value, animation)
|
return view.SetAnimated(tag, value, animation)
|
||||||
}
|
}
|
||||||
|
|
@ -906,21 +1090,17 @@ func IsAnimationPaused(view View, subviewID ...string) bool {
|
||||||
|
|
||||||
// GetTransitions returns the subview transitions. The result is always non-nil.
|
// GetTransitions returns the subview transitions. The result is always non-nil.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
||||||
func GetTransitions(view View, subviewID ...string) map[string]Animation {
|
func GetTransitions(view View, subviewID ...string) map[PropertyName]AnimationProperty {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
return view.Transitions()
|
return view.Transitions()
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]Animation{}
|
return map[PropertyName]AnimationProperty{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransition returns the subview property transition. If there is no transition for the given property then nil is returned.
|
// GetTransition returns the subview property transition. If there is no transition for the given property then nil is returned.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
||||||
func GetTransition(view View, subviewID, tag string) Animation {
|
func GetTransition(view View, subviewID string, tag PropertyName) AnimationProperty {
|
||||||
if subviewID != "" {
|
if subviewID != "" {
|
||||||
view = ViewByID(view, subviewID)
|
view = ViewByID(view, subviewID)
|
||||||
}
|
}
|
||||||
|
|
@ -934,7 +1114,7 @@ func GetTransition(view View, subviewID, tag string) Animation {
|
||||||
|
|
||||||
// AddTransition adds the transition for the subview property.
|
// AddTransition adds the transition for the subview property.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then the transition is added to the first argument (view)
|
// If the second argument (subviewID) is not specified or it is "" then the transition is added to the first argument (view)
|
||||||
func AddTransition(view View, subviewID, tag string, animation Animation) bool {
|
func AddTransition(view View, subviewID string, tag PropertyName, animation AnimationProperty) bool {
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
if subviewID != "" {
|
if subviewID != "" {
|
||||||
view = ViewByID(view, subviewID)
|
view = ViewByID(view, subviewID)
|
||||||
|
|
@ -950,18 +1130,14 @@ func AddTransition(view View, subviewID, tag string, animation Animation) bool {
|
||||||
|
|
||||||
// GetAnimation returns the subview animations. The result is always non-nil.
|
// GetAnimation returns the subview animations. The result is always non-nil.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then transitions of the first argument (view) is returned
|
||||||
func GetAnimation(view View, subviewID ...string) []Animation {
|
func GetAnimation(view View, subviewID ...string) []AnimationProperty {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if value := view.getRaw(Animation); value != nil {
|
||||||
}
|
if animations, ok := value.([]AnimationProperty); ok && animations != nil {
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.getRaw(AnimationTag); value != nil {
|
|
||||||
if animations, ok := value.([]Animation); ok && animations != nil {
|
|
||||||
return animations
|
return animations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []Animation{}
|
return []AnimationProperty{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,282 +1,259 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Constants which describe values for view's animation events properties
|
// Constants which describe values for view's animation events properties
|
||||||
const (
|
const (
|
||||||
// TransitionRunEvent is the constant for "transition-run-event" property tag.
|
// TransitionRunEvent is the constant for "transition-run-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Is fired when a transition is first created, i.e. before any transition delay has begun.
|
// Is fired when a transition is first created, i.e. before any transition delay has begun.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, propertyName string)`.
|
// func(view rui.View, propertyName rui.PropertyName).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// propertyName - Name of the property.
|
// - propertyName - Name of the property.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View),
|
||||||
// `func(propertyName string)`,
|
// func(propertyName rui.PropertyName)
|
||||||
// `func()`.
|
// func().
|
||||||
TransitionRunEvent = "transition-run-event"
|
TransitionRunEvent PropertyName = "transition-run-event"
|
||||||
|
|
||||||
// TransitionStartEvent is the constant for "transition-start-event" property tag.
|
// TransitionStartEvent is the constant for "transition-start-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Is fired when a transition has actually started, i.e., after "delay" has ended.
|
// Is fired when a transition has actually started, i.e., after "delay" has ended.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, propertyName string)`.
|
// func(view rui.View, propertyName rui.PropertyName).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// propertyName - Name of the property.
|
// - propertyName - Name of the property.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(propertyName string)`,
|
// func(propertyName rui.PropertyName)
|
||||||
// `func()`.
|
// func()
|
||||||
TransitionStartEvent = "transition-start-event"
|
TransitionStartEvent PropertyName = "transition-start-event"
|
||||||
|
|
||||||
// TransitionEndEvent is the constant for "transition-end-event" property tag.
|
// TransitionEndEvent is the constant for "transition-end-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Is fired when a transition has completed.
|
// Is fired when a transition has completed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, propertyName string)`.
|
// func(view rui.View, propertyName rui.PropertyName).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// propertyName - Name of the property.
|
// - propertyName - Name of the property.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(propertyName string)`,
|
// func(propertyName rui.PropertyName)
|
||||||
// `func()`.
|
// func()
|
||||||
TransitionEndEvent = "transition-end-event"
|
TransitionEndEvent PropertyName = "transition-end-event"
|
||||||
|
|
||||||
// TransitionCancelEvent is the constant for "transition-cancel-event" property tag.
|
// TransitionCancelEvent is the constant for "transition-cancel-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Is fired when a transition is cancelled. The transition is cancelled when: * A new property transition has begun. * The
|
// Is fired when a transition is cancelled. The transition is cancelled when:
|
||||||
// "visibility" property is set to "gone". * The transition is stopped before it has run to completion, e.g. by moving the
|
// - A new property transition has begun.
|
||||||
// mouse off a hover-transitioning view.
|
// - The "visibility" property is set to "gone".
|
||||||
|
// - The transition is stopped before it has run to completion, e.g. by moving the mouse off a hover-transitioning view.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, propertyName string)`.
|
// func(view rui.View, propertyName rui.PropertyName).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// propertyName - Name of the property.
|
// - propertyName - Name of the property.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(propertyName string)`,
|
// func(propertyName rui.PropertyName)
|
||||||
// `func()`.
|
// func()
|
||||||
TransitionCancelEvent = "transition-cancel-event"
|
TransitionCancelEvent PropertyName = "transition-cancel-event"
|
||||||
|
|
||||||
// AnimationStartEvent is the constant for "animation-start-event" property tag.
|
// AnimationStartEvent is the constant for "animation-start-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Fired when an animation has started. If there is an "animation-delay", this event will fire once the delay period has
|
// Fired when an animation has started. If there is an "animation-delay", this event will fire once the delay period has
|
||||||
// expired.
|
// expired.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, animationId string)`.
|
// func(view rui.View, animationId string).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// animationId - Id of the animation.
|
// - animationId - Id of the animation.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(animationId string)`,
|
// func(animationId string)
|
||||||
// `func()`.
|
// func()
|
||||||
AnimationStartEvent = "animation-start-event"
|
AnimationStartEvent PropertyName = "animation-start-event"
|
||||||
|
|
||||||
// AnimationEndEvent is the constant for "animation-end-event" property tag.
|
// AnimationEndEvent is the constant for "animation-end-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Fired when an animation has completed. If the animation aborts before reaching completion, such as if the element is
|
// Fired when an animation has completed. If the animation aborts before reaching completion, such as if the element is
|
||||||
// removed or the animation is removed from the element, the "animation-end-event" is not fired.
|
// removed or the animation is removed from the element, the "animation-end-event" is not fired.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, animationId string)`.
|
// func(view rui.View, animationId string).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// animationId - Id of the animation.
|
// - animationId - Id of the animation.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(animationId string)`,
|
// func(animationId string)
|
||||||
// `func()`.
|
// func()
|
||||||
AnimationEndEvent = "animation-end-event"
|
AnimationEndEvent PropertyName = "animation-end-event"
|
||||||
|
|
||||||
// AnimationCancelEvent is the constant for "animation-cancel-event" property tag.
|
// AnimationCancelEvent is the constant for "animation-cancel-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Fired when an animation unexpectedly aborts. In other words, any time it stops running without sending the
|
// Fired when an animation unexpectedly aborts. In other words, any time it stops running without sending the
|
||||||
// "animation-end-event". This might happen when the animation-name is changed such that the animation is removed, or when
|
// "animation-end-event". This might happen when the animation-name is changed such that the animation is removed, or when
|
||||||
// the animating view is hidden. Therefore, either directly or because any of its containing views are hidden. The event
|
// the animating view is hidden. Therefore, either directly or because any of its containing views are hidden. The event
|
||||||
// is not supported by all browsers.
|
// is not supported by all browsers.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, animationId string)`.
|
// func(view rui.View, animationId string).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// animationId - Id of the animation.
|
// - animationId - Id of the animation.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(animationId string)`,
|
// func(animationId string)
|
||||||
// `func()`.
|
// func()
|
||||||
AnimationCancelEvent = "animation-cancel-event"
|
AnimationCancelEvent PropertyName = "animation-cancel-event"
|
||||||
|
|
||||||
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
|
// AnimationIterationEvent is the constant for "animation-iteration-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View:
|
||||||
// Fired when an iteration of an animation ends, and another one begins. This event does not occur at the same time as the
|
// Fired when an iteration of an animation ends, and another one begins. This event does not occur at the same time as the
|
||||||
// animation end event, and therefore does not occur for animations with an "iteration-count" of one.
|
// animation end event, and therefore does not occur for animations with an "iteration-count" of one.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, animationId string)`.
|
// func(view rui.View, animationId string).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// animationId - Id of the animation.
|
// - animationId - Id of the animation.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
// func(view rui.View)
|
||||||
// `func(animationId string)`,
|
// func(animationId string)
|
||||||
// `func()`.
|
// func()
|
||||||
AnimationIterationEvent = "animation-iteration-event"
|
AnimationIterationEvent PropertyName = "animation-iteration-event"
|
||||||
)
|
)
|
||||||
|
|
||||||
var transitionEvents = map[string]struct{ jsEvent, jsFunc string }{
|
/*
|
||||||
TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"},
|
func setTransitionListener(properties Properties, tag PropertyName, value any) bool {
|
||||||
TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"},
|
if listeners, ok := valueToOneArgEventListeners[View, string](value); ok {
|
||||||
TransitionEndEvent: {jsEvent: "ontransitionend", jsFunc: "transitionEndEvent"},
|
if len(listeners) == 0 {
|
||||||
TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"},
|
properties.setRaw(tag, nil)
|
||||||
}
|
} else {
|
||||||
|
properties.setRaw(tag, listeners)
|
||||||
func (view *viewData) setTransitionListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[View, string](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removeTransitionListener(tag)
|
|
||||||
} else if js, ok := transitionEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
}
|
||||||
} else {
|
return true
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
notCompatibleType(tag, value)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) removeTransitionListener(tag string) {
|
func (view *viewData) removeTransitionListener(tag PropertyName) {
|
||||||
delete(view.properties, tag)
|
delete(view.properties, tag)
|
||||||
if view.created {
|
if view.created {
|
||||||
if js, ok := transitionEvents[tag]; ok {
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionEventsHtml(view View, buffer *strings.Builder) {
|
func transitionEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range transitionEvents {
|
for _, tag := range []PropertyName{TransitionRunEvent, TransitionStartEvent, TransitionEndEvent, TransitionCancelEvent} {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
buffer.WriteString(js.jsEvent)
|
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(`="`)
|
buffer.WriteString(js.jsEvent)
|
||||||
buffer.WriteString(js.jsFunc)
|
buffer.WriteString(`="`)
|
||||||
buffer.WriteString(`(this, event)" `)
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (view *viewData) handleTransitionEvents(tag string, data DataObject) {
|
func (view *viewData) handleTransitionEvents(tag PropertyName, data DataObject) {
|
||||||
if property, ok := data.PropertyValue("property"); ok {
|
if propertyName, ok := data.PropertyValue("property"); ok {
|
||||||
|
property := PropertyName(propertyName)
|
||||||
if tag == TransitionEndEvent || tag == TransitionCancelEvent {
|
if tag == TransitionEndEvent || tag == TransitionCancelEvent {
|
||||||
if animation, ok := view.singleTransition[property]; ok {
|
if animation, ok := view.singleTransition[property]; ok {
|
||||||
delete(view.singleTransition, property)
|
delete(view.singleTransition, property)
|
||||||
if animation != nil {
|
setTransition(view, tag, animation)
|
||||||
view.transitions[property] = animation
|
session := view.session
|
||||||
} else {
|
session.updateCSSProperty(view.htmlID(), "transition", transitionCSS(view, session))
|
||||||
delete(view.transitions, property)
|
|
||||||
}
|
|
||||||
view.updateTransitionCSS()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, listener := range getEventListeners[View, string](view, nil, tag) {
|
for _, listener := range getOneArgEventListeners[View, PropertyName](view, nil, tag) {
|
||||||
listener(view, property)
|
listener(view, property)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var animationEvents = map[string]struct{ jsEvent, jsFunc string }{
|
/*
|
||||||
AnimationStartEvent: {jsEvent: "onanimationstart", jsFunc: "animationStartEvent"},
|
func setAnimationListener(properties Properties, tag PropertyName, value any) bool {
|
||||||
AnimationEndEvent: {jsEvent: "onanimationend", jsFunc: "animationEndEvent"},
|
if listeners, ok := valueToOneArgEventListeners[View, string](value); ok {
|
||||||
AnimationIterationEvent: {jsEvent: "onanimationiteration", jsFunc: "animationIterationEvent"},
|
if len(listeners) == 0 {
|
||||||
AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"},
|
properties.setRaw(tag, nil)
|
||||||
}
|
} else {
|
||||||
|
properties.setRaw(tag, listeners)
|
||||||
func (view *viewData) setAnimationListener(tag string, value any) bool {
|
}
|
||||||
listeners, ok := valueToEventListeners[View, string](value)
|
return true
|
||||||
if !ok {
|
}
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if listeners == nil {
|
func (view *viewData) removeAnimationListener(tag PropertyName) {
|
||||||
view.removeAnimationListener(tag)
|
|
||||||
} else if js, ok := animationEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removeAnimationListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
delete(view.properties, tag)
|
||||||
if view.created {
|
if view.created {
|
||||||
if js, ok := animationEvents[tag]; ok {
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animationEventsHtml(view View, buffer *strings.Builder) {
|
func animationEventsHtml(view View, buffer *strings.Builder) {
|
||||||
for tag, js := range animationEvents {
|
for _, tag := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationIterationEvent, AnimationCancelEvent} {
|
||||||
if value := view.getRaw(tag); value != nil {
|
if value := view.getRaw(tag); value != nil {
|
||||||
if listeners, ok := value.([]func(View)); ok && len(listeners) > 0 {
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
buffer.WriteString(js.jsEvent)
|
if listeners, ok := value.([]func(View, string)); ok && len(listeners) > 0 {
|
||||||
buffer.WriteString(`="`)
|
buffer.WriteString(js.jsEvent)
|
||||||
buffer.WriteString(js.jsFunc)
|
buffer.WriteString(`="`)
|
||||||
buffer.WriteString(`(this, event)" `)
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (view *viewData) handleAnimationEvents(tag string, data DataObject) {
|
func (view *viewData) handleAnimationEvents(tag PropertyName, data DataObject) {
|
||||||
if listeners := getEventListeners[View, string](view, nil, tag); len(listeners) > 0 {
|
if listeners := getOneArgEventListeners[View, string](view, nil, tag); len(listeners) > 0 {
|
||||||
id := ""
|
id := ""
|
||||||
if name, ok := data.PropertyValue("name"); ok {
|
if name, ok := data.PropertyValue("name"); ok {
|
||||||
for _, animation := range GetAnimation(view) {
|
for _, animation := range GetAnimation(view) {
|
||||||
|
|
@ -295,54 +272,54 @@ func (view *viewData) handleAnimationEvents(tag string, data DataObject) {
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTransitionRunListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, TransitionRunEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, TransitionRunEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransitionStartListeners returns the "transition-start-event" listener list.
|
// GetTransitionStartListeners returns the "transition-start-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTransitionStartListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, TransitionStartEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, TransitionStartEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransitionEndListeners returns the "transition-end-event" listener list.
|
// GetTransitionEndListeners returns the "transition-end-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTransitionEndListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, TransitionEndEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, TransitionEndEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransitionCancelListeners returns the "transition-cancel-event" listener list.
|
// GetTransitionCancelListeners returns the "transition-cancel-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTransitionCancelListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, TransitionCancelEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, TransitionCancelEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnimationStartListeners returns the "animation-start-event" listener list.
|
// GetAnimationStartListeners returns the "animation-start-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetAnimationStartListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, AnimationStartEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, AnimationStartEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnimationEndListeners returns the "animation-end-event" listener list.
|
// GetAnimationEndListeners returns the "animation-end-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetAnimationEndListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, AnimationEndEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, AnimationEndEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnimationCancelListeners returns the "animation-cancel-event" listener list.
|
// GetAnimationCancelListeners returns the "animation-cancel-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetAnimationCancelListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, AnimationCancelEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, AnimationCancelEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAnimationIterationListeners returns the "animation-iteration-event" listener list.
|
// GetAnimationIterationListeners returns the "animation-iteration-event" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetAnimationIterationListeners(view View, subviewID ...string) []func(View, string) {
|
||||||
return getEventListeners[View, string](view, subviewID, AnimationIterationEvent)
|
return getOneArgEventListeners[View, string](view, subviewID, AnimationIterationEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
func (animation *animationData) Start(view View, listener func(view View, animation Animation, event string)) bool {
|
func (animation *animationData) Start(view View, listener func(view View, animation AnimationProperty, event PropertyName)) bool {
|
||||||
if view == nil {
|
if view == nil {
|
||||||
ErrorLog("nil View in animation.Start() function")
|
ErrorLog("nil View in animation.Start() function")
|
||||||
return false
|
return false
|
||||||
|
|
@ -13,18 +13,18 @@ func (animation *animationData) Start(view View, listener func(view View, animat
|
||||||
animation.listener = listener
|
animation.listener = listener
|
||||||
|
|
||||||
animation.oldAnimation = nil
|
animation.oldAnimation = nil
|
||||||
if value := view.Get(AnimationTag); value != nil {
|
if value := view.Get(Animation); value != nil {
|
||||||
if oldAnimation, ok := value.([]Animation); ok && len(oldAnimation) > 0 {
|
if oldAnimation, ok := value.([]AnimationProperty); ok && len(oldAnimation) > 0 {
|
||||||
animation.oldAnimation = oldAnimation
|
animation.oldAnimation = oldAnimation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animation.oldListeners = map[string][]func(View, string){}
|
animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
|
||||||
|
|
||||||
setListeners := func(event string, listener func(View, string)) {
|
setListeners := func(event PropertyName, listener func(View, PropertyName)) {
|
||||||
var listeners []func(View, string) = nil
|
var listeners []func(View, PropertyName) = nil
|
||||||
if value := view.Get(event); value != nil {
|
if value := view.Get(event); value != nil {
|
||||||
if oldListeners, ok := value.([]func(View, string)); ok && len(oldListeners) > 0 {
|
if oldListeners, ok := value.([]func(View, PropertyName)); ok && len(oldListeners) > 0 {
|
||||||
listeners = oldListeners
|
listeners = oldListeners
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -42,13 +42,13 @@ func (animation *animationData) Start(view View, listener func(view View, animat
|
||||||
setListeners(AnimationCancelEvent, animation.onAnimationCancel)
|
setListeners(AnimationCancelEvent, animation.onAnimationCancel)
|
||||||
setListeners(AnimationIterationEvent, animation.onAnimationIteration)
|
setListeners(AnimationIterationEvent, animation.onAnimationIteration)
|
||||||
|
|
||||||
view.Set(AnimationTag, animation)
|
view.Set(Animation, animation)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) finish() {
|
func (animation *animationData) finish() {
|
||||||
if animation.view != nil {
|
if animation.view != nil {
|
||||||
for _, event := range []string{AnimationStartEvent, AnimationEndEvent, AnimationCancelEvent, AnimationIterationEvent} {
|
for _, event := range []PropertyName{AnimationStartEvent, AnimationEndEvent, AnimationCancelEvent, AnimationIterationEvent} {
|
||||||
if listeners, ok := animation.oldListeners[event]; ok {
|
if listeners, ok := animation.oldListeners[event]; ok {
|
||||||
animation.view.Set(event, listeners)
|
animation.view.Set(event, listeners)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -57,13 +57,13 @@ func (animation *animationData) finish() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if animation.oldAnimation != nil {
|
if animation.oldAnimation != nil {
|
||||||
animation.view.Set(AnimationTag, animation.oldAnimation)
|
animation.view.Set(Animation, animation.oldAnimation)
|
||||||
animation.oldAnimation = nil
|
animation.oldAnimation = nil
|
||||||
} else {
|
} else {
|
||||||
animation.view.Set(AnimationTag, "")
|
animation.view.Set(Animation, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
animation.oldListeners = map[string][]func(View, string){}
|
animation.oldListeners = map[PropertyName][]func(View, PropertyName){}
|
||||||
|
|
||||||
animation.view = nil
|
animation.view = nil
|
||||||
animation.listener = nil
|
animation.listener = nil
|
||||||
|
|
@ -86,13 +86,13 @@ func (animation *animationData) Resume() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) onAnimationStart(view View, _ string) {
|
func (animation *animationData) onAnimationStart(view View, _ PropertyName) {
|
||||||
if animation.view != nil && animation.listener != nil {
|
if animation.view != nil && animation.listener != nil {
|
||||||
animation.listener(animation.view, animation, AnimationStartEvent)
|
animation.listener(animation.view, animation, AnimationStartEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) onAnimationEnd(view View, _ string) {
|
func (animation *animationData) onAnimationEnd(view View, _ PropertyName) {
|
||||||
if animation.view != nil {
|
if animation.view != nil {
|
||||||
animationView := animation.view
|
animationView := animation.view
|
||||||
listener := animation.listener
|
listener := animation.listener
|
||||||
|
|
@ -112,13 +112,13 @@ func (animation *animationData) onAnimationEnd(view View, _ string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) onAnimationIteration(view View, _ string) {
|
func (animation *animationData) onAnimationIteration(view View, _ PropertyName) {
|
||||||
if animation.view != nil && animation.listener != nil {
|
if animation.view != nil && animation.listener != nil {
|
||||||
animation.listener(animation.view, animation, AnimationIterationEvent)
|
animation.listener(animation.view, animation, AnimationIterationEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (animation *animationData) onAnimationCancel(view View, _ string) {
|
func (animation *animationData) onAnimationCancel(view View, _ PropertyName) {
|
||||||
if animation.view != nil {
|
if animation.view != nil {
|
||||||
animationView := animation.view
|
animationView := animation.view
|
||||||
listener := animation.listener
|
listener := animation.listener
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ func (app *wasmApp) init(params AppParams) {
|
||||||
div := document.Call("createElement", "div")
|
div := document.Call("createElement", "div")
|
||||||
div.Set("className", "ruiRoot")
|
div.Set("className", "ruiRoot")
|
||||||
div.Set("id", "ruiRootView")
|
div.Set("id", "ruiRootView")
|
||||||
viewHTML(app.session.RootView(), buffer)
|
viewHTML(app.session.RootView(), buffer, "")
|
||||||
div.Set("innerHTML", buffer.String())
|
div.Set("innerHTML", buffer.String())
|
||||||
body.Call("appendChild", div)
|
body.Call("appendChild", div)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,21 @@ function appendToInnerHTML(elementId, content) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendToInputValue(elementId, content) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (element) {
|
||||||
|
element.value += content;
|
||||||
|
scanElementsSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeView(elementId) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (element) {
|
||||||
|
element.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setDisabled(elementId, disabled) {
|
function setDisabled(elementId, disabled) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
if (element) {
|
if (element) {
|
||||||
|
|
@ -615,21 +630,11 @@ function listItemClickEvent(element, event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected = false;
|
|
||||||
if (element.classList) {
|
|
||||||
const focusStyle = getListFocusedItemStyle(element);
|
|
||||||
const blurStyle = getListSelectedItemStyle(element);
|
|
||||||
selected = (element.classList.contains(focusStyle) || element.classList.contains(blurStyle));
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = element.parentNode.parentNode
|
const list = element.parentNode.parentNode
|
||||||
if (list) {
|
if (list) {
|
||||||
if (!selected) {
|
const number = getListItemNumber(element.id)
|
||||||
selectListItem(list, element, true)
|
selectListItem(list, element)
|
||||||
}
|
sendMessage("itemClick{session=" + sessionID + ",id=" + list.id + ",number=" + number + "}");
|
||||||
|
|
||||||
const message = "itemClick{session=" + sessionID + ",id=" + list.id + "}"
|
|
||||||
sendMessage(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -656,7 +661,7 @@ function getListSelectedItemStyle(element) {
|
||||||
return getStyleAttribute(element, "data-bluritemstyle", "ruiListItemSelected");
|
return getStyleAttribute(element, "data-bluritemstyle", "ruiListItemSelected");
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectListItem(element, item, needSendMessage) {
|
function selectListItem(element, item) {
|
||||||
const currentId = element.getAttribute("data-current");
|
const currentId = element.getAttribute("data-current");
|
||||||
let message;
|
let message;
|
||||||
const focusStyle = getListFocusedItemStyle(element);
|
const focusStyle = getListFocusedItemStyle(element);
|
||||||
|
|
@ -668,9 +673,7 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
if (current.classList) {
|
if (current.classList) {
|
||||||
current.classList.remove(focusStyle, blurStyle);
|
current.classList.remove(focusStyle, blurStyle);
|
||||||
}
|
}
|
||||||
if (sendMessage) {
|
message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}";
|
||||||
message = "itemUnselected{session=" + sessionID + ",id=" + element.id + "}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -686,11 +689,9 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
element.setAttribute("data-current", item.id);
|
element.setAttribute("data-current", item.id);
|
||||||
if (sendMessage) {
|
const number = getListItemNumber(item.id)
|
||||||
const number = getListItemNumber(item.id)
|
if (number != undefined) {
|
||||||
if (number != undefined) {
|
message = "itemSelected{session=" + sessionID + ",id=" + element.id + ",number=" + number + "}";
|
||||||
message = "itemSelected{session=" + sessionID + ",id=" + element.id + ",number=" + number + "}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.scrollIntoViewIfNeeded) {
|
if (item.scrollIntoViewIfNeeded) {
|
||||||
|
|
@ -698,29 +699,9 @@ function selectListItem(element, item, needSendMessage) {
|
||||||
} else {
|
} else {
|
||||||
item.scrollIntoView({block: "nearest", inline: "nearest"});
|
item.scrollIntoView({block: "nearest", inline: "nearest"});
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
let left = item.offsetLeft - element.offsetLeft;
|
|
||||||
if (left < element.scrollLeft) {
|
|
||||||
element.scrollLeft = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
let top = item.offsetTop - element.offsetTop;
|
|
||||||
if (top < element.scrollTop) {
|
|
||||||
element.scrollTop = top;
|
|
||||||
}
|
|
||||||
|
|
||||||
let right = left + item.offsetWidth;
|
|
||||||
if (right > element.scrollLeft + element.clientWidth) {
|
|
||||||
element.scrollLeft = right - element.clientWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bottom = top + item.offsetHeight
|
|
||||||
if (bottom > element.scrollTop + element.clientHeight) {
|
|
||||||
element.scrollTop = bottom - element.clientHeight;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needSendMessage && message != undefined) {
|
if (message != undefined) {
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
}
|
}
|
||||||
scanElementsSize();
|
scanElementsSize();
|
||||||
|
|
@ -849,7 +830,7 @@ function listViewKeyDownEvent(element, event) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case " ":
|
case " ":
|
||||||
case "Enter":
|
case "Enter":
|
||||||
const message = "itemClick{session=" + sessionID + ",id=" + element.id + "}";
|
const message = "itemClick{session=" + sessionID + ",id=" + element.id + ",number=" + getListItemNumber(currentId) + "}";
|
||||||
sendMessage(message);
|
sendMessage(message);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -889,7 +870,7 @@ function listViewKeyDownEvent(element, event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (item && item !== current) {
|
if (item && item !== current) {
|
||||||
selectListItem(element, item, true);
|
selectListItem(element, item);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
|
@ -908,7 +889,7 @@ function listViewKeyDownEvent(element, event) {
|
||||||
if (item.getAttribute("data-disabled") == "1") {
|
if (item.getAttribute("data-disabled") == "1") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
selectListItem(element, item, true);
|
selectListItem(element, item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ ul:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ruiPopupLayer {
|
.ruiPopupLayer {
|
||||||
background-color: rgba(128,128,128,0.1);
|
/*background-color: rgba(128,128,128,0.1);*/
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
|
@ -181,6 +181,14 @@ ul:focus {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hiddenMarker {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hiddenMarker::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
body {
|
body {
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,12 @@ type audioPlayerData struct {
|
||||||
func NewAudioPlayer(session Session, params Params) AudioPlayer {
|
func NewAudioPlayer(session Session, params Params) AudioPlayer {
|
||||||
view := new(audioPlayerData)
|
view := new(audioPlayerData)
|
||||||
view.init(session)
|
view.init(session)
|
||||||
view.tag = "AudioPlayer"
|
|
||||||
setInitParams(view, params)
|
setInitParams(view, params)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAudioPlayer(session Session) View {
|
func newAudioPlayer(session Session) View {
|
||||||
return NewAudioPlayer(session, nil)
|
return new(audioPlayerData) // NewAudioPlayer(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *audioPlayerData) init(session Session) {
|
func (player *audioPlayerData) init(session Session) {
|
||||||
|
|
@ -27,10 +26,6 @@ func (player *audioPlayerData) init(session Session) {
|
||||||
player.tag = "AudioPlayer"
|
player.tag = "AudioPlayer"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *audioPlayerData) String() string {
|
|
||||||
return getViewString(player, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (player *audioPlayerData) htmlTag() string {
|
func (player *audioPlayerData) htmlTag() string {
|
||||||
return "audio"
|
return "audio"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
432
background.go
432
background.go
|
|
@ -2,64 +2,29 @@ package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants related to view's background description
|
|
||||||
const (
|
const (
|
||||||
// NoRepeat is value of the Repeat property of an background image:
|
// BorderBox is the value of the following properties:
|
||||||
// The image is not repeated (and hence the background image painting area
|
// - BackgroundClip - The background extends to the outside edge of the border (but underneath the border in z-ordering).
|
||||||
// will not necessarily be entirely covered). The position of the non-repeated
|
// - BackgroundOrigin - The background is positioned relative to the border box.
|
||||||
// background image is defined by the background-position CSS property.
|
// - MaskClip - The painted content is clipped to the border box.
|
||||||
NoRepeat = 0
|
// - MaskOrigin - The mask is positioned relative to the border box.
|
||||||
// RepeatXY is value of the Repeat property of an background image:
|
BorderBox = 0
|
||||||
// The image is repeated as much as needed to cover the whole background
|
|
||||||
// image painting area. The last image will be clipped if it doesn't fit.
|
|
||||||
RepeatXY = 1
|
|
||||||
// RepeatX is value of the Repeat property of an background image:
|
|
||||||
// The image is repeated horizontally as much as needed to cover
|
|
||||||
// the whole width background image painting area. The image is not repeated vertically.
|
|
||||||
// The last image will be clipped if it doesn't fit.
|
|
||||||
RepeatX = 2
|
|
||||||
// RepeatY is value of the Repeat property of an background image:
|
|
||||||
// The image is repeated vertically as much as needed to cover
|
|
||||||
// the whole height background image painting area. The image is not repeated horizontally.
|
|
||||||
// The last image will be clipped if it doesn't fit.
|
|
||||||
RepeatY = 3
|
|
||||||
// RepeatRound is value of the Repeat property of an background image:
|
|
||||||
// As the allowed space increases in size, the repeated images will stretch (leaving no gaps)
|
|
||||||
// until there is room (space left >= half of the image width) for another one to be added.
|
|
||||||
// When the next image is added, all of the current ones compress to allow room.
|
|
||||||
RepeatRound = 4
|
|
||||||
// RepeatSpace is value of the Repeat property of an background image:
|
|
||||||
// The image is repeated as much as possible without clipping. The first and last images
|
|
||||||
// are pinned to either side of the element, and whitespace is distributed evenly between the images.
|
|
||||||
RepeatSpace = 5
|
|
||||||
|
|
||||||
// ScrollAttachment is value of the Attachment property of an background image:
|
// PaddingBox is value of the BackgroundClip and MaskClip property:
|
||||||
// The background is fixed relative to the element itself and does not scroll with its contents.
|
// - BackgroundClip - The background extends to the outside edge of the padding. No background is drawn beneath the border.
|
||||||
// (It is effectively attached to the element's border.)
|
// - BackgroundOrigin - The background is positioned relative to the padding box.
|
||||||
ScrollAttachment = 0
|
// - MaskClip - The painted content is clipped to the padding box.
|
||||||
// FixedAttachment is value of the Attachment property of an background image:
|
// - MaskOrigin - The mask is positioned relative to the padding box.
|
||||||
// The background is fixed relative to the viewport. Even if an element has
|
PaddingBox = 1
|
||||||
// a scrolling mechanism, the background doesn't move with the element.
|
|
||||||
FixedAttachment = 1
|
|
||||||
// LocalAttachment is value of the Attachment property of an background image:
|
|
||||||
// The background is fixed relative to the element's contents. If the element has a scrolling mechanism,
|
|
||||||
// the background scrolls with the element's contents, and the background painting area
|
|
||||||
// and background positioning area are relative to the scrollable area of the element
|
|
||||||
// rather than to the border framing them.
|
|
||||||
LocalAttachment = 2
|
|
||||||
|
|
||||||
// BorderBoxClip is value of the BackgroundClip property:
|
// ContentBox is value of the BackgroundClip and MaskClip property:
|
||||||
// The background extends to the outside edge of the border (but underneath the border in z-ordering).
|
// - BackgroundClip - The background is painted within (clipped to) the content box.
|
||||||
BorderBoxClip = 0
|
// - BackgroundOrigin - The background is positioned relative to the content box.
|
||||||
// PaddingBoxClip is value of the BackgroundClip property:
|
// - MaskClip - The painted content is clipped to the content box.
|
||||||
// The background extends to the outside edge of the padding. No background is drawn beneath the border.
|
// - MaskOrigin - The mask is positioned relative to the content box.
|
||||||
PaddingBoxClip = 1
|
ContentBox = 2
|
||||||
// ContentBoxClip is value of the BackgroundClip property:
|
|
||||||
// The background is painted within (clipped to) the content box.
|
|
||||||
ContentBoxClip = 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackgroundElement describes the background element
|
// BackgroundElement describes the background element
|
||||||
|
|
@ -78,37 +43,24 @@ type BackgroundElement interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type backgroundElement struct {
|
type backgroundElement struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
type backgroundImage struct {
|
|
||||||
backgroundElement
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackgroundImage creates the new background image
|
|
||||||
func createBackground(obj DataObject) BackgroundElement {
|
func createBackground(obj DataObject) BackgroundElement {
|
||||||
var result BackgroundElement = nil
|
var result BackgroundElement = nil
|
||||||
|
|
||||||
switch obj.Tag() {
|
switch obj.Tag() {
|
||||||
case "image":
|
case "image":
|
||||||
image := new(backgroundImage)
|
result = NewBackgroundImage(nil)
|
||||||
image.properties = map[string]any{}
|
|
||||||
result = image
|
|
||||||
|
|
||||||
case "linear-gradient":
|
case "linear-gradient":
|
||||||
gradient := new(backgroundLinearGradient)
|
result = NewBackgroundLinearGradient(nil)
|
||||||
gradient.properties = map[string]any{}
|
|
||||||
result = gradient
|
|
||||||
|
|
||||||
case "radial-gradient":
|
case "radial-gradient":
|
||||||
gradient := new(backgroundRadialGradient)
|
result = NewBackgroundRadialGradient(nil)
|
||||||
gradient.properties = map[string]any{}
|
|
||||||
result = gradient
|
|
||||||
|
|
||||||
case "conic-gradient":
|
case "conic-gradient":
|
||||||
gradient := new(backgroundConicGradient)
|
result = NewBackgroundConicGradient(nil)
|
||||||
gradient.properties = map[string]any{}
|
|
||||||
result = gradient
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -118,7 +70,7 @@ func createBackground(obj DataObject) BackgroundElement {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
if node := obj.Property(i); node.Type() == TextNode {
|
if node := obj.Property(i); node.Type() == TextNode {
|
||||||
if value := node.Text(); value != "" {
|
if value := node.Text(); value != "" {
|
||||||
result.Set(node.Tag(), value)
|
result.Set(PropertyName(node.Tag()), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,144 +78,240 @@ func createBackground(obj DataObject) BackgroundElement {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackgroundImage creates the new background image
|
func parseBackgroundValue(value any) []BackgroundElement {
|
||||||
func NewBackgroundImage(params Params) BackgroundElement {
|
|
||||||
result := new(backgroundImage)
|
|
||||||
result.properties = map[string]any{}
|
|
||||||
for tag, value := range params {
|
|
||||||
result.Set(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundImage) Tag() string {
|
switch value := value.(type) {
|
||||||
return "image"
|
case BackgroundElement:
|
||||||
}
|
return []BackgroundElement{value}
|
||||||
|
|
||||||
func (image *backgroundImage) Clone() BackgroundElement {
|
case []BackgroundElement:
|
||||||
result := NewBackgroundImage(nil)
|
return value
|
||||||
for tag, value := range image.properties {
|
|
||||||
result.setRaw(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundImage) normalizeTag(tag string) string {
|
case []DataValue:
|
||||||
tag = strings.ToLower(tag)
|
background := []BackgroundElement{}
|
||||||
switch tag {
|
for _, el := range value {
|
||||||
case "source":
|
if el.IsObject() {
|
||||||
tag = Source
|
if element := createBackground(el.Object()); element != nil {
|
||||||
|
background = append(background, element)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if obj := ParseDataText(el.Value()); obj != nil {
|
||||||
|
if element := createBackground(obj); element != nil {
|
||||||
|
background = append(background, element)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return background
|
||||||
|
|
||||||
case Fit:
|
case DataObject:
|
||||||
tag = backgroundFit
|
if element := createBackground(value); element != nil {
|
||||||
|
return []BackgroundElement{element}
|
||||||
case HorizontalAlign:
|
|
||||||
tag = ImageHorizontalAlign
|
|
||||||
|
|
||||||
case VerticalAlign:
|
|
||||||
tag = ImageVerticalAlign
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundImage) Set(tag string, value any) bool {
|
|
||||||
tag = image.normalizeTag(tag)
|
|
||||||
switch tag {
|
|
||||||
case Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign,
|
|
||||||
backgroundFit, Source:
|
|
||||||
return image.backgroundElement.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundImage) Get(tag string) any {
|
|
||||||
return image.backgroundElement.Get(image.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundImage) cssStyle(session Session) string {
|
|
||||||
if src, ok := imageProperty(image, Source, session); ok && src != "" {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
buffer.WriteString(`url(`)
|
|
||||||
buffer.WriteString(src)
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
|
|
||||||
attachment, _ := enumProperty(image, Attachment, session, NoRepeat)
|
|
||||||
values := enumProperties[Attachment].values
|
|
||||||
if attachment > 0 && attachment < len(values) {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(values[attachment])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
align, _ := enumProperty(image, ImageHorizontalAlign, session, LeftAlign)
|
case []DataObject:
|
||||||
values = enumProperties[ImageHorizontalAlign].values
|
background := []BackgroundElement{}
|
||||||
if align >= 0 && align < len(values) {
|
for _, obj := range value {
|
||||||
buffer.WriteRune(' ')
|
if element := createBackground(obj); element != nil {
|
||||||
buffer.WriteString(values[align])
|
background = append(background, element)
|
||||||
} else {
|
} else {
|
||||||
buffer.WriteString(` left`)
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return background
|
||||||
|
|
||||||
align, _ = enumProperty(image, ImageVerticalAlign, session, TopAlign)
|
case string:
|
||||||
values = enumProperties[ImageVerticalAlign].values
|
if obj := ParseDataText(value); obj != nil {
|
||||||
if align >= 0 && align < len(values) {
|
if element := createBackground(obj); element != nil {
|
||||||
buffer.WriteRune(' ')
|
return []BackgroundElement{element}
|
||||||
buffer.WriteString(values[align])
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(` top`)
|
|
||||||
}
|
|
||||||
|
|
||||||
fit, _ := enumProperty(image, backgroundFit, session, NoneFit)
|
|
||||||
values = enumProperties[backgroundFit].values
|
|
||||||
if fit > 0 && fit < len(values) {
|
|
||||||
|
|
||||||
buffer.WriteString(` / `)
|
|
||||||
buffer.WriteString(values[fit])
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
width, _ := sizeProperty(image, Width, session)
|
|
||||||
height, _ := sizeProperty(image, Height, session)
|
|
||||||
|
|
||||||
if width.Type != Auto || height.Type != Auto {
|
|
||||||
buffer.WriteString(` / `)
|
|
||||||
buffer.WriteString(width.cssString("auto", session))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(height.cssString("auto", session))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repeat, _ := enumProperty(image, Repeat, session, NoRepeat)
|
case []string:
|
||||||
values = enumProperties[Repeat].values
|
elements := make([]BackgroundElement, 0, len(value))
|
||||||
if repeat >= 0 && repeat < len(values) {
|
for _, element := range value {
|
||||||
buffer.WriteRune(' ')
|
if obj := ParseDataText(element); obj != nil {
|
||||||
buffer.WriteString(values[repeat])
|
if element := createBackground(obj); element != nil {
|
||||||
} else {
|
elements = append(elements, element)
|
||||||
buffer.WriteString(` no-repeat`)
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return elements
|
||||||
|
|
||||||
|
case []any:
|
||||||
|
elements := make([]BackgroundElement, 0, len(value))
|
||||||
|
for _, element := range value {
|
||||||
|
switch element := element.(type) {
|
||||||
|
case BackgroundElement:
|
||||||
|
elements = append(elements, element)
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if obj := ParseDataText(element); obj != nil {
|
||||||
|
if element := createBackground(obj); element != nil {
|
||||||
|
elements = append(elements, element)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elements
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBackgroundProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
|
||||||
|
background := parseBackgroundValue(value)
|
||||||
|
if background == nil {
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(background) > 0 {
|
||||||
|
properties.setRaw(tag, background)
|
||||||
|
} else if properties.getRaw(tag) != nil {
|
||||||
|
properties.setRaw(tag, nil)
|
||||||
|
} else {
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundCSS(properties Properties, session Session) string {
|
||||||
|
|
||||||
|
if value := properties.getRaw(Background); value != nil {
|
||||||
|
if backgrounds, ok := value.([]BackgroundElement); ok && len(backgrounds) > 0 {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
for _, background := range backgrounds {
|
||||||
|
if value := background.cssStyle(session); value != "" {
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
backgroundColor, _ := colorProperty(properties, BackgroundColor, session)
|
||||||
|
if backgroundColor != 0 {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(backgroundColor.cssString())
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) {
|
func maskCSS(properties Properties, session Session) string {
|
||||||
image.writeToBuffer(buffer, indent, image.Tag(), []string{
|
|
||||||
Source,
|
if value := properties.getRaw(Mask); value != nil {
|
||||||
Width,
|
if backgrounds, ok := value.([]BackgroundElement); ok && len(backgrounds) > 0 {
|
||||||
Height,
|
buffer := allocStringBuilder()
|
||||||
ImageHorizontalAlign,
|
defer freeStringBuilder(buffer)
|
||||||
ImageVerticalAlign,
|
|
||||||
backgroundFit,
|
for _, background := range backgrounds {
|
||||||
Repeat,
|
if value := background.cssStyle(session); value != "" {
|
||||||
Attachment,
|
if buffer.Len() > 0 {
|
||||||
})
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (image *backgroundImage) String() string {
|
func backgroundStyledPropery(view View, subviewID []string, tag PropertyName) []BackgroundElement {
|
||||||
return runStringWriter(image)
|
var background []BackgroundElement = nil
|
||||||
|
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
if value := view.getRaw(tag); value != nil {
|
||||||
|
if backgrounds, ok := value.([]BackgroundElement); ok {
|
||||||
|
background = backgrounds
|
||||||
|
}
|
||||||
|
} else if value := valueFromStyle(view, tag); value != nil {
|
||||||
|
background = parseBackgroundValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count := len(background); count > 0 {
|
||||||
|
result := make([]BackgroundElement, count)
|
||||||
|
copy(result, background)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return []BackgroundElement{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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.
|
||||||
|
func GetMask(view View, subviewID ...string) []BackgroundElement {
|
||||||
|
return backgroundStyledPropery(view, subviewID, Mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackgroundClip returns a "background-clip" of the subview. Returns one of next values:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func GetBackgroundClip(view View, subviewID ...string) int {
|
||||||
|
return enumStyledProperty(view, subviewID, BackgroundClip, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackgroundOrigin returns a "background-origin" of the subview. Returns one of next values:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func GetBackgroundOrigin(view View, subviewID ...string) int {
|
||||||
|
return enumStyledProperty(view, subviewID, BackgroundOrigin, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaskClip returns a "mask-clip" of the subview. Returns one of next values:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func GetMaskClip(view View, subviewID ...string) int {
|
||||||
|
return enumStyledProperty(view, subviewID, MaskClip, 0, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMaskOrigin returns a "mask-origin" of the subview. Returns one of next values:
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
func GetMaskOrigin(view View, subviewID ...string) int {
|
||||||
|
return enumStyledProperty(view, subviewID, MaskOrigin, 0, false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,16 @@ type BackgroundGradientAngle struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBackgroundConicGradient creates the new background conic gradient
|
// NewBackgroundConicGradient creates the new background conic gradient
|
||||||
|
//
|
||||||
|
// 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 X point of the gradient.
|
||||||
|
// - "center-y" [CenterY] - center Y point of the gradient.
|
||||||
|
// - "from" [From] - start angle position of the gradient.
|
||||||
|
// - "repeating" [Repeating] - Defines whether stop points needs to be repeated after the last one.
|
||||||
func NewBackgroundConicGradient(params Params) BackgroundElement {
|
func NewBackgroundConicGradient(params Params) BackgroundElement {
|
||||||
result := new(backgroundConicGradient)
|
result := new(backgroundConicGradient)
|
||||||
result.properties = map[string]any{}
|
result.init()
|
||||||
for tag, value := range params {
|
for tag, value := range params {
|
||||||
result.Set(tag, value)
|
result.Set(tag, value)
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +55,6 @@ func (point *BackgroundGradientAngle) String() string {
|
||||||
|
|
||||||
case AngleUnit:
|
case AngleUnit:
|
||||||
result += " " + value.String()
|
result += " " + value.String()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +79,11 @@ func (point *BackgroundGradientAngle) color(session Session) (Color, bool) {
|
||||||
|
|
||||||
case Color:
|
case Color:
|
||||||
return color, true
|
return color, true
|
||||||
|
|
||||||
|
default:
|
||||||
|
if n, ok := isInt(color); ok {
|
||||||
|
return Color(n), true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
|
|
@ -115,6 +126,15 @@ func (point *BackgroundGradientAngle) cssString(session Session, buffer *strings
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundConicGradient) init() {
|
||||||
|
gradient.backgroundElement.init()
|
||||||
|
gradient.normalize = normalizeConicGradientTag
|
||||||
|
gradient.set = backgroundConicGradientSet
|
||||||
|
gradient.supportedProperties = []PropertyName{
|
||||||
|
CenterX, CenterY, Repeating, From, Gradient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) Tag() string {
|
func (gradient *backgroundConicGradient) Tag() string {
|
||||||
return "conic-gradient"
|
return "conic-gradient"
|
||||||
}
|
}
|
||||||
|
|
@ -127,8 +147,8 @@ func (image *backgroundConicGradient) Clone() BackgroundElement {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) normalizeTag(tag string) string {
|
func normalizeConicGradientTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case "x-center":
|
case "x-center":
|
||||||
tag = CenterX
|
tag = CenterX
|
||||||
|
|
@ -140,18 +160,50 @@ func (gradient *backgroundConicGradient) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) Set(tag string, value any) bool {
|
func backgroundConicGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
tag = gradient.normalizeTag(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case CenterX, CenterY, Repeating, From:
|
|
||||||
return gradient.propertyList.Set(tag, value)
|
|
||||||
|
|
||||||
case Gradient:
|
case Gradient:
|
||||||
return gradient.setGradient(value)
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if value == "" {
|
||||||
|
return propertiesRemove(properties, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(value, ",") || strings.Contains(value, " ") {
|
||||||
|
if vector := parseGradientText(value); vector != nil {
|
||||||
|
properties.setRaw(Gradient, vector)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
} else if isConstantName(value) {
|
||||||
|
properties.setRaw(Gradient, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`Invalid conic gradient: "%s"`, value)
|
||||||
|
|
||||||
|
case []BackgroundGradientAngle:
|
||||||
|
count := len(value)
|
||||||
|
if count < 2 {
|
||||||
|
ErrorLog("The gradient must contain at least 2 points")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, point := range value {
|
||||||
|
if point.Color == nil {
|
||||||
|
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
properties.setRaw(Gradient, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
default:
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by BackgroundConicGradient`, tag)
|
return propertiesSet(properties, tag, value)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) {
|
func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) {
|
||||||
|
|
@ -216,57 +268,6 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg
|
||||||
}
|
}
|
||||||
return vector
|
return vector
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) setGradient(value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
delete(gradient.properties, Gradient)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if value == "" {
|
|
||||||
delete(gradient.properties, Gradient)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(value, ",") || strings.Contains(value, " ") {
|
|
||||||
if vector := gradient.parseGradientText(value); vector != nil {
|
|
||||||
gradient.properties[Gradient] = vector
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
} else if value[0] == '@' {
|
|
||||||
gradient.properties[Gradient] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`Invalid conic gradient: "%s"`, value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
case []BackgroundGradientAngle:
|
|
||||||
count := len(value)
|
|
||||||
if count < 2 {
|
|
||||||
ErrorLog("The gradient must contain at least 2 points")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, point := range value {
|
|
||||||
if point.Color == nil {
|
|
||||||
ErrorLogF("Invalid %d element of the conic gradient: Color is nil", i)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gradient.properties[Gradient] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) Get(tag string) any {
|
|
||||||
return gradient.backgroundElement.Get(gradient.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) cssStyle(session Session) string {
|
func (gradient *backgroundConicGradient) cssStyle(session Session) string {
|
||||||
|
|
||||||
points := []BackgroundGradientAngle{}
|
points := []BackgroundGradientAngle{}
|
||||||
|
|
@ -339,7 +340,7 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) {
|
func (gradient *backgroundConicGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
|
||||||
Gradient,
|
Gradient,
|
||||||
CenterX,
|
CenterX,
|
||||||
CenterY,
|
CenterY,
|
||||||
|
|
|
||||||
|
|
@ -1,667 +0,0 @@
|
||||||
package rui
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Constants related to view's background gradient description
|
|
||||||
const (
|
|
||||||
|
|
||||||
// ToTopGradient is value of the Direction property of a linear gradient. The value is equivalent to the 0deg angle
|
|
||||||
ToTopGradient = 0
|
|
||||||
// ToRightTopGradient is value of the Direction property of a linear gradient.
|
|
||||||
ToRightTopGradient = 1
|
|
||||||
// ToRightGradient is value of the Direction property of a linear gradient. The value is equivalent to the 90deg angle
|
|
||||||
ToRightGradient = 2
|
|
||||||
// ToRightBottomGradient is value of the Direction property of a linear gradient.
|
|
||||||
ToRightBottomGradient = 3
|
|
||||||
// ToBottomGradient is value of the Direction property of a linear gradient. The value is equivalent to the 180deg angle
|
|
||||||
ToBottomGradient = 4
|
|
||||||
// ToLeftBottomGradient is value of the Direction property of a linear gradient.
|
|
||||||
ToLeftBottomGradient = 5
|
|
||||||
// ToLeftGradient is value of the Direction property of a linear gradient. The value is equivalent to the 270deg angle
|
|
||||||
ToLeftGradient = 6
|
|
||||||
// ToLeftTopGradient is value of the Direction property of a linear gradient.
|
|
||||||
ToLeftTopGradient = 7
|
|
||||||
|
|
||||||
// EllipseGradient is value of the Shape property of a radial gradient background:
|
|
||||||
// the shape is an axis-aligned ellipse
|
|
||||||
EllipseGradient = 0
|
|
||||||
// CircleGradient is value of the Shape property of a radial gradient background:
|
|
||||||
// the gradient's shape is a circle with constant radius
|
|
||||||
CircleGradient = 1
|
|
||||||
|
|
||||||
// ClosestSideGradient is value of the Radius property of a radial gradient background:
|
|
||||||
// The gradient's ending shape meets the side of the box closest to its center (for circles)
|
|
||||||
// or meets both the vertical and horizontal sides closest to the center (for ellipses).
|
|
||||||
ClosestSideGradient = 0
|
|
||||||
// ClosestCornerGradient is value of the Radius property of a radial gradient background:
|
|
||||||
// The gradient's ending shape is sized so that it exactly meets the closest corner
|
|
||||||
// of the box from its center.
|
|
||||||
ClosestCornerGradient = 1
|
|
||||||
// FarthestSideGradient is value of the Radius property of a radial gradient background:
|
|
||||||
// Similar to closest-side, except the ending shape is sized to meet the side of the box
|
|
||||||
// farthest from its center (or vertical and horizontal sides).
|
|
||||||
FarthestSideGradient = 2
|
|
||||||
// FarthestCornerGradient is value of the Radius property of a radial gradient background:
|
|
||||||
// The default value, the gradient's ending shape is sized so that it exactly meets
|
|
||||||
// the farthest corner of the box from its center.
|
|
||||||
FarthestCornerGradient = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// BackgroundGradientPoint define point on gradient straight line
|
|
||||||
type BackgroundGradientPoint struct {
|
|
||||||
// Color - the color of the point. Must not be nil.
|
|
||||||
// Can take a value of Color type or string (color constant or textual description of the color)
|
|
||||||
Color any
|
|
||||||
// Pos - the distance from the start of the gradient straight line. Optional (may be nil).
|
|
||||||
// Can take a value of SizeUnit type or string (angle constant or textual description of the SizeUnit)
|
|
||||||
Pos any
|
|
||||||
}
|
|
||||||
|
|
||||||
type backgroundGradient struct {
|
|
||||||
backgroundElement
|
|
||||||
}
|
|
||||||
|
|
||||||
type backgroundLinearGradient struct {
|
|
||||||
backgroundGradient
|
|
||||||
}
|
|
||||||
|
|
||||||
type backgroundRadialGradient struct {
|
|
||||||
backgroundGradient
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackgroundLinearGradient creates the new background linear gradient
|
|
||||||
func NewBackgroundLinearGradient(params Params) BackgroundElement {
|
|
||||||
result := new(backgroundLinearGradient)
|
|
||||||
result.properties = map[string]any{}
|
|
||||||
for tag, value := range params {
|
|
||||||
result.Set(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackgroundRadialGradient creates the new background radial gradient
|
|
||||||
func NewBackgroundRadialGradient(params Params) BackgroundElement {
|
|
||||||
result := new(backgroundRadialGradient)
|
|
||||||
result.properties = map[string]any{}
|
|
||||||
for tag, value := range params {
|
|
||||||
result.Set(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundGradient) parseGradientText(value string) []BackgroundGradientPoint {
|
|
||||||
elements := strings.Split(value, ",")
|
|
||||||
count := len(elements)
|
|
||||||
if count < 2 {
|
|
||||||
ErrorLog("The gradient must contain at least 2 points")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
points := make([]BackgroundGradientPoint, count)
|
|
||||||
for i, element := range elements {
|
|
||||||
if !points[i].setValue(element) {
|
|
||||||
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundGradient) Set(tag string, value any) bool {
|
|
||||||
|
|
||||||
switch tag = strings.ToLower(tag); tag {
|
|
||||||
case Repeating:
|
|
||||||
return gradient.setBoolProperty(tag, value)
|
|
||||||
|
|
||||||
case Gradient:
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if value != "" {
|
|
||||||
if strings.Contains(value, " ") || strings.Contains(value, ",") {
|
|
||||||
if points := gradient.parseGradientText(value); len(points) >= 2 {
|
|
||||||
gradient.properties[Gradient] = points
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else if value[0] == '@' {
|
|
||||||
gradient.properties[Gradient] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case []BackgroundGradientPoint:
|
|
||||||
if len(value) >= 2 {
|
|
||||||
gradient.properties[Gradient] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case []Color:
|
|
||||||
count := len(value)
|
|
||||||
if count >= 2 {
|
|
||||||
points := make([]BackgroundGradientPoint, count)
|
|
||||||
for i, color := range value {
|
|
||||||
points[i].Color = color
|
|
||||||
}
|
|
||||||
gradient.properties[Gradient] = points
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case []GradientPoint:
|
|
||||||
count := len(value)
|
|
||||||
if count >= 2 {
|
|
||||||
points := make([]BackgroundGradientPoint, count)
|
|
||||||
for i, point := range value {
|
|
||||||
points[i].Color = point.Color
|
|
||||||
points[i].Pos = Percent(point.Offset * 100)
|
|
||||||
}
|
|
||||||
gradient.properties[Gradient] = points
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF("Invalid gradient %v", value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF("Property %s is not supported by a background gradient", tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (point *BackgroundGradientPoint) setValue(text string) bool {
|
|
||||||
text = strings.Trim(text, " ")
|
|
||||||
|
|
||||||
colorText := text
|
|
||||||
pointText := ""
|
|
||||||
|
|
||||||
if index := strings.Index(text, " "); index > 0 {
|
|
||||||
colorText = text[:index]
|
|
||||||
pointText = strings.Trim(text[index+1:], " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
if colorText == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if colorText[0] == '@' {
|
|
||||||
point.Color = colorText
|
|
||||||
} else if color, ok := StringToColor(colorText); ok {
|
|
||||||
point.Color = color
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if pointText == "" {
|
|
||||||
point.Pos = nil
|
|
||||||
} else if pointText[0] == '@' {
|
|
||||||
point.Pos = pointText
|
|
||||||
} else if pos, ok := StringToSizeUnit(pointText); ok {
|
|
||||||
point.Pos = pos
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (point *BackgroundGradientPoint) color(session Session) (Color, bool) {
|
|
||||||
if point.Color != nil {
|
|
||||||
switch color := point.Color.(type) {
|
|
||||||
case string:
|
|
||||||
if color != "" {
|
|
||||||
if color[0] == '@' {
|
|
||||||
if clr, ok := session.Color(color[1:]); ok {
|
|
||||||
return clr, true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if clr, ok := StringToColor(color); ok {
|
|
||||||
return clr, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case Color:
|
|
||||||
return color, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// String convert internal representation of [BackgroundGradientPoint] into a string.
|
|
||||||
func (point *BackgroundGradientPoint) String() string {
|
|
||||||
result := "black"
|
|
||||||
if point.Color != nil {
|
|
||||||
switch color := point.Color.(type) {
|
|
||||||
case string:
|
|
||||||
result = color
|
|
||||||
|
|
||||||
case Color:
|
|
||||||
result = color.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if point.Pos != nil {
|
|
||||||
switch value := point.Pos.(type) {
|
|
||||||
case string:
|
|
||||||
result += " " + value
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
if value.Type != Auto {
|
|
||||||
result += " " + value.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool {
|
|
||||||
|
|
||||||
value, ok := gradient.properties[Gradient]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var points []BackgroundGradientPoint = nil
|
|
||||||
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if value != "" && value[0] == '@' {
|
|
||||||
if text, ok := session.Constant(value[1:]); ok {
|
|
||||||
points = gradient.parseGradientText(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case []BackgroundGradientPoint:
|
|
||||||
points = value
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(points) > 0 {
|
|
||||||
for i, point := range points {
|
|
||||||
if i > 0 {
|
|
||||||
buffer.WriteString(`, `)
|
|
||||||
}
|
|
||||||
|
|
||||||
if color, ok := point.color(session); ok {
|
|
||||||
buffer.WriteString(color.cssString())
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if point.Pos != nil {
|
|
||||||
switch value := point.Pos.(type) {
|
|
||||||
case string:
|
|
||||||
if value != "" {
|
|
||||||
if value, ok := session.resolveConstants(value); ok {
|
|
||||||
if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(pos.cssString("", session))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
if value.Type != Auto {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(value.cssString("", session))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundLinearGradient) Tag() string {
|
|
||||||
return "linear-gradient"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundLinearGradient) Clone() BackgroundElement {
|
|
||||||
result := NewBackgroundLinearGradient(nil)
|
|
||||||
for tag, value := range image.properties {
|
|
||||||
result.setRaw(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundLinearGradient) Set(tag string, value any) bool {
|
|
||||||
if strings.ToLower(tag) == Direction {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case AngleUnit:
|
|
||||||
gradient.properties[Direction] = value
|
|
||||||
return true
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if gradient.setSimpleProperty(tag, value) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if angle, ok := StringToAngleUnit(value); ok {
|
|
||||||
gradient.properties[Direction] = angle
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gradient.setEnumProperty(tag, value, enumProperties[Direction].values)
|
|
||||||
}
|
|
||||||
|
|
||||||
return gradient.backgroundGradient.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundLinearGradient) cssStyle(session Session) string {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
|
||||||
buffer.WriteString(`repeating-linear-gradient(`)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(`linear-gradient(`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := gradient.properties[Direction]; ok {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if text, ok := session.resolveConstants(value); ok {
|
|
||||||
direction := enumProperties[Direction]
|
|
||||||
if n, ok := enumStringToInt(text, direction.values, false); ok {
|
|
||||||
buffer.WriteString(direction.cssValues[n])
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
} else {
|
|
||||||
if angle, ok := StringToAngleUnit(text); ok {
|
|
||||||
buffer.WriteString(angle.cssString())
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
} else {
|
|
||||||
ErrorLog(`Invalid linear gradient direction: ` + text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ErrorLog(`Invalid linear gradient direction: ` + value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case int:
|
|
||||||
values := enumProperties[Direction].cssValues
|
|
||||||
if value >= 0 && value < len(values) {
|
|
||||||
buffer.WriteString(values[value])
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
} else {
|
|
||||||
ErrorLogF(`Invalid linear gradient direction: %d`, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case AngleUnit:
|
|
||||||
buffer.WriteString(value.cssString())
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !gradient.writeGradient(session, buffer) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(") ")
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
|
||||||
Gradient,
|
|
||||||
Repeating,
|
|
||||||
Direction,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundLinearGradient) String() string {
|
|
||||||
return runStringWriter(gradient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) Tag() string {
|
|
||||||
return "radial-gradient"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (image *backgroundRadialGradient) Clone() BackgroundElement {
|
|
||||||
result := NewBackgroundRadialGradient(nil)
|
|
||||||
for tag, value := range image.properties {
|
|
||||||
result.setRaw(tag, value)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
|
||||||
case Radius:
|
|
||||||
tag = RadialGradientRadius
|
|
||||||
|
|
||||||
case Shape:
|
|
||||||
tag = RadialGradientShape
|
|
||||||
|
|
||||||
case "x-center":
|
|
||||||
tag = CenterX
|
|
||||||
|
|
||||||
case "y-center":
|
|
||||||
tag = CenterY
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) Set(tag string, value any) bool {
|
|
||||||
tag = gradient.normalizeTag(tag)
|
|
||||||
switch tag {
|
|
||||||
case RadialGradientRadius:
|
|
||||||
switch value := value.(type) {
|
|
||||||
case []SizeUnit:
|
|
||||||
switch len(value) {
|
|
||||||
case 0:
|
|
||||||
delete(gradient.properties, RadialGradientRadius)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
if value[0].Type == Auto {
|
|
||||||
delete(gradient.properties, RadialGradientRadius)
|
|
||||||
} else {
|
|
||||||
gradient.properties[RadialGradientRadius] = value[0]
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
default:
|
|
||||||
gradient.properties[RadialGradientRadius] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case []any:
|
|
||||||
switch len(value) {
|
|
||||||
case 0:
|
|
||||||
delete(gradient.properties, RadialGradientRadius)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
return gradient.Set(RadialGradientRadius, value[0])
|
|
||||||
|
|
||||||
default:
|
|
||||||
gradient.properties[RadialGradientRadius] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if gradient.setSimpleProperty(RadialGradientRadius, value) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if size, err := stringToSizeUnit(value); err == nil {
|
|
||||||
if size.Type == Auto {
|
|
||||||
delete(gradient.properties, RadialGradientRadius)
|
|
||||||
} else {
|
|
||||||
gradient.properties[RadialGradientRadius] = size
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return gradient.setEnumProperty(RadialGradientRadius, value, enumProperties[RadialGradientRadius].values)
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
if value.Type == Auto {
|
|
||||||
delete(gradient.properties, RadialGradientRadius)
|
|
||||||
} else {
|
|
||||||
gradient.properties[RadialGradientRadius] = value
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
case int:
|
|
||||||
n := value
|
|
||||||
if n >= 0 && n < len(enumProperties[RadialGradientRadius].values) {
|
|
||||||
return gradient.propertyList.Set(RadialGradientRadius, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ErrorLogF(`Invalid value of "%s" property: %v`, tag, value)
|
|
||||||
|
|
||||||
case RadialGradientShape, CenterX, CenterY:
|
|
||||||
return gradient.propertyList.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return gradient.backgroundGradient.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) Get(tag string) any {
|
|
||||||
return gradient.backgroundGradient.Get(gradient.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
|
||||||
buffer.WriteString(`repeating-radial-gradient(`)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(`radial-gradient(`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var shapeText string
|
|
||||||
if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient {
|
|
||||||
shapeText = `circle `
|
|
||||||
} else {
|
|
||||||
shapeText = `ellipse `
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := gradient.properties[RadialGradientRadius]; ok {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if text, ok := session.resolveConstants(value); ok {
|
|
||||||
values := enumProperties[RadialGradientRadius]
|
|
||||||
if n, ok := enumStringToInt(text, values.values, false); ok {
|
|
||||||
buffer.WriteString(shapeText)
|
|
||||||
shapeText = ""
|
|
||||||
buffer.WriteString(values.cssValues[n])
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
} else {
|
|
||||||
if r, ok := StringToSizeUnit(text); ok && r.Type != Auto {
|
|
||||||
buffer.WriteString("ellipse ")
|
|
||||||
shapeText = ""
|
|
||||||
buffer.WriteString(r.cssString("", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
buffer.WriteString(r.cssString("", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
} else {
|
|
||||||
ErrorLog(`Invalid radial gradient radius: ` + text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ErrorLog(`Invalid radial gradient radius: ` + value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case int:
|
|
||||||
values := enumProperties[RadialGradientRadius].cssValues
|
|
||||||
if value >= 0 && value < len(values) {
|
|
||||||
buffer.WriteString(shapeText)
|
|
||||||
shapeText = ""
|
|
||||||
buffer.WriteString(values[value])
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
} else {
|
|
||||||
ErrorLogF(`Invalid radial gradient radius: %d`, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
if value.Type != Auto {
|
|
||||||
buffer.WriteString("ellipse ")
|
|
||||||
shapeText = ""
|
|
||||||
buffer.WriteString(value.cssString("", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
buffer.WriteString(value.cssString("", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
case []SizeUnit:
|
|
||||||
count := len(value)
|
|
||||||
if count > 2 {
|
|
||||||
count = 2
|
|
||||||
}
|
|
||||||
buffer.WriteString("ellipse ")
|
|
||||||
shapeText = ""
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
buffer.WriteString(value[i].cssString("50%", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
case []any:
|
|
||||||
count := len(value)
|
|
||||||
if count > 2 {
|
|
||||||
count = 2
|
|
||||||
}
|
|
||||||
buffer.WriteString("ellipse ")
|
|
||||||
shapeText = ""
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
if value[i] != nil {
|
|
||||||
switch value := value[i].(type) {
|
|
||||||
case SizeUnit:
|
|
||||||
buffer.WriteString(value.cssString("50%", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if text, ok := session.resolveConstants(value); ok {
|
|
||||||
if size, err := stringToSizeUnit(text); err == nil {
|
|
||||||
buffer.WriteString(size.cssString("50%", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString("50% ")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buffer.WriteString("50% ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buffer.WriteString("50% ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x, _ := sizeProperty(gradient, CenterX, session)
|
|
||||||
y, _ := sizeProperty(gradient, CenterX, session)
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
if shapeText != "" {
|
|
||||||
buffer.WriteString(shapeText)
|
|
||||||
}
|
|
||||||
buffer.WriteString("at ")
|
|
||||||
buffer.WriteString(x.cssString("50%", session))
|
|
||||||
buffer.WriteString(" ")
|
|
||||||
buffer.WriteString(y.cssString("50%", session))
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
if !gradient.writeGradient(session, buffer) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(") ")
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []string{
|
|
||||||
Gradient,
|
|
||||||
CenterX,
|
|
||||||
CenterY,
|
|
||||||
Repeating,
|
|
||||||
RadialGradientShape,
|
|
||||||
RadialGradientRadius,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gradient *backgroundRadialGradient) String() string {
|
|
||||||
return runStringWriter(gradient)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,217 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants related to view's background description
|
||||||
|
const (
|
||||||
|
// NoRepeat is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// The image is not repeated (and hence the background image painting area
|
||||||
|
// will not necessarily be entirely covered). The position of the non-repeated
|
||||||
|
// background image is defined by the background-position CSS property.
|
||||||
|
NoRepeat = 0
|
||||||
|
|
||||||
|
// RepeatXY is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// The image is repeated as much as needed to cover the whole background
|
||||||
|
// image painting area. The last image will be clipped if it doesn't fit.
|
||||||
|
RepeatXY = 1
|
||||||
|
|
||||||
|
// RepeatX is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// The image is repeated horizontally as much as needed to cover
|
||||||
|
// the whole width background image painting area. The image is not repeated vertically.
|
||||||
|
// The last image will be clipped if it doesn't fit.
|
||||||
|
RepeatX = 2
|
||||||
|
|
||||||
|
// RepeatY is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// The image is repeated vertically as much as needed to cover
|
||||||
|
// the whole height background image painting area. The image is not repeated horizontally.
|
||||||
|
// The last image will be clipped if it doesn't fit.
|
||||||
|
RepeatY = 3
|
||||||
|
|
||||||
|
// RepeatRound is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// As the allowed space increases in size, the repeated images will stretch (leaving no gaps)
|
||||||
|
// until there is room (space left >= half of the image width) for another one to be added.
|
||||||
|
// When the next image is added, all of the current ones compress to allow room.
|
||||||
|
RepeatRound = 4
|
||||||
|
|
||||||
|
// RepeatSpace is value of the Repeat property of an background image:
|
||||||
|
//
|
||||||
|
// The image is repeated as much as possible without clipping. The first and last images
|
||||||
|
// are pinned to either side of the element, and whitespace is distributed evenly between the images.
|
||||||
|
RepeatSpace = 5
|
||||||
|
|
||||||
|
// ScrollAttachment is value of the Attachment property of an background image:
|
||||||
|
//
|
||||||
|
// The background is fixed relative to the element itself and does not scroll with its contents.
|
||||||
|
// (It is effectively attached to the element's border.)
|
||||||
|
ScrollAttachment = 0
|
||||||
|
|
||||||
|
// FixedAttachment is value of the Attachment property of an background image:
|
||||||
|
//
|
||||||
|
// The background is fixed relative to the viewport. Even if an element has
|
||||||
|
// a scrolling mechanism, the background doesn't move with the element.
|
||||||
|
FixedAttachment = 1
|
||||||
|
|
||||||
|
// LocalAttachment is value of the Attachment property of an background image:
|
||||||
|
//
|
||||||
|
// The background is fixed relative to the element's contents. If the element has a scrolling mechanism,
|
||||||
|
// the background scrolls with the element's contents, and the background painting area
|
||||||
|
// and background positioning area are relative to the scrollable area of the element
|
||||||
|
// rather than to the border framing them.
|
||||||
|
LocalAttachment = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type backgroundImage struct {
|
||||||
|
backgroundElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackgroundImage creates the new background image
|
||||||
|
//
|
||||||
|
// The following properties can be used:
|
||||||
|
// - "src" [Source] - the name of the image in the "images" folder of the resources, or the URL of the image or inline-image.
|
||||||
|
// - "width" [Width] - the width of the image.
|
||||||
|
// - "height" [Height] - the height of the image.
|
||||||
|
// - "image-horizontal-align" [ImageHorizontalAlign] - the horizontal alignment of the image relative to view's bounds.
|
||||||
|
// - "image-vertical-align" [ImageVerticalAlign] - the vertical alignment of the image relative to view's bounds.
|
||||||
|
// - "repeat" [Repeat] - the repetition of the image.
|
||||||
|
// - "fit" [Fit] - the image scaling parameters.
|
||||||
|
// - "attachment" [Attachment] - defines whether a background image's position is fixed within the viewport or scrolls with its containing block.
|
||||||
|
func NewBackgroundImage(params Params) BackgroundElement {
|
||||||
|
result := new(backgroundImage)
|
||||||
|
result.init()
|
||||||
|
for tag, value := range params {
|
||||||
|
result.Set(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) init() {
|
||||||
|
image.backgroundElement.init()
|
||||||
|
image.normalize = normalizeBackgroundImageTag
|
||||||
|
image.supportedProperties = []PropertyName{
|
||||||
|
Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign, backgroundFit, Source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) Tag() string {
|
||||||
|
return "image"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) Clone() BackgroundElement {
|
||||||
|
result := NewBackgroundImage(nil)
|
||||||
|
for tag, value := range image.properties {
|
||||||
|
result.setRaw(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBackgroundImageTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
|
switch tag {
|
||||||
|
case "source":
|
||||||
|
tag = Source
|
||||||
|
|
||||||
|
case Fit:
|
||||||
|
tag = backgroundFit
|
||||||
|
|
||||||
|
case HorizontalAlign:
|
||||||
|
tag = ImageHorizontalAlign
|
||||||
|
|
||||||
|
case VerticalAlign:
|
||||||
|
tag = ImageVerticalAlign
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) cssStyle(session Session) string {
|
||||||
|
if src, ok := imageProperty(image, Source, session); ok && src != "" {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
buffer.WriteString(`url(`)
|
||||||
|
buffer.WriteString(src)
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
|
||||||
|
attachment, _ := enumProperty(image, Attachment, session, NoRepeat)
|
||||||
|
values := enumProperties[Attachment].values
|
||||||
|
if attachment > 0 && attachment < len(values) {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(values[attachment])
|
||||||
|
}
|
||||||
|
|
||||||
|
align, _ := enumProperty(image, ImageHorizontalAlign, session, LeftAlign)
|
||||||
|
values = enumProperties[ImageHorizontalAlign].values
|
||||||
|
if align >= 0 && align < len(values) {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(values[align])
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(` left`)
|
||||||
|
}
|
||||||
|
|
||||||
|
align, _ = enumProperty(image, ImageVerticalAlign, session, TopAlign)
|
||||||
|
values = enumProperties[ImageVerticalAlign].values
|
||||||
|
if align >= 0 && align < len(values) {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(values[align])
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(` top`)
|
||||||
|
}
|
||||||
|
|
||||||
|
fit, _ := enumProperty(image, backgroundFit, session, NoneFit)
|
||||||
|
values = enumProperties[backgroundFit].values
|
||||||
|
if fit > 0 && fit < len(values) {
|
||||||
|
|
||||||
|
buffer.WriteString(` / `)
|
||||||
|
buffer.WriteString(values[fit])
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
width, _ := sizeProperty(image, Width, session)
|
||||||
|
height, _ := sizeProperty(image, Height, session)
|
||||||
|
|
||||||
|
if width.Type != Auto || height.Type != Auto {
|
||||||
|
buffer.WriteString(` / `)
|
||||||
|
buffer.WriteString(width.cssString("auto", session))
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(height.cssString("auto", session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat, _ := enumProperty(image, Repeat, session, NoRepeat)
|
||||||
|
values = enumProperties[Repeat].values
|
||||||
|
if repeat >= 0 && repeat < len(values) {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(values[repeat])
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(` no-repeat`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
image.writeToBuffer(buffer, indent, image.Tag(), []PropertyName{
|
||||||
|
Source,
|
||||||
|
Width,
|
||||||
|
Height,
|
||||||
|
ImageHorizontalAlign,
|
||||||
|
ImageVerticalAlign,
|
||||||
|
backgroundFit,
|
||||||
|
Repeat,
|
||||||
|
Attachment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundImage) String() string {
|
||||||
|
return runStringWriter(image)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,413 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type LinearGradientDirectionType int
|
||||||
|
|
||||||
|
// Constants related to view's background gradient description
|
||||||
|
const (
|
||||||
|
// ToTopGradient is value of the Direction property of a linear gradient. The value is equivalent to the 0deg angle
|
||||||
|
ToTopGradient LinearGradientDirectionType = 0
|
||||||
|
|
||||||
|
// ToRightTopGradient is value of the Direction property of a linear gradient.
|
||||||
|
ToRightTopGradient LinearGradientDirectionType = 1
|
||||||
|
|
||||||
|
// ToRightGradient is value of the Direction property of a linear gradient. The value is equivalent to the 90deg angle
|
||||||
|
ToRightGradient LinearGradientDirectionType = 2
|
||||||
|
|
||||||
|
// ToRightBottomGradient is value of the Direction property of a linear gradient.
|
||||||
|
ToRightBottomGradient LinearGradientDirectionType = 3
|
||||||
|
|
||||||
|
// ToBottomGradient is value of the Direction property of a linear gradient. The value is equivalent to the 180deg angle
|
||||||
|
ToBottomGradient LinearGradientDirectionType = 4
|
||||||
|
|
||||||
|
// ToLeftBottomGradient is value of the Direction property of a linear gradient.
|
||||||
|
ToLeftBottomGradient LinearGradientDirectionType = 5
|
||||||
|
|
||||||
|
// ToLeftGradient is value of the Direction property of a linear gradient. The value is equivalent to the 270deg angle
|
||||||
|
ToLeftGradient LinearGradientDirectionType = 6
|
||||||
|
|
||||||
|
// ToLeftTopGradient is value of the Direction property of a linear gradient.
|
||||||
|
ToLeftTopGradient LinearGradientDirectionType = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackgroundGradientPoint define point on gradient straight line
|
||||||
|
type BackgroundGradientPoint struct {
|
||||||
|
// Color - the color of the point. Must not be nil.
|
||||||
|
// Can take a value of Color type or string (color constant or textual description of the color)
|
||||||
|
Color any
|
||||||
|
// Pos - the distance from the start of the gradient straight line. Optional (may be nil).
|
||||||
|
// Can take a value of SizeUnit type or string (size constant or textual description of the SizeUnit)
|
||||||
|
Pos any
|
||||||
|
}
|
||||||
|
|
||||||
|
type backgroundGradient struct {
|
||||||
|
backgroundElement
|
||||||
|
}
|
||||||
|
|
||||||
|
type backgroundLinearGradient struct {
|
||||||
|
backgroundGradient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackgroundLinearGradient creates the new background linear gradient.
|
||||||
|
//
|
||||||
|
// The following properties can be used:
|
||||||
|
// - "gradient" [Gradient] - Describes gradient stop points. This is a mandatory property while describing background gradients.
|
||||||
|
// - "direction" [Direction] - Defines the direction of the gradient line.
|
||||||
|
// - "repeating" [Repeating] - Defines whether stop points needs to be repeated after the last one.
|
||||||
|
func NewBackgroundLinearGradient(params Params) BackgroundElement {
|
||||||
|
result := new(backgroundLinearGradient)
|
||||||
|
result.init()
|
||||||
|
for tag, value := range params {
|
||||||
|
result.Set(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinearGradient creates the new background linear gradient.
|
||||||
|
func NewLinearGradient[DirectionType LinearGradientDirectionType | AngleUnit](direction DirectionType, repeating bool, point1 GradientPoint, point2 GradientPoint, points ...GradientPoint) BackgroundElement {
|
||||||
|
params := Params{
|
||||||
|
Direction: direction,
|
||||||
|
Gradient: append([]GradientPoint{point1, point2}, points...),
|
||||||
|
}
|
||||||
|
if repeating {
|
||||||
|
params[Repeating] = true
|
||||||
|
}
|
||||||
|
return NewBackgroundLinearGradient(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGradientText(value string) []BackgroundGradientPoint {
|
||||||
|
elements := strings.Split(value, ",")
|
||||||
|
count := len(elements)
|
||||||
|
if count < 2 {
|
||||||
|
ErrorLog("The gradient must contain at least 2 points")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([]BackgroundGradientPoint, count)
|
||||||
|
for i, element := range elements {
|
||||||
|
if !points[i].setValue(element) {
|
||||||
|
ErrorLogF(`Invalid %d element of the conic gradient: "%s"`, i, element)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
|
||||||
|
switch tag {
|
||||||
|
case Repeating:
|
||||||
|
return setBoolProperty(properties, tag, value)
|
||||||
|
|
||||||
|
case Gradient:
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if value != "" {
|
||||||
|
if strings.Contains(value, " ") || strings.Contains(value, ",") {
|
||||||
|
if points := parseGradientText(value); len(points) >= 2 {
|
||||||
|
properties.setRaw(Gradient, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
} else if value[0] == '@' {
|
||||||
|
properties.setRaw(Gradient, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case []BackgroundGradientPoint:
|
||||||
|
if len(value) >= 2 {
|
||||||
|
properties.setRaw(Gradient, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case []Color:
|
||||||
|
count := len(value)
|
||||||
|
if count >= 2 {
|
||||||
|
points := make([]BackgroundGradientPoint, count)
|
||||||
|
for i, color := range value {
|
||||||
|
points[i].Color = color
|
||||||
|
}
|
||||||
|
properties.setRaw(Gradient, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case []GradientPoint:
|
||||||
|
count := len(value)
|
||||||
|
if count >= 2 {
|
||||||
|
points := make([]BackgroundGradientPoint, count)
|
||||||
|
for i, point := range value {
|
||||||
|
points[i].Color = point.Color
|
||||||
|
points[i].Pos = Percent(point.Offset * 100)
|
||||||
|
}
|
||||||
|
properties.setRaw(Gradient, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF("Invalid gradient %v", value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF("Property %s is not supported by a background gradient", tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (point *BackgroundGradientPoint) setValue(text string) bool {
|
||||||
|
text = strings.Trim(text, " ")
|
||||||
|
|
||||||
|
colorText := text
|
||||||
|
pointText := ""
|
||||||
|
|
||||||
|
if index := strings.Index(text, " "); index > 0 {
|
||||||
|
colorText = text[:index]
|
||||||
|
pointText = strings.Trim(text[index+1:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if colorText == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if colorText[0] == '@' {
|
||||||
|
point.Color = colorText
|
||||||
|
} else if color, ok := StringToColor(colorText); ok {
|
||||||
|
point.Color = color
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pointText == "" {
|
||||||
|
point.Pos = nil
|
||||||
|
} else if pointText[0] == '@' {
|
||||||
|
point.Pos = pointText
|
||||||
|
} else if pos, ok := StringToSizeUnit(pointText); ok {
|
||||||
|
point.Pos = pos
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (point *BackgroundGradientPoint) color(session Session) (Color, bool) {
|
||||||
|
if point.Color != nil {
|
||||||
|
switch color := point.Color.(type) {
|
||||||
|
case string:
|
||||||
|
if color != "" {
|
||||||
|
if color[0] == '@' {
|
||||||
|
if clr, ok := session.Color(color[1:]); ok {
|
||||||
|
return clr, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if clr, ok := StringToColor(color); ok {
|
||||||
|
return clr, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case Color:
|
||||||
|
return color, true
|
||||||
|
|
||||||
|
default:
|
||||||
|
if n, ok := isInt(point.Color); ok {
|
||||||
|
return Color(n), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// String convert internal representation of [BackgroundGradientPoint] into a string.
|
||||||
|
func (point *BackgroundGradientPoint) String() string {
|
||||||
|
result := "black"
|
||||||
|
if point.Color != nil {
|
||||||
|
switch color := point.Color.(type) {
|
||||||
|
case string:
|
||||||
|
result = color
|
||||||
|
|
||||||
|
case Color:
|
||||||
|
result = color.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if point.Pos != nil {
|
||||||
|
switch value := point.Pos.(type) {
|
||||||
|
case string:
|
||||||
|
result += " " + value
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
if value.Type != Auto {
|
||||||
|
result += " " + value.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool {
|
||||||
|
|
||||||
|
value, ok := gradient.properties[Gradient]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var points []BackgroundGradientPoint = nil
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if value != "" && value[0] == '@' {
|
||||||
|
if text, ok := session.Constant(value[1:]); ok {
|
||||||
|
points = parseGradientText(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case []BackgroundGradientPoint:
|
||||||
|
points = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(points) > 0 {
|
||||||
|
for i, point := range points {
|
||||||
|
if i > 0 {
|
||||||
|
buffer.WriteString(`, `)
|
||||||
|
}
|
||||||
|
|
||||||
|
if color, ok := point.color(session); ok {
|
||||||
|
buffer.WriteString(color.cssString())
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if point.Pos != nil {
|
||||||
|
switch value := point.Pos.(type) {
|
||||||
|
case string:
|
||||||
|
if value != "" {
|
||||||
|
if value, ok := session.resolveConstants(value); ok {
|
||||||
|
if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(pos.cssString("", session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
if value.Type != Auto {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(value.cssString("", session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) init() {
|
||||||
|
gradient.backgroundElement.init()
|
||||||
|
gradient.set = backgroundLinearGradientSet
|
||||||
|
gradient.supportedProperties = []PropertyName{Direction, Repeating, Gradient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) Tag() string {
|
||||||
|
return "linear-gradient"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundLinearGradient) Clone() BackgroundElement {
|
||||||
|
result := NewBackgroundLinearGradient(nil)
|
||||||
|
for tag, value := range image.properties {
|
||||||
|
result.setRaw(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundLinearGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
if tag == Direction {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case AngleUnit:
|
||||||
|
properties.setRaw(Direction, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if setSimpleProperty(properties, tag, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
if angle, ok := StringToAngleUnit(value); ok {
|
||||||
|
properties.setRaw(Direction, angle)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case LinearGradientDirectionType:
|
||||||
|
return setEnumProperty(properties, tag, int(value), enumProperties[Direction].values)
|
||||||
|
}
|
||||||
|
return setEnumProperty(properties, tag, value, enumProperties[Direction].values)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backgroundGradientSet(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) cssStyle(session Session) string {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
||||||
|
buffer.WriteString(`repeating-linear-gradient(`)
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(`linear-gradient(`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := gradient.properties[Direction]; ok {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if text, ok := session.resolveConstants(value); ok {
|
||||||
|
direction := enumProperties[Direction]
|
||||||
|
if n, ok := enumStringToInt(text, direction.values, false); ok {
|
||||||
|
buffer.WriteString(direction.cssValues[n])
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
} else {
|
||||||
|
if angle, ok := StringToAngleUnit(text); ok {
|
||||||
|
buffer.WriteString(angle.cssString())
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Invalid linear gradient direction: ` + text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Invalid linear gradient direction: ` + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case int:
|
||||||
|
values := enumProperties[Direction].cssValues
|
||||||
|
if value >= 0 && value < len(values) {
|
||||||
|
buffer.WriteString(values[value])
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
} else {
|
||||||
|
ErrorLogF(`Invalid linear gradient direction: %d`, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case AngleUnit:
|
||||||
|
buffer.WriteString(value.cssString())
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gradient.writeGradient(session, buffer) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
|
||||||
|
Gradient,
|
||||||
|
Repeating,
|
||||||
|
Direction,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundLinearGradient) String() string {
|
||||||
|
return runStringWriter(gradient)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,357 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type RadialGradientRadiusType int
|
||||||
|
|
||||||
|
// Constants related to view's background gradient description
|
||||||
|
const (
|
||||||
|
// EllipseGradient is value of the Shape property of a radial gradient background:
|
||||||
|
// the shape is an axis-aligned ellipse
|
||||||
|
EllipseGradient = 0
|
||||||
|
|
||||||
|
// CircleGradient is value of the Shape property of a radial gradient background:
|
||||||
|
// the gradient's shape is a circle with constant radius
|
||||||
|
CircleGradient = 1
|
||||||
|
|
||||||
|
// ClosestSideGradient is value of the Radius property of a radial gradient background:
|
||||||
|
// The gradient's ending shape meets the side of the box closest to its center (for circles)
|
||||||
|
// or meets both the vertical and horizontal sides closest to the center (for ellipses).
|
||||||
|
ClosestSideGradient RadialGradientRadiusType = 0
|
||||||
|
|
||||||
|
// ClosestCornerGradient is value of the Radius property of a radial gradient background:
|
||||||
|
// The gradient's ending shape is sized so that it exactly meets the closest corner
|
||||||
|
// of the box from its center.
|
||||||
|
ClosestCornerGradient RadialGradientRadiusType = 1
|
||||||
|
|
||||||
|
// FarthestSideGradient is value of the Radius property of a radial gradient background:
|
||||||
|
// Similar to closest-side, except the ending shape is sized to meet the side of the box
|
||||||
|
// farthest from its center (or vertical and horizontal sides).
|
||||||
|
FarthestSideGradient RadialGradientRadiusType = 2
|
||||||
|
|
||||||
|
// FarthestCornerGradient is value of the Radius property of a radial gradient background:
|
||||||
|
// The default value, the gradient's ending shape is sized so that it exactly meets
|
||||||
|
// the farthest corner of the box from its center.
|
||||||
|
FarthestCornerGradient RadialGradientRadiusType = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type backgroundRadialGradient struct {
|
||||||
|
backgroundGradient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackgroundRadialGradient creates the new background radial gradient.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
// - "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.
|
||||||
|
func NewBackgroundRadialGradient(params Params) BackgroundElement {
|
||||||
|
result := new(backgroundRadialGradient)
|
||||||
|
result.init()
|
||||||
|
for tag, value := range params {
|
||||||
|
result.Set(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCircleRadialGradient creates the new background circle radial gradient.
|
||||||
|
func NewCircleRadialGradient[radiusType SizeUnit | RadialGradientRadiusType](xCenter, yCenter SizeUnit, radius radiusType, repeating bool, point1 GradientPoint, point2 GradientPoint, points ...GradientPoint) BackgroundElement {
|
||||||
|
params := Params{
|
||||||
|
RadialGradientShape: CircleGradient,
|
||||||
|
Gradient: append([]GradientPoint{point1, point2}, points...),
|
||||||
|
RadialGradientRadius: radius,
|
||||||
|
}
|
||||||
|
if xCenter.Type != Auto {
|
||||||
|
params[CenterX] = xCenter
|
||||||
|
}
|
||||||
|
if yCenter.Type != Auto {
|
||||||
|
params[CenterY] = yCenter
|
||||||
|
}
|
||||||
|
if repeating {
|
||||||
|
params[Repeating] = true
|
||||||
|
}
|
||||||
|
return NewBackgroundRadialGradient(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEllipseRadialGradient creates the new background ellipse radial gradient.
|
||||||
|
func NewEllipseRadialGradient[radiusType []SizeUnit | RadialGradientRadiusType](xCenter, yCenter SizeUnit, radius radiusType, repeating bool, point1 GradientPoint, point2 GradientPoint, points ...GradientPoint) BackgroundElement {
|
||||||
|
params := Params{
|
||||||
|
RadialGradientShape: EllipseGradient,
|
||||||
|
Gradient: append([]GradientPoint{point1, point2}, points...),
|
||||||
|
RadialGradientRadius: radius,
|
||||||
|
}
|
||||||
|
if xCenter.Type != Auto {
|
||||||
|
params[CenterX] = xCenter
|
||||||
|
}
|
||||||
|
if yCenter.Type != Auto {
|
||||||
|
params[CenterY] = yCenter
|
||||||
|
}
|
||||||
|
if repeating {
|
||||||
|
params[Repeating] = true
|
||||||
|
}
|
||||||
|
return NewBackgroundRadialGradient(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) init() {
|
||||||
|
gradient.backgroundElement.init()
|
||||||
|
gradient.normalize = normalizeRadialGradientTag
|
||||||
|
gradient.set = backgroundRadialGradientSet
|
||||||
|
gradient.supportedProperties = []PropertyName{
|
||||||
|
RadialGradientRadius, RadialGradientShape, CenterX, CenterY, Gradient, Repeating,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) Tag() string {
|
||||||
|
return "radial-gradient"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (image *backgroundRadialGradient) Clone() BackgroundElement {
|
||||||
|
result := NewBackgroundRadialGradient(nil)
|
||||||
|
for tag, value := range image.properties {
|
||||||
|
result.setRaw(tag, value)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRadialGradientTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
|
switch tag {
|
||||||
|
case Radius:
|
||||||
|
tag = RadialGradientRadius
|
||||||
|
|
||||||
|
case Shape:
|
||||||
|
tag = RadialGradientShape
|
||||||
|
|
||||||
|
case "x-center":
|
||||||
|
tag = CenterX
|
||||||
|
|
||||||
|
case "y-center":
|
||||||
|
tag = CenterY
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func backgroundRadialGradientSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case RadialGradientRadius:
|
||||||
|
switch value := value.(type) {
|
||||||
|
case []SizeUnit:
|
||||||
|
switch len(value) {
|
||||||
|
case 0:
|
||||||
|
properties.setRaw(RadialGradientRadius, nil)
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if value[0].Type == Auto {
|
||||||
|
properties.setRaw(RadialGradientRadius, nil)
|
||||||
|
} else {
|
||||||
|
properties.setRaw(RadialGradientRadius, value[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
properties.setRaw(RadialGradientRadius, value)
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case []any:
|
||||||
|
switch len(value) {
|
||||||
|
case 0:
|
||||||
|
properties.setRaw(RadialGradientRadius, nil)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return backgroundRadialGradientSet(properties, RadialGradientRadius, value[0])
|
||||||
|
|
||||||
|
default:
|
||||||
|
properties.setRaw(RadialGradientRadius, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if setSimpleProperty(properties, RadialGradientRadius, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
if size, err := stringToSizeUnit(value); err == nil {
|
||||||
|
if size.Type == Auto {
|
||||||
|
properties.setRaw(RadialGradientRadius, nil)
|
||||||
|
} else {
|
||||||
|
properties.setRaw(RadialGradientRadius, size)
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values)
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
if value.Type == Auto {
|
||||||
|
properties.setRaw(RadialGradientRadius, nil)
|
||||||
|
} else {
|
||||||
|
properties.setRaw(RadialGradientRadius, value)
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case RadialGradientRadiusType:
|
||||||
|
return setEnumProperty(properties, RadialGradientRadius, int(value), enumProperties[RadialGradientRadius].values)
|
||||||
|
|
||||||
|
case int:
|
||||||
|
return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values)
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`Invalid value of "%s" property: %v`, tag, value)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case RadialGradientShape, CenterX, CenterY:
|
||||||
|
return propertiesSet(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backgroundGradientSet(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
if repeating, _ := boolProperty(gradient, Repeating, session); repeating {
|
||||||
|
buffer.WriteString(`repeating-radial-gradient(`)
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(`radial-gradient(`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var shapeText string
|
||||||
|
if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient {
|
||||||
|
shapeText = `circle `
|
||||||
|
} else {
|
||||||
|
shapeText = `ellipse `
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := gradient.properties[RadialGradientRadius]; ok {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if text, ok := session.resolveConstants(value); ok {
|
||||||
|
values := enumProperties[RadialGradientRadius]
|
||||||
|
if n, ok := enumStringToInt(text, values.values, false); ok {
|
||||||
|
buffer.WriteString(shapeText)
|
||||||
|
shapeText = ""
|
||||||
|
buffer.WriteString(values.cssValues[n])
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
} else {
|
||||||
|
if r, ok := StringToSizeUnit(text); ok && r.Type != Auto {
|
||||||
|
buffer.WriteString("ellipse ")
|
||||||
|
shapeText = ""
|
||||||
|
buffer.WriteString(r.cssString("", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
buffer.WriteString(r.cssString("", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Invalid radial gradient radius: ` + text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Invalid radial gradient radius: ` + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case int:
|
||||||
|
values := enumProperties[RadialGradientRadius].cssValues
|
||||||
|
if value >= 0 && value < len(values) {
|
||||||
|
buffer.WriteString(shapeText)
|
||||||
|
shapeText = ""
|
||||||
|
buffer.WriteString(values[value])
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
} else {
|
||||||
|
ErrorLogF(`Invalid radial gradient radius: %d`, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
if value.Type != Auto {
|
||||||
|
buffer.WriteString("ellipse ")
|
||||||
|
shapeText = ""
|
||||||
|
buffer.WriteString(value.cssString("", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
buffer.WriteString(value.cssString("", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
case []SizeUnit:
|
||||||
|
count := len(value)
|
||||||
|
if count > 2 {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
buffer.WriteString("ellipse ")
|
||||||
|
shapeText = ""
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
buffer.WriteString(value[i].cssString("50%", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
case []any:
|
||||||
|
count := len(value)
|
||||||
|
if count > 2 {
|
||||||
|
count = 2
|
||||||
|
}
|
||||||
|
buffer.WriteString("ellipse ")
|
||||||
|
shapeText = ""
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if value[i] != nil {
|
||||||
|
switch value := value[i].(type) {
|
||||||
|
case SizeUnit:
|
||||||
|
buffer.WriteString(value.cssString("50%", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if text, ok := session.resolveConstants(value); ok {
|
||||||
|
if size, err := stringToSizeUnit(text); err == nil {
|
||||||
|
buffer.WriteString(size.cssString("50%", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("50% ")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("50% ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("50% ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x, _ := sizeProperty(gradient, CenterX, session)
|
||||||
|
y, _ := sizeProperty(gradient, CenterX, session)
|
||||||
|
if x.Type != Auto || y.Type != Auto {
|
||||||
|
if shapeText != "" {
|
||||||
|
buffer.WriteString(shapeText)
|
||||||
|
}
|
||||||
|
buffer.WriteString("at ")
|
||||||
|
buffer.WriteString(x.cssString("50%", session))
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
buffer.WriteString(y.cssString("50%", session))
|
||||||
|
} else if shapeText != "" {
|
||||||
|
buffer.WriteString(shapeText)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
if !gradient.writeGradient(session, buffer) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{
|
||||||
|
Gradient,
|
||||||
|
CenterX,
|
||||||
|
CenterY,
|
||||||
|
Repeating,
|
||||||
|
RadialGradientShape,
|
||||||
|
RadialGradientRadius,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gradient *backgroundRadialGradient) String() string {
|
||||||
|
return runStringWriter(gradient)
|
||||||
|
}
|
||||||
578
border.go
578
border.go
|
|
@ -27,151 +27,151 @@ const (
|
||||||
|
|
||||||
// LeftStyle is the constant for "left-style" property tag.
|
// LeftStyle is the constant for "left-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Left border line style.
|
// Left border line style.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneLine`) or "none" - The border will not be drawn.
|
// - 0 (NoneLine) or "none" - The border will not be drawn.
|
||||||
// `1`(`SolidLine`) or "solid" - Solid line as a border.
|
// - 1 (SolidLine) or "solid" - Solid line as a border.
|
||||||
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
|
// - 2 (DashedLine) or "dashed" - Dashed line as a border.
|
||||||
// `3`(`DottedLine`) or "dotted" - Dotted line as a border.
|
// - 3 (DottedLine) or "dotted" - Dotted line as a border.
|
||||||
// `4`(`DoubleLine`) or "double" - Double line as a border.
|
// - 4 (DoubleLine) or "double" - Double line as a border.
|
||||||
LeftStyle = "left-style"
|
LeftStyle PropertyName = "left-style"
|
||||||
|
|
||||||
// RightStyle is the constant for "right-style" property tag.
|
// RightStyle is the constant for "right-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Right border line style.
|
// Right border line style.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneLine`) or "none" - The border will not be drawn.
|
// - 0 (NoneLine) or "none" - The border will not be drawn.
|
||||||
// `1`(`SolidLine`) or "solid" - Solid line as a border.
|
// - 1 (SolidLine) or "solid" - Solid line as a border.
|
||||||
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
|
// - 2 (DashedLine) or "dashed" - Dashed line as a border.
|
||||||
// `3`(`DottedLine`) or "dotted" - Dotted line as a border.
|
// - 3 (DottedLine) or "dotted" - Dotted line as a border.
|
||||||
// `4`(`DoubleLine`) or "double" - Double line as a border.
|
// - 4 (DoubleLine) or "double" - Double line as a border.
|
||||||
RightStyle = "right-style"
|
RightStyle PropertyName = "right-style"
|
||||||
|
|
||||||
// TopStyle is the constant for "top-style" property tag.
|
// TopStyle is the constant for "top-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Top border line style.
|
// Top border line style.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneLine`) or "none" - The border will not be drawn.
|
// - 0 (NoneLine) or "none" - The border will not be drawn.
|
||||||
// `1`(`SolidLine`) or "solid" - Solid line as a border.
|
// - 1 (SolidLine) or "solid" - Solid line as a border.
|
||||||
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
|
// - 2 (DashedLine) or "dashed" - Dashed line as a border.
|
||||||
// `3`(`DottedLine`) or "dotted" - Dotted line as a border.
|
// - 3 (DottedLine) or "dotted" - Dotted line as a border.
|
||||||
// `4`(`DoubleLine`) or "double" - Double line as a border.
|
// - 4 (DoubleLine) or "double" - Double line as a border.
|
||||||
TopStyle = "top-style"
|
TopStyle PropertyName = "top-style"
|
||||||
|
|
||||||
// BottomStyle is the constant for "bottom-style" property tag.
|
// BottomStyle is the constant for "bottom-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Bottom border line style.
|
// Bottom border line style.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneLine`) or "none" - The border will not be drawn.
|
// - 0 (NoneLine) or "none" - The border will not be drawn.
|
||||||
// `1`(`SolidLine`) or "solid" - Solid line as a border.
|
// - 1 (SolidLine) or "solid" - Solid line as a border.
|
||||||
// `2`(`DashedLine`) or "dashed" - Dashed line as a border.
|
// - 2 (DashedLine) or "dashed" - Dashed line as a border.
|
||||||
// `3`(`DottedLine`) or "dotted" - Dotted line as a border.
|
// - 3 (DottedLine) or "dotted" - Dotted line as a border.
|
||||||
// `4`(`DoubleLine`) or "double" - Double line as a border.
|
// - 4 (DoubleLine) or "double" - Double line as a border.
|
||||||
BottomStyle = "bottom-style"
|
BottomStyle PropertyName = "bottom-style"
|
||||||
|
|
||||||
// LeftWidth is the constant for "left-width" property tag.
|
// LeftWidth is the constant for "left-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Left border line width.
|
// Left border line width.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
LeftWidth = "left-width"
|
LeftWidth PropertyName = "left-width"
|
||||||
|
|
||||||
// RightWidth is the constant for "right-width" property tag.
|
// RightWidth is the constant for "right-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Right border line width.
|
// Right border line width.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
RightWidth = "right-width"
|
RightWidth PropertyName = "right-width"
|
||||||
|
|
||||||
// TopWidth is the constant for "top-width" property tag.
|
// TopWidth is the constant for "top-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Top border line width.
|
// Top border line width.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
TopWidth = "top-width"
|
TopWidth PropertyName = "top-width"
|
||||||
|
|
||||||
// BottomWidth is the constant for "bottom-width" property tag.
|
// BottomWidth is the constant for "bottom-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Bottom border line width.
|
// Bottom border line width.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
BottomWidth = "bottom-width"
|
BottomWidth PropertyName = "bottom-width"
|
||||||
|
|
||||||
// LeftColor is the constant for "left-color" property tag.
|
// LeftColor is the constant for "left-color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Left border line color.
|
// Left border line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See [Color] description for more details.
|
||||||
LeftColor = "left-color"
|
LeftColor PropertyName = "left-color"
|
||||||
|
|
||||||
// RightColor is the constant for "right-color" property tag.
|
// RightColor is the constant for "right-color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Right border line color.
|
// Right border line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See [Color] description for more details.
|
||||||
RightColor = "right-color"
|
RightColor PropertyName = "right-color"
|
||||||
|
|
||||||
// TopColor is the constant for "top-color" property tag.
|
// TopColor is the constant for "top-color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Top border line color.
|
// Top border line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See [Color] description for more details.
|
||||||
TopColor = "top-color"
|
TopColor PropertyName = "top-color"
|
||||||
|
|
||||||
// BottomColor is the constant for "bottom-color" property tag.
|
// BottomColor is the constant for "bottom-color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `BorderProperty`.
|
// Used by BorderProperty.
|
||||||
// Bottom border line color.
|
// Bottom border line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See [Color] description for more details.
|
||||||
BottomColor = "bottom-color"
|
BottomColor PropertyName = "bottom-color"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BorderProperty is the interface of a view border data
|
// BorderProperty is the interface of a view border data
|
||||||
|
|
@ -183,7 +183,7 @@ type BorderProperty interface {
|
||||||
// ViewBorders returns top, right, bottom and left borders information all together
|
// ViewBorders returns top, right, bottom and left borders information all together
|
||||||
ViewBorders(session Session) ViewBorders
|
ViewBorders(session Session) ViewBorders
|
||||||
|
|
||||||
delete(tag string)
|
deleteTag(tag PropertyName) bool
|
||||||
cssStyle(builder cssBuilder, session Session)
|
cssStyle(builder cssBuilder, session Session)
|
||||||
cssWidth(builder cssBuilder, session Session)
|
cssWidth(builder cssBuilder, session Session)
|
||||||
cssColor(builder cssBuilder, session Session)
|
cssColor(builder cssBuilder, session Session)
|
||||||
|
|
@ -193,12 +193,12 @@ type BorderProperty interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type borderProperty struct {
|
type borderProperty struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBorderProperty(value any) BorderProperty {
|
func newBorderProperty(value any) BorderProperty {
|
||||||
border := new(borderProperty)
|
border := new(borderProperty)
|
||||||
border.properties = map[string]any{}
|
border.init()
|
||||||
|
|
||||||
if value != nil {
|
if value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -270,9 +270,10 @@ func newBorderProperty(value any) BorderProperty {
|
||||||
// "width" (Width). Determines the line thickness (SizeUnit).
|
// "width" (Width). Determines the line thickness (SizeUnit).
|
||||||
func NewBorder(params Params) BorderProperty {
|
func NewBorder(params Params) BorderProperty {
|
||||||
border := new(borderProperty)
|
border := new(borderProperty)
|
||||||
border.properties = map[string]any{}
|
border.init()
|
||||||
|
|
||||||
if params != nil {
|
if params != nil {
|
||||||
for _, tag := range []string{Style, Width, ColorTag, Left, Right, Top, Bottom,
|
for _, tag := range []PropertyName{Style, Width, ColorTag, Left, Right, Top, Bottom,
|
||||||
LeftStyle, RightStyle, TopStyle, BottomStyle,
|
LeftStyle, RightStyle, TopStyle, BottomStyle,
|
||||||
LeftWidth, RightWidth, TopWidth, BottomWidth,
|
LeftWidth, RightWidth, TopWidth, BottomWidth,
|
||||||
LeftColor, RightColor, TopColor, BottomColor} {
|
LeftColor, RightColor, TopColor, BottomColor} {
|
||||||
|
|
@ -284,8 +285,37 @@ func NewBorder(params Params) BorderProperty {
|
||||||
return border
|
return border
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) normalizeTag(tag string) string {
|
func (border *borderProperty) init() {
|
||||||
tag = strings.ToLower(tag)
|
border.dataProperty.init()
|
||||||
|
border.normalize = normalizeBorderTag
|
||||||
|
border.get = borderGet
|
||||||
|
border.set = borderSet
|
||||||
|
border.remove = borderRemove
|
||||||
|
border.supportedProperties = []PropertyName{
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Style,
|
||||||
|
LeftStyle,
|
||||||
|
RightStyle,
|
||||||
|
TopStyle,
|
||||||
|
BottomStyle,
|
||||||
|
Width,
|
||||||
|
LeftWidth,
|
||||||
|
RightWidth,
|
||||||
|
TopWidth,
|
||||||
|
BottomWidth,
|
||||||
|
ColorTag,
|
||||||
|
LeftColor,
|
||||||
|
RightColor,
|
||||||
|
TopColor,
|
||||||
|
BottomColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBorderTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case BorderLeft, CellBorderLeft:
|
case BorderLeft, CellBorderLeft:
|
||||||
return Left
|
return Left
|
||||||
|
|
@ -352,23 +382,23 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
|
||||||
buffer.WriteString("_{ ")
|
buffer.WriteString("_{ ")
|
||||||
comma := false
|
comma := false
|
||||||
|
|
||||||
write := func(tag string, value any) {
|
write := func(tag PropertyName, value any) {
|
||||||
if comma {
|
if comma {
|
||||||
buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, BorderStyle, value, indent)
|
writePropertyValue(buffer, BorderStyle, value, indent)
|
||||||
comma = true
|
comma = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if value, ok := border.properties[tag]; ok {
|
if value, ok := border.properties[tag]; ok {
|
||||||
write(tag, value)
|
write(tag, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
for _, side := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
style, okStyle := border.properties[side+"-"+Style]
|
style, okStyle := border.properties[side+"-"+Style]
|
||||||
width, okWidth := border.properties[side+"-"+Width]
|
width, okWidth := border.properties[side+"-"+Width]
|
||||||
color, okColor := border.properties[side+"-"+ColorTag]
|
color, okColor := border.properties[side+"-"+ColorTag]
|
||||||
|
|
@ -378,7 +408,7 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string
|
||||||
comma = false
|
comma = false
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(side)
|
buffer.WriteString(string(side))
|
||||||
buffer.WriteString(" = _{ ")
|
buffer.WriteString(" = _{ ")
|
||||||
if okStyle {
|
if okStyle {
|
||||||
write(Style, style)
|
write(Style, style)
|
||||||
|
|
@ -401,164 +431,96 @@ func (border *borderProperty) String() string {
|
||||||
return runStringWriter(border)
|
return runStringWriter(border)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) setSingleBorderObject(prefix string, obj DataObject) bool {
|
|
||||||
result := true
|
|
||||||
if text, ok := obj.PropertyValue(Style); ok {
|
|
||||||
if !border.setEnumProperty(prefix+"-style", text, enumProperties[BorderStyle].values) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if text, ok := obj.PropertyValue(ColorTag); ok {
|
|
||||||
if !border.setColorProperty(prefix+"-color", text) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if text, ok := obj.PropertyValue("width"); ok {
|
|
||||||
if !border.setSizeProperty(prefix+"-width", text) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (border *borderProperty) setBorderObject(obj DataObject) bool {
|
func (border *borderProperty) setBorderObject(obj DataObject) bool {
|
||||||
result := true
|
result := true
|
||||||
|
for i := 0; i < obj.PropertyCount(); i++ {
|
||||||
for _, side := range []string{Top, Right, Bottom, Left} {
|
if node := obj.Property(i); node != nil {
|
||||||
if node := obj.PropertyByTag(side); node != nil {
|
tag := PropertyName(node.Tag())
|
||||||
if node.Type() == ObjectNode {
|
switch node.Type() {
|
||||||
if !border.setSingleBorderObject(side, node.Object()) {
|
case TextNode:
|
||||||
|
if borderSet(border, tag, node.Text()) == nil {
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
notCompatibleType(side, node)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if text, ok := obj.PropertyValue(Style); ok {
|
case ObjectNode:
|
||||||
values := split4Values(text)
|
if borderSet(border, tag, node.Object()) == nil {
|
||||||
styles := enumProperties[BorderStyle].values
|
|
||||||
switch len(values) {
|
|
||||||
case 1:
|
|
||||||
if !border.setEnumProperty(Style, values[0], styles) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
for n, tag := range [4]string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
|
||||||
if !border.setEnumProperty(tag, values[n], styles) {
|
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(Style, text)
|
result = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if text, ok := obj.PropertyValue(ColorTag); ok {
|
|
||||||
values := split4Values(text)
|
|
||||||
switch len(values) {
|
|
||||||
case 1:
|
|
||||||
if !border.setColorProperty(ColorTag, values[0]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
for n, tag := range [4]string{TopColor, RightColor, BottomColor, LeftColor} {
|
|
||||||
if !border.setColorProperty(tag, values[n]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(ColorTag, text)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if text, ok := obj.PropertyValue(Width); ok {
|
|
||||||
values := split4Values(text)
|
|
||||||
switch len(values) {
|
|
||||||
case 1:
|
|
||||||
if !border.setSizeProperty(Width, values[0]) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
for n, tag := range [4]string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
|
||||||
if !border.setSizeProperty(tag, values[n]) {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(Width, text)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) Remove(tag string) {
|
func borderRemove(properties Properties, tag PropertyName) []PropertyName {
|
||||||
tag = border.normalizeTag(tag)
|
result := []PropertyName{}
|
||||||
|
removeTag := func(t PropertyName) {
|
||||||
|
if properties.getRaw(t) != nil {
|
||||||
|
properties.setRaw(t, nil)
|
||||||
|
result = append(result, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Style:
|
case Style:
|
||||||
for _, t := range []string{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
for _, t := range []PropertyName{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||||
delete(border.properties, t)
|
removeTag(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
case Width:
|
case Width:
|
||||||
for _, t := range []string{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
for _, t := range []PropertyName{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||||
delete(border.properties, t)
|
removeTag(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ColorTag:
|
case ColorTag:
|
||||||
for _, t := range []string{tag, TopColor, RightColor, BottomColor, LeftColor} {
|
for _, t := range []PropertyName{tag, TopColor, RightColor, BottomColor, LeftColor} {
|
||||||
delete(border.properties, t)
|
removeTag(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
case Left, Right, Top, Bottom:
|
case Left, Right, Top, Bottom:
|
||||||
border.Remove(tag + "-style")
|
removeTag(tag + "-style")
|
||||||
border.Remove(tag + "-width")
|
removeTag(tag + "-width")
|
||||||
border.Remove(tag + "-color")
|
removeTag(tag + "-color")
|
||||||
|
|
||||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||||
delete(border.properties, tag)
|
removeTag(tag)
|
||||||
if style, ok := border.properties[Style]; ok && style != nil {
|
if style := properties.getRaw(Style); style != nil {
|
||||||
for _, t := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
for _, t := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||||
if t != tag {
|
if t != tag {
|
||||||
if _, ok := border.properties[t]; !ok {
|
if properties.getRaw(t) == nil {
|
||||||
border.properties[t] = style
|
properties.setRaw(t, style)
|
||||||
|
result = append(result, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||||
delete(border.properties, tag)
|
removeTag(tag)
|
||||||
if width, ok := border.properties[Width]; ok && width != nil {
|
if width := properties.getRaw(Width); width != nil {
|
||||||
for _, t := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
for _, t := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||||
if t != tag {
|
if t != tag {
|
||||||
if _, ok := border.properties[t]; !ok {
|
if properties.getRaw(t) == nil {
|
||||||
border.properties[t] = width
|
properties.setRaw(t, width)
|
||||||
|
result = append(result, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftColor, RightColor, TopColor, BottomColor:
|
case LeftColor, RightColor, TopColor, BottomColor:
|
||||||
delete(border.properties, tag)
|
removeTag(tag)
|
||||||
if color, ok := border.properties[ColorTag]; ok && color != nil {
|
if color := properties.getRaw(ColorTag); color != nil {
|
||||||
for _, t := range []string{TopColor, RightColor, BottomColor, LeftColor} {
|
for _, t := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
|
||||||
if t != tag {
|
if t != tag {
|
||||||
if _, ok := border.properties[t]; !ok {
|
if properties.getRaw(t) == nil {
|
||||||
border.properties[t] = color
|
properties.setRaw(t, color)
|
||||||
|
result = append(result, t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -567,80 +529,118 @@ func (border *borderProperty) Remove(tag string) {
|
||||||
default:
|
default:
|
||||||
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) Set(tag string, value any) bool {
|
func borderSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if value == nil {
|
|
||||||
border.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = border.normalizeTag(tag)
|
setSingleBorderObject := func(prefix PropertyName, obj DataObject) []PropertyName {
|
||||||
|
result := []PropertyName{}
|
||||||
|
if text, ok := obj.PropertyValue(string(Style)); ok {
|
||||||
|
props := setEnumProperty(properties, prefix+"-style", text, enumProperties[BorderStyle].values)
|
||||||
|
if props == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = append(result, props...)
|
||||||
|
}
|
||||||
|
if text, ok := obj.PropertyValue(string(ColorTag)); ok {
|
||||||
|
props := setColorProperty(properties, prefix+"-color", text)
|
||||||
|
if props == nil && len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = append(result, props...)
|
||||||
|
}
|
||||||
|
if text, ok := obj.PropertyValue("width"); ok {
|
||||||
|
props := setSizeProperty(properties, prefix+"-width", text)
|
||||||
|
if props == nil && len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result = append(result, props...)
|
||||||
|
}
|
||||||
|
if len(result) > 0 {
|
||||||
|
result = append(result, prefix)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Style:
|
case Style:
|
||||||
if border.setEnumProperty(Style, value, enumProperties[BorderStyle].values) {
|
if result := setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values); result != nil {
|
||||||
for _, side := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
for _, side := range []PropertyName{TopStyle, RightStyle, BottomStyle, LeftStyle} {
|
||||||
delete(border.properties, side)
|
if value := properties.getRaw(side); value != nil {
|
||||||
|
properties.setRaw(side, nil)
|
||||||
|
result = append(result, side)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
case Width:
|
case Width:
|
||||||
if border.setSizeProperty(Width, value) {
|
if result := setSizeProperty(properties, Width, value); result != nil {
|
||||||
for _, side := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
for _, side := range []PropertyName{TopWidth, RightWidth, BottomWidth, LeftWidth} {
|
||||||
delete(border.properties, side)
|
if value := properties.getRaw(side); value != nil {
|
||||||
|
properties.setRaw(side, nil)
|
||||||
|
result = append(result, side)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
case ColorTag:
|
case ColorTag:
|
||||||
if border.setColorProperty(ColorTag, value) {
|
if result := setColorProperty(properties, ColorTag, value); result != nil {
|
||||||
for _, side := range []string{TopColor, RightColor, BottomColor, LeftColor} {
|
for _, side := range []PropertyName{TopColor, RightColor, BottomColor, LeftColor} {
|
||||||
delete(border.properties, side)
|
if value := properties.getRaw(side); value != nil {
|
||||||
|
properties.setRaw(side, nil)
|
||||||
|
result = append(result, side)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||||
return border.setEnumProperty(tag, value, enumProperties[BorderStyle].values)
|
return setEnumProperty(properties, tag, value, enumProperties[BorderStyle].values)
|
||||||
|
|
||||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||||
return border.setSizeProperty(tag, value)
|
return setSizeProperty(properties, tag, value)
|
||||||
|
|
||||||
case LeftColor, RightColor, TopColor, BottomColor:
|
case LeftColor, RightColor, TopColor, BottomColor:
|
||||||
return border.setColorProperty(tag, value)
|
return setColorProperty(properties, tag, value)
|
||||||
|
|
||||||
case Left, Right, Top, Bottom:
|
case Left, Right, Top, Bottom:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if obj := ParseDataText(value); obj != nil {
|
if obj := ParseDataText(value); obj != nil {
|
||||||
return border.setSingleBorderObject(tag, obj)
|
return setSingleBorderObject(tag, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
return border.setSingleBorderObject(tag, value)
|
return setSingleBorderObject(tag, value)
|
||||||
|
|
||||||
case BorderProperty:
|
case BorderProperty:
|
||||||
|
result := []PropertyName{}
|
||||||
styleTag := tag + "-" + Style
|
styleTag := tag + "-" + Style
|
||||||
if style := value.Get(styleTag); value != nil {
|
if style := value.Get(styleTag); value != nil {
|
||||||
border.properties[styleTag] = style
|
properties.setRaw(styleTag, style)
|
||||||
|
result = append(result, styleTag)
|
||||||
}
|
}
|
||||||
colorTag := tag + "-" + ColorTag
|
colorTag := tag + "-" + ColorTag
|
||||||
if color := value.Get(colorTag); value != nil {
|
if color := value.Get(colorTag); value != nil {
|
||||||
border.properties[colorTag] = color
|
properties.setRaw(colorTag, color)
|
||||||
|
result = append(result, colorTag)
|
||||||
}
|
}
|
||||||
widthTag := tag + "-" + Width
|
widthTag := tag + "-" + Width
|
||||||
if width := value.Get(widthTag); value != nil {
|
if width := value.Get(widthTag); value != nil {
|
||||||
border.properties[widthTag] = width
|
properties.setRaw(widthTag, width)
|
||||||
|
result = append(result, widthTag)
|
||||||
}
|
}
|
||||||
return true
|
return result
|
||||||
|
|
||||||
case ViewBorder:
|
case ViewBorder:
|
||||||
border.properties[tag+"-"+Style] = value.Style
|
properties.setRaw(tag+"-"+Style, value.Style)
|
||||||
border.properties[tag+"-"+Width] = value.Width
|
properties.setRaw(tag+"-"+Width, value.Width)
|
||||||
border.properties[tag+"-"+ColorTag] = value.Color
|
properties.setRaw(tag+"-"+ColorTag, value.Color)
|
||||||
return true
|
return []PropertyName{tag + "-" + Style, tag + "-" + Width, tag + "-" + ColorTag}
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
|
|
@ -648,105 +648,119 @@ func (border *borderProperty) Set(tag string, value any) bool {
|
||||||
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) Get(tag string) any {
|
func borderGet(properties Properties, tag PropertyName) any {
|
||||||
tag = border.normalizeTag(tag)
|
if result := properties.getRaw(tag); result != nil {
|
||||||
|
|
||||||
if result, ok := border.properties[tag]; ok {
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Left, Right, Top, Bottom:
|
case Left, Right, Top, Bottom:
|
||||||
result := newBorderProperty(nil)
|
result := newBorderProperty(nil)
|
||||||
if style, ok := border.properties[tag+"-"+Style]; ok {
|
if style := properties.getRaw(tag + "-" + Style); style != nil {
|
||||||
result.Set(Style, style)
|
result.Set(Style, style)
|
||||||
} else if style, ok := border.properties[Style]; ok {
|
} else if style := properties.getRaw(Style); style != nil {
|
||||||
result.Set(Style, style)
|
result.Set(Style, style)
|
||||||
}
|
}
|
||||||
if width, ok := border.properties[tag+"-"+Width]; ok {
|
if width := properties.getRaw(tag + "-" + Width); width != nil {
|
||||||
result.Set(Width, width)
|
result.Set(Width, width)
|
||||||
} else if width, ok := border.properties[Width]; ok {
|
} else if width := properties.getRaw(Width); width != nil {
|
||||||
result.Set(Width, width)
|
result.Set(Width, width)
|
||||||
}
|
}
|
||||||
if color, ok := border.properties[tag+"-"+ColorTag]; ok {
|
if color := properties.getRaw(tag + "-" + ColorTag); color != nil {
|
||||||
result.Set(ColorTag, color)
|
result.Set(ColorTag, color)
|
||||||
} else if color, ok := border.properties[ColorTag]; ok {
|
} else if color := properties.getRaw(ColorTag); color != nil {
|
||||||
result.Set(ColorTag, color)
|
result.Set(ColorTag, color)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||||
if style, ok := border.properties[tag]; ok {
|
if style := properties.getRaw(tag); style != nil {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
return border.properties[Style]
|
return properties.getRaw(Style)
|
||||||
|
|
||||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||||
if width, ok := border.properties[tag]; ok {
|
if width := properties.getRaw(tag); width != nil {
|
||||||
return width
|
return width
|
||||||
}
|
}
|
||||||
return border.properties[Width]
|
return properties.getRaw(Width)
|
||||||
|
|
||||||
case LeftColor, RightColor, TopColor, BottomColor:
|
case LeftColor, RightColor, TopColor, BottomColor:
|
||||||
if color, ok := border.properties[tag]; ok {
|
if color := properties.getRaw(tag); color != nil {
|
||||||
return color
|
return color
|
||||||
}
|
}
|
||||||
return border.properties[ColorTag]
|
return properties.getRaw(ColorTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) delete(tag string) {
|
func (border *borderProperty) deleteTag(tag PropertyName) bool {
|
||||||
tag = border.normalizeTag(tag)
|
|
||||||
remove := []string{}
|
result := false
|
||||||
|
removeTags := func(tags []PropertyName) {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if border.getRaw(tag) != nil {
|
||||||
|
border.setRaw(tag, nil)
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Style:
|
case Style:
|
||||||
remove = []string{Style, LeftStyle, RightStyle, TopStyle, BottomStyle}
|
removeTags([]PropertyName{Style, LeftStyle, RightStyle, TopStyle, BottomStyle})
|
||||||
|
|
||||||
case Width:
|
case Width:
|
||||||
remove = []string{Width, LeftWidth, RightWidth, TopWidth, BottomWidth}
|
removeTags([]PropertyName{Width, LeftWidth, RightWidth, TopWidth, BottomWidth})
|
||||||
|
|
||||||
case ColorTag:
|
case ColorTag:
|
||||||
remove = []string{ColorTag, LeftColor, RightColor, TopColor, BottomColor}
|
removeTags([]PropertyName{ColorTag, LeftColor, RightColor, TopColor, BottomColor})
|
||||||
|
|
||||||
case Left, Right, Top, Bottom:
|
case Left, Right, Top, Bottom:
|
||||||
if border.Get(Style) != nil {
|
if border.Get(Style) != nil {
|
||||||
border.properties[tag+"-"+Style] = 0
|
border.properties[tag+"-"+Style] = 0
|
||||||
remove = []string{tag + "-" + ColorTag, tag + "-" + Width}
|
result = true
|
||||||
|
removeTags([]PropertyName{tag + "-" + ColorTag, tag + "-" + Width})
|
||||||
} else {
|
} else {
|
||||||
remove = []string{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width}
|
removeTags([]PropertyName{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width})
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
case LeftStyle, RightStyle, TopStyle, BottomStyle:
|
||||||
if border.Get(Style) != nil {
|
if border.getRaw(tag) != nil {
|
||||||
border.properties[tag] = 0
|
if border.Get(Style) != nil {
|
||||||
} else {
|
border.properties[tag] = 0
|
||||||
remove = []string{tag}
|
result = true
|
||||||
|
} else {
|
||||||
|
removeTags([]PropertyName{tag})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
case LeftWidth, RightWidth, TopWidth, BottomWidth:
|
||||||
if border.Get(Width) != nil {
|
if border.getRaw(tag) != nil {
|
||||||
border.properties[tag] = AutoSize()
|
if border.Get(Width) != nil {
|
||||||
} else {
|
border.properties[tag] = AutoSize()
|
||||||
remove = []string{tag}
|
result = true
|
||||||
|
} else {
|
||||||
|
removeTags([]PropertyName{tag})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case LeftColor, RightColor, TopColor, BottomColor:
|
case LeftColor, RightColor, TopColor, BottomColor:
|
||||||
if border.Get(ColorTag) != nil {
|
if border.getRaw(tag) != nil {
|
||||||
border.properties[tag] = 0
|
if border.Get(ColorTag) != nil {
|
||||||
} else {
|
border.properties[tag] = 0
|
||||||
remove = []string{tag}
|
result = true
|
||||||
|
} else {
|
||||||
|
removeTags([]PropertyName{tag})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range remove {
|
return result
|
||||||
delete(border.properties, tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (border *borderProperty) ViewBorders(session Session) ViewBorders {
|
func (border *borderProperty) ViewBorders(session Session) ViewBorders {
|
||||||
|
|
@ -755,7 +769,7 @@ func (border *borderProperty) ViewBorders(session Session) ViewBorders {
|
||||||
defWidth, _ := sizeProperty(border, Width, session)
|
defWidth, _ := sizeProperty(border, Width, session)
|
||||||
defColor, _ := colorProperty(border, ColorTag, session)
|
defColor, _ := colorProperty(border, ColorTag, session)
|
||||||
|
|
||||||
getBorder := func(prefix string) ViewBorder {
|
getBorder := func(prefix PropertyName) ViewBorder {
|
||||||
var result ViewBorder
|
var result ViewBorder
|
||||||
var ok bool
|
var ok bool
|
||||||
if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok {
|
if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok {
|
||||||
|
|
@ -784,9 +798,9 @@ func (border *borderProperty) cssStyle(builder cssBuilder, session Session) {
|
||||||
if borders.Top.Style == borders.Right.Style &&
|
if borders.Top.Style == borders.Right.Style &&
|
||||||
borders.Top.Style == borders.Left.Style &&
|
borders.Top.Style == borders.Left.Style &&
|
||||||
borders.Top.Style == borders.Bottom.Style {
|
borders.Top.Style == borders.Bottom.Style {
|
||||||
builder.add(BorderStyle, values[borders.Top.Style])
|
builder.add(string(BorderStyle), values[borders.Top.Style])
|
||||||
} else {
|
} else {
|
||||||
builder.addValues(BorderStyle, " ", values[borders.Top.Style],
|
builder.addValues(string(BorderStyle), " ", values[borders.Top.Style],
|
||||||
values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style])
|
values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -870,11 +884,25 @@ func (border *ViewBorders) AllTheSame() bool {
|
||||||
border.Top.Width.Equal(border.Bottom.Width)
|
border.Top.Width.Equal(border.Bottom.Width)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBorder(style Properties, tag string) BorderProperty {
|
func getBorderProperty(properties Properties, tag PropertyName) BorderProperty {
|
||||||
if value := style.Get(tag); value != nil {
|
if value := properties.getRaw(tag); value != nil {
|
||||||
if border, ok := value.(BorderProperty); ok {
|
if border, ok := value.(BorderProperty); ok {
|
||||||
return border
|
return border
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setBorderPropertyElement(properties Properties, mainTag, tag PropertyName, value any) []PropertyName {
|
||||||
|
border := getBorderProperty(properties, mainTag)
|
||||||
|
if border == nil {
|
||||||
|
border = NewBorder(nil)
|
||||||
|
if border.Set(tag, value) {
|
||||||
|
properties.setRaw(mainTag, border)
|
||||||
|
return []PropertyName{mainTag, tag}
|
||||||
|
}
|
||||||
|
} else if border.Set(tag, value) {
|
||||||
|
return []PropertyName{mainTag, tag}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
223
bounds.go
223
bounds.go
|
|
@ -16,26 +16,49 @@ type BoundsProperty interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type boundsPropertyData struct {
|
type boundsPropertyData struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBoundsProperty creates the new BoundsProperty object.
|
// NewBoundsProperty creates the new BoundsProperty object.
|
||||||
|
//
|
||||||
// The following SizeUnit properties can be used: "left" (Left), "right" (Right), "top" (Top), and "bottom" (Bottom).
|
// The following SizeUnit properties can be used: "left" (Left), "right" (Right), "top" (Top), and "bottom" (Bottom).
|
||||||
func NewBoundsProperty(params Params) BoundsProperty {
|
func NewBoundsProperty(params Params) BoundsProperty {
|
||||||
bounds := new(boundsPropertyData)
|
bounds := new(boundsPropertyData)
|
||||||
bounds.properties = map[string]any{}
|
bounds.init()
|
||||||
|
|
||||||
if params != nil {
|
if params != nil {
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
for _, tag := range bounds.supportedProperties {
|
||||||
if value, ok := params[tag]; ok {
|
if value, ok := params[tag]; ok && value != nil {
|
||||||
bounds.Set(tag, value)
|
bounds.set(bounds, tag, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bounds
|
return bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bounds *boundsPropertyData) normalizeTag(tag string) string {
|
// NewBounds creates the new BoundsProperty object.
|
||||||
tag = strings.ToLower(tag)
|
//
|
||||||
|
// The arguments specify the boundaries in a clockwise direction: "top", "right", "bottom", and "left".
|
||||||
|
//
|
||||||
|
// If the argument is specified as int or float64, the value is considered to be in pixels.
|
||||||
|
func NewBounds[topType SizeUnit | int | float64, rightType SizeUnit | int | float64, bottomType SizeUnit | int | float64, leftType SizeUnit | int | float64](
|
||||||
|
top topType, right rightType, bottom bottomType, left leftType) BoundsProperty {
|
||||||
|
return NewBoundsProperty(Params{
|
||||||
|
Top: top,
|
||||||
|
Right: right,
|
||||||
|
Bottom: bottom,
|
||||||
|
Left: left,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bounds *boundsPropertyData) init() {
|
||||||
|
bounds.dataProperty.init()
|
||||||
|
bounds.normalize = normalizeBoundsTag
|
||||||
|
bounds.supportedProperties = []PropertyName{Top, Right, Bottom, Left}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeBoundsTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case MarginTop, PaddingTop, CellPaddingTop,
|
case MarginTop, PaddingTop, CellPaddingTop,
|
||||||
"top-margin", "top-padding", "top-cell-padding":
|
"top-margin", "top-padding", "top-cell-padding":
|
||||||
|
|
@ -64,12 +87,12 @@ func (bounds *boundsPropertyData) String() string {
|
||||||
func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent string) {
|
func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent string) {
|
||||||
buffer.WriteString("_{ ")
|
buffer.WriteString("_{ ")
|
||||||
comma := false
|
comma := false
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
if value, ok := bounds.properties[tag]; ok {
|
if value, ok := bounds.properties[tag]; ok {
|
||||||
if comma {
|
if comma {
|
||||||
buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
comma = true
|
comma = true
|
||||||
|
|
@ -78,38 +101,6 @@ func (bounds *boundsPropertyData) writeString(buffer *strings.Builder, indent st
|
||||||
buffer.WriteString(" }")
|
buffer.WriteString(" }")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bounds *boundsPropertyData) Remove(tag string) {
|
|
||||||
bounds.propertyList.Remove(bounds.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bounds *boundsPropertyData) Set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
bounds.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = bounds.normalizeTag(tag)
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case Top, Right, Bottom, Left:
|
|
||||||
return bounds.setSizeProperty(tag, value)
|
|
||||||
|
|
||||||
default:
|
|
||||||
ErrorLogF(`"%s" property is not compatible with the BoundsProperty`, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bounds *boundsPropertyData) Get(tag string) any {
|
|
||||||
tag = bounds.normalizeTag(tag)
|
|
||||||
if value, ok := bounds.properties[tag]; ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bounds *boundsPropertyData) Bounds(session Session) Bounds {
|
func (bounds *boundsPropertyData) Bounds(session Session) Bounds {
|
||||||
top, _ := sizeProperty(bounds, Top, session)
|
top, _ := sizeProperty(bounds, Top, session)
|
||||||
right, _ := sizeProperty(bounds, Right, session)
|
right, _ := sizeProperty(bounds, Right, session)
|
||||||
|
|
@ -141,7 +132,7 @@ func (bounds *Bounds) SetAll(value SizeUnit) {
|
||||||
bounds.Left = value
|
bounds.Left = value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTag string, properties Properties, session Session) {
|
func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTag PropertyName, properties Properties, session Session) {
|
||||||
bounds.Top = AutoSize()
|
bounds.Top = AutoSize()
|
||||||
if size, ok := sizeProperty(properties, tag, session); ok {
|
if size, ok := sizeProperty(properties, tag, session); ok {
|
||||||
bounds.Top = size
|
bounds.Top = size
|
||||||
|
|
@ -164,22 +155,6 @@ func (bounds *Bounds) setFromProperties(tag, topTag, rightTag, bottomTag, leftTa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (bounds *Bounds) allFieldsAuto() bool {
|
|
||||||
return bounds.Left.Type == Auto &&
|
|
||||||
bounds.Top.Type == Auto &&
|
|
||||||
bounds.Right.Type == Auto &&
|
|
||||||
bounds.Bottom.Type == Auto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bounds *Bounds) allFieldsZero() bool {
|
|
||||||
return (bounds.Left.Type == Auto || bounds.Left.Value == 0) &&
|
|
||||||
(bounds.Top.Type == Auto || bounds.Top.Value == 0) &&
|
|
||||||
(bounds.Right.Type == Auto || bounds.Right.Value == 0) &&
|
|
||||||
(bounds.Bottom.Type == Auto || bounds.Bottom.Value == 0)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (bounds *Bounds) allFieldsEqual() bool {
|
func (bounds *Bounds) allFieldsEqual() bool {
|
||||||
if bounds.Left.Type == bounds.Top.Type &&
|
if bounds.Left.Type == bounds.Top.Type &&
|
||||||
bounds.Left.Type == bounds.Right.Type &&
|
bounds.Left.Type == bounds.Right.Type &&
|
||||||
|
|
@ -193,20 +168,6 @@ func (bounds *Bounds) allFieldsEqual() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (bounds Bounds) writeCSSString(buffer *strings.Builder, textForAuto string) {
|
|
||||||
buffer.WriteString(bounds.Top.cssString(textForAuto))
|
|
||||||
if !bounds.allFieldsEqual() {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(bounds.Right.cssString(textForAuto))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(bounds.Bottom.cssString(textForAuto))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(bounds.Left.cssString(textForAuto))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// String convert Bounds to string
|
// String convert Bounds to string
|
||||||
func (bounds *Bounds) String() string {
|
func (bounds *Bounds) String() string {
|
||||||
if bounds.allFieldsEqual() {
|
if bounds.allFieldsEqual() {
|
||||||
|
|
@ -216,11 +177,11 @@ func (bounds *Bounds) String() string {
|
||||||
bounds.Bottom.String() + "," + bounds.Left.String()
|
bounds.Bottom.String() + "," + bounds.Left.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bounds *Bounds) cssValue(tag string, builder cssBuilder, session Session) {
|
func (bounds *Bounds) cssValue(tag PropertyName, builder cssBuilder, session Session) {
|
||||||
if bounds.allFieldsEqual() {
|
if bounds.allFieldsEqual() {
|
||||||
builder.add(tag, bounds.Top.cssString("0", session))
|
builder.add(string(tag), bounds.Top.cssString("0", session))
|
||||||
} else {
|
} else {
|
||||||
builder.addValues(tag, " ",
|
builder.addValues(string(tag), " ",
|
||||||
bounds.Top.cssString("0", session),
|
bounds.Top.cssString("0", session),
|
||||||
bounds.Right.cssString("0", session),
|
bounds.Right.cssString("0", session),
|
||||||
bounds.Bottom.cssString("0", session),
|
bounds.Bottom.cssString("0", session),
|
||||||
|
|
@ -234,8 +195,8 @@ func (bounds *Bounds) cssString(session Session) string {
|
||||||
return builder.finish()
|
return builder.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setBounds(tag string, value any) bool {
|
func setBoundsProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if strings.Contains(value, ",") {
|
if strings.Contains(value, ",") {
|
||||||
|
|
@ -247,88 +208,119 @@ func (properties *propertyList) setBounds(tag string, value any) bool {
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
bounds := NewBoundsProperty(nil)
|
bounds := NewBoundsProperty(nil)
|
||||||
for i, tag := range []string{Top, Right, Bottom, Left} {
|
for i, tag := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
if !bounds.Set(tag, values[i]) {
|
if !bounds.Set(tag, values[i]) {
|
||||||
notCompatibleType(tag, value)
|
return nil
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties.properties[tag] = bounds
|
properties.setRaw(tag, bounds)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return properties.setSizeProperty(tag, value)
|
return setSizeProperty(properties, tag, value)
|
||||||
|
|
||||||
case SizeUnit:
|
case SizeUnit:
|
||||||
properties.properties[tag] = value
|
properties.setRaw(tag, value)
|
||||||
|
|
||||||
case float32:
|
case float32:
|
||||||
properties.properties[tag] = Px(float64(value))
|
properties.setRaw(tag, Px(float64(value)))
|
||||||
|
|
||||||
case float64:
|
case float64:
|
||||||
properties.properties[tag] = Px(value)
|
properties.setRaw(tag, Px(value))
|
||||||
|
|
||||||
case Bounds:
|
case Bounds:
|
||||||
bounds := NewBoundsProperty(nil)
|
bounds := NewBoundsProperty(nil)
|
||||||
if value.Top.Type != Auto {
|
if value.Top.Type != Auto {
|
||||||
bounds.Set(Top, value.Top)
|
bounds.setRaw(Top, value.Top)
|
||||||
}
|
}
|
||||||
if value.Right.Type != Auto {
|
if value.Right.Type != Auto {
|
||||||
bounds.Set(Right, value.Right)
|
bounds.setRaw(Right, value.Right)
|
||||||
}
|
}
|
||||||
if value.Bottom.Type != Auto {
|
if value.Bottom.Type != Auto {
|
||||||
bounds.Set(Bottom, value.Bottom)
|
bounds.setRaw(Bottom, value.Bottom)
|
||||||
}
|
}
|
||||||
if value.Left.Type != Auto {
|
if value.Left.Type != Auto {
|
||||||
bounds.Set(Left, value.Left)
|
bounds.setRaw(Left, value.Left)
|
||||||
}
|
}
|
||||||
properties.properties[tag] = bounds
|
properties.setRaw(tag, bounds)
|
||||||
|
|
||||||
case BoundsProperty:
|
case BoundsProperty:
|
||||||
properties.properties[tag] = value
|
properties.setRaw(tag, value)
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
bounds := NewBoundsProperty(nil)
|
bounds := NewBoundsProperty(nil)
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
if text, ok := value.PropertyValue(tag); ok {
|
if text, ok := value.PropertyValue(string(tag)); ok {
|
||||||
if !bounds.Set(tag, text) {
|
if !bounds.Set(tag, text) {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties.properties[tag] = bounds
|
properties.setRaw(tag, bounds)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if n, ok := isInt(value); ok {
|
if n, ok := isInt(value); ok {
|
||||||
properties.properties[tag] = Px(float64(n))
|
properties.setRaw(tag, Px(float64(n)))
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) boundsProperty(tag string) BoundsProperty {
|
func removeBoundsPropertySide(properties Properties, mainTag, sideTag PropertyName) []PropertyName {
|
||||||
if value, ok := properties.properties[tag]; ok {
|
if bounds := getBoundsProperty(properties, mainTag); bounds != nil {
|
||||||
|
if bounds.getRaw(sideTag) != nil {
|
||||||
|
bounds.Remove(sideTag)
|
||||||
|
if bounds.empty() {
|
||||||
|
bounds = nil
|
||||||
|
}
|
||||||
|
properties.setRaw(mainTag, bounds)
|
||||||
|
return []PropertyName{mainTag, sideTag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoundsPropertySide(properties Properties, mainTag, sideTag PropertyName, value any) []PropertyName {
|
||||||
|
if value == nil {
|
||||||
|
return removeBoundsPropertySide(properties, mainTag, sideTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds := getBoundsProperty(properties, mainTag)
|
||||||
|
if bounds == nil {
|
||||||
|
bounds = NewBoundsProperty(nil)
|
||||||
|
}
|
||||||
|
if bounds.Set(sideTag, value) {
|
||||||
|
properties.setRaw(mainTag, bounds)
|
||||||
|
return []PropertyName{mainTag, sideTag}
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(sideTag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBoundsProperty(properties Properties, tag PropertyName) BoundsProperty {
|
||||||
|
if value := properties.getRaw(tag); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
bounds := NewBoundsProperty(nil)
|
bounds := NewBoundsProperty(nil)
|
||||||
for _, t := range []string{Top, Right, Bottom, Left} {
|
for _, t := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
bounds.Set(t, value)
|
bounds.Set(t, value)
|
||||||
}
|
}
|
||||||
return bounds
|
return bounds
|
||||||
|
|
||||||
case SizeUnit:
|
case SizeUnit:
|
||||||
bounds := NewBoundsProperty(nil)
|
bounds := NewBoundsProperty(nil)
|
||||||
for _, t := range []string{Top, Right, Bottom, Left} {
|
for _, t := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
bounds.Set(t, value)
|
bounds.Set(t, value)
|
||||||
}
|
}
|
||||||
return bounds
|
return bounds
|
||||||
|
|
@ -345,29 +337,10 @@ func (properties *propertyList) boundsProperty(tag string) BoundsProperty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewBoundsProperty(nil)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) removeBoundsSide(mainTag, sideTag string) {
|
func getBounds(properties Properties, tag PropertyName, session Session) (Bounds, bool) {
|
||||||
bounds := properties.boundsProperty(mainTag)
|
|
||||||
if bounds.Get(sideTag) != nil {
|
|
||||||
bounds.Remove(sideTag)
|
|
||||||
properties.properties[mainTag] = bounds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (properties *propertyList) setBoundsSide(mainTag, sideTag string, value any) bool {
|
|
||||||
bounds := properties.boundsProperty(mainTag)
|
|
||||||
if bounds.Set(sideTag, value) {
|
|
||||||
properties.properties[mainTag] = bounds
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
notCompatibleType(sideTag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func boundsProperty(properties Properties, tag string, session Session) (Bounds, bool) {
|
|
||||||
if value := properties.Get(tag); value != nil {
|
if value := properties.Get(tag); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ func NewButton(session Session, params Params) Button {
|
||||||
|
|
||||||
func newButton(session Session) View {
|
func newButton(session Session) View {
|
||||||
return NewButton(session, nil)
|
return NewButton(session, nil)
|
||||||
|
//return new(buttonData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *buttonData) CreateSuperView(session Session) View {
|
func (button *buttonData) CreateSuperView(session Session) View {
|
||||||
|
|
|
||||||
178
canvas.go
178
canvas.go
|
|
@ -6,39 +6,54 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LineJoin is the type for setting the shape used to join two line segments where they meet.
|
||||||
|
type LineJoin int
|
||||||
|
|
||||||
|
// LineCap is the type for setting the shape used to draw the end points of lines.
|
||||||
|
type LineCap int
|
||||||
|
|
||||||
// Constants related to canvas view operations
|
// Constants related to canvas view operations
|
||||||
const (
|
const (
|
||||||
// MiterJoin - Connected segments are joined by extending their outside edges
|
// MiterJoin - Connected segments are joined by extending their outside edges
|
||||||
// to connect at a single point, with the effect of filling an additional
|
// to connect at a single point, with the effect of filling an additional
|
||||||
// lozenge-shaped area. This setting is affected by the miterLimit property
|
// lozenge-shaped area. This setting is affected by the miterLimit property
|
||||||
MiterJoin = 0
|
MiterJoin LineJoin = 0
|
||||||
|
|
||||||
// RoundJoin - rounds off the corners of a shape by filling an additional sector
|
// RoundJoin - rounds off the corners of a shape by filling an additional sector
|
||||||
// of disc centered at the common endpoint of connected segments.
|
// of disc centered at the common endpoint of connected segments.
|
||||||
// The radius for these rounded corners is equal to the line width.
|
// The radius for these rounded corners is equal to the line width.
|
||||||
RoundJoin = 1
|
RoundJoin LineJoin = 1
|
||||||
|
|
||||||
// BevelJoin - Fills an additional triangular area between the common endpoint
|
// BevelJoin - Fills an additional triangular area between the common endpoint
|
||||||
// of connected segments, and the separate outside rectangular corners of each segment.
|
// of connected segments, and the separate outside rectangular corners of each segment.
|
||||||
BevelJoin = 2
|
BevelJoin LineJoin = 2
|
||||||
|
|
||||||
// ButtCap - the ends of lines are squared off at the endpoints. Default value.
|
// ButtCap - the ends of lines are squared off at the endpoints. Default value.
|
||||||
ButtCap = 0
|
ButtCap LineCap = 0
|
||||||
|
|
||||||
// RoundCap - the ends of lines are rounded.
|
// RoundCap - the ends of lines are rounded.
|
||||||
RoundCap = 1
|
RoundCap LineCap = 1
|
||||||
|
|
||||||
// SquareCap - the ends of lines are squared off by adding a box with an equal width
|
// SquareCap - the ends of lines are squared off by adding a box with an equal width
|
||||||
// and half the height of the line's thickness.
|
// and half the height of the line's thickness.
|
||||||
SquareCap = 2
|
SquareCap LineCap = 2
|
||||||
|
|
||||||
// AlphabeticBaseline - the text baseline is the normal alphabetic baseline. Default value.
|
// AlphabeticBaseline - the text baseline is the normal alphabetic baseline. Default value.
|
||||||
AlphabeticBaseline = 0
|
AlphabeticBaseline = 0
|
||||||
|
|
||||||
// TopBaseline - the text baseline is the top of the em square.
|
// TopBaseline - the text baseline is the top of the em square.
|
||||||
TopBaseline = 1
|
TopBaseline = 1
|
||||||
|
|
||||||
// MiddleBaseline - the text baseline is the middle of the em square.
|
// MiddleBaseline - the text baseline is the middle of the em square.
|
||||||
MiddleBaseline = 2
|
MiddleBaseline = 2
|
||||||
|
|
||||||
// BottomBaseline - the text baseline is the bottom of the bounding box.
|
// BottomBaseline - the text baseline is the bottom of the bounding box.
|
||||||
// This differs from the ideographic baseline in that the ideographic baseline doesn't consider descenders.
|
// This differs from the ideographic baseline in that the ideographic baseline doesn't consider descenders.
|
||||||
BottomBaseline = 3
|
BottomBaseline = 3
|
||||||
|
|
||||||
// HangingBaseline - the text baseline is the hanging baseline. (Used by Tibetan and other Indic scripts.)
|
// HangingBaseline - the text baseline is the hanging baseline. (Used by Tibetan and other Indic scripts.)
|
||||||
HangingBaseline = 4
|
HangingBaseline = 4
|
||||||
|
|
||||||
// IdeographicBaseline - the text baseline is the ideographic baseline; this is
|
// IdeographicBaseline - the text baseline is the ideographic baseline; this is
|
||||||
// the bottom of the body of the characters, if the main body of characters protrudes
|
// the bottom of the body of the characters, if the main body of characters protrudes
|
||||||
// beneath the alphabetic baseline. (Used by Chinese, Japanese, and Korean scripts.)
|
// beneath the alphabetic baseline. (Used by Chinese, Japanese, and Korean scripts.)
|
||||||
|
|
@ -47,6 +62,7 @@ const (
|
||||||
// StartAlign - the text is aligned at the normal start of the line (left-aligned
|
// StartAlign - the text is aligned at the normal start of the line (left-aligned
|
||||||
// for left-to-right locales, right-aligned for right-to-left locales).
|
// for left-to-right locales, right-aligned for right-to-left locales).
|
||||||
StartAlign = 3
|
StartAlign = 3
|
||||||
|
|
||||||
// EndAlign - the text is aligned at the normal end of the line (right-aligned
|
// EndAlign - the text is aligned at the normal end of the line (right-aligned
|
||||||
// for left-to-right locales, left-aligned for right-to-left locales).
|
// for left-to-right locales, left-aligned for right-to-left locales).
|
||||||
EndAlign = 4
|
EndAlign = 4
|
||||||
|
|
@ -113,15 +129,15 @@ type Canvas interface {
|
||||||
ClipPath(path Path)
|
ClipPath(path Path)
|
||||||
|
|
||||||
// SetScale adds a scaling transformation to the canvas units horizontally and/or vertically.
|
// SetScale adds a scaling transformation to the canvas units horizontally and/or vertically.
|
||||||
// x - scaling factor in the horizontal direction. A negative value flips pixels across
|
// * x - scaling factor in the horizontal direction. A negative value flips pixels across
|
||||||
// the vertical axis. A value of 1 results in no horizontal scaling;
|
// the vertical axis. A value of 1 results in no horizontal scaling;
|
||||||
// y - scaling factor in the vertical direction. A negative value flips pixels across
|
// * y - scaling factor in the vertical direction. A negative value flips pixels across
|
||||||
// the horizontal axis. A value of 1 results in no vertical scaling.
|
// the horizontal axis. A value of 1 results in no vertical scaling.
|
||||||
SetScale(x, y float64)
|
SetScale(x, y float64)
|
||||||
|
|
||||||
// SetTranslation adds a translation transformation to the current matrix.
|
// SetTranslation adds a translation transformation to the current matrix.
|
||||||
// x - distance to move in the horizontal direction. Positive values are to the right, and negative to the left;
|
// * x - distance to move in the horizontal direction. Positive values are to the right, and negative to the left;
|
||||||
// y - distance to move in the vertical direction. Positive values are down, and negative are up.
|
// * y - distance to move in the vertical direction. Positive values are down, and negative are up.
|
||||||
SetTranslation(x, y float64)
|
SetTranslation(x, y float64)
|
||||||
|
|
||||||
// SetRotation adds a rotation to the transformation matrix.
|
// SetRotation adds a rotation to the transformation matrix.
|
||||||
|
|
@ -131,12 +147,13 @@ type Canvas interface {
|
||||||
// SetTransformation multiplies the current transformation with the matrix described by the arguments
|
// SetTransformation multiplies the current transformation with the matrix described by the arguments
|
||||||
// of this method. This lets you scale, rotate, translate (move), and skew the context.
|
// of this method. This lets you scale, rotate, translate (move), and skew the context.
|
||||||
// The transformation matrix is described by:
|
// The transformation matrix is described by:
|
||||||
// ⎡ xScale xSkew dx ⎤
|
// ⎡ xScale xSkew dx ⎤
|
||||||
// ⎢ ySkew yScale dy ⎥
|
// ⎢ ySkew yScale dy ⎥
|
||||||
// ⎣ 0 0 1 ⎦
|
// ⎣ 0 0 1 ⎦
|
||||||
// xScale, yScale - horizontal and vertical scaling. A value of 1 results in no scaling;
|
// where
|
||||||
// xSkew, ySkew - horizontal and vertical skewing;
|
// * xScale, yScale - horizontal and vertical scaling. A value of 1 results in no scaling;
|
||||||
// dx, dy - horizontal and vertical translation (moving).
|
// * xSkew, ySkew - horizontal and vertical skewing;
|
||||||
|
// * dx, dy - horizontal and vertical translation (moving).
|
||||||
SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64)
|
SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64)
|
||||||
|
|
||||||
// ResetTransformation resets the current transform to the identity matrix
|
// ResetTransformation resets the current transform to the identity matrix
|
||||||
|
|
@ -149,45 +166,64 @@ type Canvas interface {
|
||||||
SetSolidColorStrokeStyle(color Color)
|
SetSolidColorStrokeStyle(color Color)
|
||||||
|
|
||||||
// SetLinearGradientFillStyle sets a gradient along the line connecting two given coordinates to use inside shapes
|
// SetLinearGradientFillStyle sets a gradient along the line connecting two given coordinates to use inside shapes
|
||||||
// x0, y0 - coordinates of the start point;
|
// * x0, y0 - coordinates of the start point;
|
||||||
// x1, y1 - coordinates of the end point;
|
// * x1, y1 - coordinates of the end point;
|
||||||
// startColor, endColor - the start and end color
|
// * startColor, endColor - the start and end color
|
||||||
// stopPoints - the array of stop points
|
// * stopPoints - the array of stop points
|
||||||
SetLinearGradientFillStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint)
|
SetLinearGradientFillStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
// SetLinearGradientStrokeStyle sets a gradient along the line connecting two given coordinates to use for the strokes (outlines) around shapes
|
// SetLinearGradientStrokeStyle sets a gradient along the line connecting two given coordinates to use for the strokes (outlines) around shapes
|
||||||
// x0, y0 - coordinates of the start point;
|
// * x0, y0 - coordinates of the start point;
|
||||||
// x1, y1 - coordinates of the end point;
|
// * x1, y1 - coordinates of the end point;
|
||||||
// color0, color1 - the start and end color
|
// * color0, color1 - the start and end color
|
||||||
// stopPoints - the array of stop points
|
// * stopPoints - the array of stop points
|
||||||
SetLinearGradientStrokeStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint)
|
SetLinearGradientStrokeStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
// SetRadialGradientFillStyle sets a radial gradient using the size and coordinates of two circles
|
// SetRadialGradientFillStyle sets a radial gradient using the size and coordinates of two circles
|
||||||
// to use inside shapes
|
// to use inside shapes
|
||||||
// x0, y0 - coordinates of the center of the start circle;
|
// * x0, y0 - coordinates of the center of the start circle;
|
||||||
// r0 - the radius of the start circle;
|
// * r0 - the radius of the start circle;
|
||||||
// x1, y1 - coordinates the center of the end circle;
|
// * x1, y1 - coordinates the center of the end circle;
|
||||||
// r1 - the radius of the end circle;
|
// * r1 - the radius of the end circle;
|
||||||
// color0, color1 - the start and end color
|
// * color0, color1 - the start and end color
|
||||||
// stopPoints - the array of stop points
|
// * stopPoints - the array of stop points
|
||||||
SetRadialGradientFillStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
SetRadialGradientFillStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
// SetRadialGradientStrokeStyle sets a radial gradient using the size and coordinates of two circles
|
// SetRadialGradientStrokeStyle sets a radial gradient using the size and coordinates of two circles
|
||||||
// to use for the strokes (outlines) around shapes
|
// to use for the strokes (outlines) around shapes
|
||||||
// x0, y0 - coordinates of the center of the start circle;
|
// * x0, y0 - coordinates of the center of the start circle;
|
||||||
// r0 - the radius of the start circle;
|
// * r0 - the radius of the start circle;
|
||||||
// x1, y1 - coordinates the center of the end circle;
|
// * x1, y1 - coordinates the center of the end circle;
|
||||||
// r1 - the radius of the end circle;
|
// * r1 - the radius of the end circle;
|
||||||
// color0, color1 - the start and end color
|
// * color0, color1 - the start and end color
|
||||||
// stopPoints - the array of stop points
|
// * stopPoints - the array of stop points
|
||||||
SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
|
// SetConicGradientFillStyle sets a conic gradient around a point
|
||||||
|
// to use inside shapes
|
||||||
|
// * x, y - coordinates of the center of the conic gradient in pilels;
|
||||||
|
// * 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;
|
||||||
|
// * stopPoints - the array of stop points. The Pos field of GradientPoint, in the range from 0 to 1, specifies the angle in turns.
|
||||||
|
SetConicGradientFillStyle(x, y, startAngle float64, startColor, endColor Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
|
// SetConicGradientFillStyle sets a conic gradient around a point
|
||||||
|
// to use inside shapes
|
||||||
|
// * x, y - coordinates of the center of the conic gradient in pilels;
|
||||||
|
// * 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;
|
||||||
|
// * stopPoints - the array of stop points. The Pos field of GradientPoint, in the range from 0 to 1, specifies the angle in turns.
|
||||||
|
SetConicGradientStrokeStyle(x, y, startAngle float64, startColor, endColor Color, stopPoints []GradientPoint)
|
||||||
|
|
||||||
// SetImageFillStyle set the image as the filling pattern.
|
// SetImageFillStyle set the image as the filling pattern.
|
||||||
// repeat - indicating how to repeat the pattern's image. Possible values are:
|
//
|
||||||
// NoRepeat (0) - neither direction,
|
// repeat - indicating how to repeat the pattern's image. Possible values are:
|
||||||
// RepeatXY (1) - both directions,
|
// * NoRepeat (0) - neither direction,
|
||||||
// RepeatX (2) - horizontal only,
|
// * RepeatXY (1) - both directions,
|
||||||
// RepeatY (3) - vertical only.
|
// * RepeatX (2) - horizontal only,
|
||||||
|
// * RepeatY (3) - vertical only.
|
||||||
SetImageFillStyle(image Image, repeat int)
|
SetImageFillStyle(image Image, repeat int)
|
||||||
|
|
||||||
// SetLineWidth the line width, in coordinate space units. Zero, negative, Infinity, and NaN values are ignored.
|
// SetLineWidth the line width, in coordinate space units. Zero, negative, Infinity, and NaN values are ignored.
|
||||||
|
|
@ -195,11 +231,11 @@ type Canvas interface {
|
||||||
|
|
||||||
// SetLineJoin sets the shape used to join two line segments where they meet.
|
// SetLineJoin sets the shape used to join two line segments where they meet.
|
||||||
// Valid values: MiterJoin (0), RoundJoin (1), BevelJoin (2). All other values are ignored.
|
// Valid values: MiterJoin (0), RoundJoin (1), BevelJoin (2). All other values are ignored.
|
||||||
SetLineJoin(join int)
|
SetLineJoin(join LineJoin)
|
||||||
|
|
||||||
// SetLineJoin sets the shape used to draw the end points of lines.
|
// SetLineJoin sets the shape used to draw the end points of lines.
|
||||||
// Valid values: ButtCap (0), RoundCap (1), SquareCap (2). All other values are ignored.
|
// Valid values: ButtCap (0), RoundCap (1), SquareCap (2). All other values are ignored.
|
||||||
SetLineCap(cap int)
|
SetLineCap(cap LineCap)
|
||||||
|
|
||||||
// SetLineDash sets the line dash pattern used when stroking lines.
|
// SetLineDash sets the line dash pattern used when stroking lines.
|
||||||
// dash - an array of values that specify alternating lengths of lines and gaps which describe the pattern.
|
// dash - an array of values that specify alternating lengths of lines and gaps which describe the pattern.
|
||||||
|
|
@ -224,56 +260,67 @@ type Canvas interface {
|
||||||
SetTextAlign(align int)
|
SetTextAlign(align int)
|
||||||
|
|
||||||
// SetShadow sets shadow parameters:
|
// SetShadow sets shadow parameters:
|
||||||
// offsetX, offsetY - the distance that shadows will be offset horizontally and vertically;
|
// * offsetX, offsetY - the distance that shadows will be offset horizontally and vertically;
|
||||||
// blur - the amount of blur applied to shadows. Must be non-negative;
|
// * blur - the amount of blur applied to shadows. Must be non-negative;
|
||||||
// color - the color of shadows.
|
// * color - the color of shadows.
|
||||||
SetShadow(offsetX, offsetY, blur float64, color Color)
|
SetShadow(offsetX, offsetY, blur float64, color Color)
|
||||||
|
|
||||||
// ResetShadow sets shadow parameters to default values (invisible shadow)
|
// ResetShadow sets shadow parameters to default values (invisible shadow)
|
||||||
ResetShadow()
|
ResetShadow()
|
||||||
|
|
||||||
// ClearRect erases the pixels in a rectangular area by setting them to transparent black
|
// ClearRect erases the pixels in a rectangular area by setting them to transparent black
|
||||||
ClearRect(x, y, width, height float64)
|
ClearRect(x, y, width, height float64)
|
||||||
|
|
||||||
// FillRect draws a rectangle that is filled according to the current FillStyle.
|
// FillRect draws a rectangle that is filled according to the current FillStyle.
|
||||||
FillRect(x, y, width, height float64)
|
FillRect(x, y, width, height float64)
|
||||||
|
|
||||||
// StrokeRect draws a rectangle that is stroked (outlined) according to the current strokeStyle
|
// StrokeRect draws a rectangle that is stroked (outlined) according to the current strokeStyle
|
||||||
// and other context settings
|
// and other context settings
|
||||||
StrokeRect(x, y, width, height float64)
|
StrokeRect(x, y, width, height float64)
|
||||||
|
|
||||||
// FillAndStrokeRect draws a rectangle that is filled according to the current FillStyle and
|
// FillAndStrokeRect draws a rectangle that is filled according to the current FillStyle and
|
||||||
// is stroked (outlined) according to the current strokeStyle and other context settings
|
// is stroked (outlined) according to the current strokeStyle and other context settings
|
||||||
FillAndStrokeRect(x, y, width, height float64)
|
FillAndStrokeRect(x, y, width, height float64)
|
||||||
|
|
||||||
// FillRoundedRect draws a rounded rectangle that is filled according to the current FillStyle.
|
// FillRoundedRect draws a rounded rectangle that is filled according to the current FillStyle.
|
||||||
FillRoundedRect(x, y, width, height, r float64)
|
FillRoundedRect(x, y, width, height, r float64)
|
||||||
|
|
||||||
// StrokeRoundedRect draws a rounded rectangle that is stroked (outlined) according
|
// StrokeRoundedRect draws a rounded rectangle that is stroked (outlined) according
|
||||||
// to the current strokeStyle and other context settings
|
// to the current strokeStyle and other context settings
|
||||||
StrokeRoundedRect(x, y, width, height, r float64)
|
StrokeRoundedRect(x, y, width, height, r float64)
|
||||||
|
|
||||||
// FillAndStrokeRoundedRect draws a rounded rectangle that is filled according to the current FillStyle
|
// FillAndStrokeRoundedRect draws a rounded rectangle that is filled according to the current FillStyle
|
||||||
// and is stroked (outlined) according to the current strokeStyle and other context settings
|
// and is stroked (outlined) according to the current strokeStyle and other context settings
|
||||||
FillAndStrokeRoundedRect(x, y, width, height, r float64)
|
FillAndStrokeRoundedRect(x, y, width, height, r float64)
|
||||||
|
|
||||||
// FillEllipse draws a ellipse that is filled according to the current FillStyle.
|
// FillEllipse draws a ellipse that is filled according to the current FillStyle.
|
||||||
// x, y - coordinates of the ellipse's center;
|
// * x, y - coordinates of the ellipse's center;
|
||||||
// radiusX - the ellipse's major-axis radius. Must be non-negative;
|
// * radiusX - the ellipse's major-axis radius. Must be non-negative;
|
||||||
// radiusY - the ellipse's minor-axis radius. Must be non-negative;
|
// * radiusY - the ellipse's minor-axis radius. Must be non-negative;
|
||||||
// rotation - the rotation of the ellipse, expressed in radians.
|
// * rotation - the rotation of the ellipse, expressed in radians.
|
||||||
FillEllipse(x, y, radiusX, radiusY, rotation float64)
|
FillEllipse(x, y, radiusX, radiusY, rotation float64)
|
||||||
|
|
||||||
// StrokeRoundedRect draws a ellipse that is stroked (outlined) according
|
// StrokeRoundedRect draws a ellipse that is stroked (outlined) according
|
||||||
// to the current strokeStyle and other context settings
|
// to the current strokeStyle and other context settings
|
||||||
StrokeEllipse(x, y, radiusX, radiusY, rotation float64)
|
StrokeEllipse(x, y, radiusX, radiusY, rotation float64)
|
||||||
|
|
||||||
// FillAndStrokeEllipse draws a ellipse that is filled according to the current FillStyle
|
// FillAndStrokeEllipse draws a ellipse that is filled according to the current FillStyle
|
||||||
// and is stroked (outlined) according to the current strokeStyle and other context settings
|
// and is stroked (outlined) according to the current strokeStyle and other context settings
|
||||||
FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64)
|
FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64)
|
||||||
|
|
||||||
// NewPath creates a new Path object
|
// NewPath creates a new Path object
|
||||||
NewPath() Path
|
NewPath() Path
|
||||||
|
|
||||||
// NewPathFromSvg creates a new Path and initialize it by a string consisting of SVG path data
|
// NewPathFromSvg creates a new Path and initialize it by a string consisting of SVG path data
|
||||||
NewPathFromSvg(data string) Path
|
NewPathFromSvg(data string) Path
|
||||||
|
|
||||||
// FillPath draws a path that is filled according to the current FillStyle.
|
// FillPath draws a path that is filled according to the current FillStyle.
|
||||||
FillPath(path Path)
|
FillPath(path Path)
|
||||||
|
|
||||||
// StrokePath draws a path that is stroked (outlined) according to the current strokeStyle
|
// StrokePath draws a path that is stroked (outlined) according to the current strokeStyle
|
||||||
// and other context settings
|
// and other context settings
|
||||||
StrokePath(path Path)
|
StrokePath(path Path)
|
||||||
|
|
||||||
// FillAndStrokeRect draws a path that is filled according to the current FillStyle and
|
// FillAndStrokeRect draws a path that is filled according to the current FillStyle and
|
||||||
// is stroked (outlined) according to the current strokeStyle and other context settings
|
// is stroked (outlined) according to the current strokeStyle and other context settings
|
||||||
FillAndStrokePath(path Path)
|
FillAndStrokePath(path Path)
|
||||||
|
|
@ -284,14 +331,17 @@ type Canvas interface {
|
||||||
// FillText draws a text string at the specified coordinates, filling the string's characters
|
// FillText draws a text string at the specified coordinates, filling the string's characters
|
||||||
// with the current FillStyle
|
// with the current FillStyle
|
||||||
FillText(x, y float64, text string)
|
FillText(x, y float64, text string)
|
||||||
|
|
||||||
// StrokeText strokes — that is, draws the outlines of — the characters of a text string
|
// StrokeText strokes — that is, draws the outlines of — the characters of a text string
|
||||||
// at the specified coordinates
|
// at the specified coordinates
|
||||||
StrokeText(x, y float64, text string)
|
StrokeText(x, y float64, text string)
|
||||||
|
|
||||||
// DrawImage draws the image at the (x, y) position
|
// DrawImage draws the image at the (x, y) position
|
||||||
DrawImage(x, y float64, image Image)
|
DrawImage(x, y float64, image Image)
|
||||||
|
|
||||||
// DrawImageInRect draws the image in the rectangle (x, y, width, height), scaling in height and width if necessary
|
// DrawImageInRect draws the image in the rectangle (x, y, width, height), scaling in height and width if necessary
|
||||||
DrawImageInRect(x, y, width, height float64, image Image)
|
DrawImageInRect(x, y, width, height float64, image Image)
|
||||||
|
|
||||||
// DrawImageFragment draws the fragment (described by srcX, srcY, srcWidth, srcHeight) of image
|
// DrawImageFragment draws the fragment (described by srcX, srcY, srcWidth, srcHeight) of image
|
||||||
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
|
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
|
||||||
DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image)
|
DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image)
|
||||||
|
|
@ -435,6 +485,30 @@ func (canvas *canvasData) SetRadialGradientStrokeStyle(x0, y0, r0 float64, color
|
||||||
canvas.session.updateCanvasProperty("strokeStyle", gradient)
|
canvas.session.updateCanvasProperty("strokeStyle", gradient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (canvas *canvasData) createConicGradient(x, y, startAngle float64, startColor, endColor Color, stopPoints []GradientPoint) any {
|
||||||
|
gradient := canvas.session.createCanvasVar("createConicGradient", startAngle, x, y)
|
||||||
|
canvas.session.callCanvasVarFunc(gradient, "addColorStop", 0, startColor.cssString())
|
||||||
|
|
||||||
|
for _, point := range stopPoints {
|
||||||
|
if point.Offset >= 0 && point.Offset <= 1 {
|
||||||
|
canvas.session.callCanvasVarFunc(gradient, "addColorStop", point.Offset, point.Color.cssString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.session.callCanvasVarFunc(gradient, "addColorStop", 1, endColor.cssString())
|
||||||
|
return gradient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (canvas *canvasData) SetConicGradientFillStyle(x, y, startAngle float64, startColor, endColor Color, stopPoints []GradientPoint) {
|
||||||
|
gradient := canvas.createConicGradient(x, y, startAngle, startColor, endColor, stopPoints)
|
||||||
|
canvas.session.updateCanvasProperty("fillStyle", gradient)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (canvas *canvasData) SetConicGradientStrokeStyle(x, y, startAngle float64, startColor, endColor Color, stopPoints []GradientPoint) {
|
||||||
|
gradient := canvas.createConicGradient(x, y, startAngle, startColor, endColor, stopPoints)
|
||||||
|
canvas.session.updateCanvasProperty("strokeStyle", gradient)
|
||||||
|
}
|
||||||
|
|
||||||
func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) {
|
func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) {
|
||||||
if image == nil || image.LoadingStatus() != ImageReady {
|
if image == nil || image.LoadingStatus() != ImageReady {
|
||||||
return
|
return
|
||||||
|
|
@ -467,7 +541,7 @@ func (canvas *canvasData) SetLineWidth(width float64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvas *canvasData) SetLineJoin(join int) {
|
func (canvas *canvasData) SetLineJoin(join LineJoin) {
|
||||||
switch join {
|
switch join {
|
||||||
case MiterJoin:
|
case MiterJoin:
|
||||||
canvas.session.updateCanvasProperty("lineJoin", "miter")
|
canvas.session.updateCanvasProperty("lineJoin", "miter")
|
||||||
|
|
@ -480,7 +554,7 @@ func (canvas *canvasData) SetLineJoin(join int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvas *canvasData) SetLineCap(cap int) {
|
func (canvas *canvasData) SetLineCap(cap LineCap) {
|
||||||
switch cap {
|
switch cap {
|
||||||
case ButtCap:
|
case ButtCap:
|
||||||
canvas.session.updateCanvasProperty("lineCap", "butt")
|
canvas.session.updateCanvasProperty("lineCap", "butt")
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// DrawFunction is the constant for "draw-function" property tag.
|
// DrawFunction is the constant for "draw-function" property tag.
|
||||||
//
|
//
|
||||||
// Used by `CanvasView`.
|
// Used by `CanvasView`.
|
||||||
// Property sets the draw function of `CanvasView`.
|
// Property sets the draw function of `CanvasView`.
|
||||||
//
|
//
|
||||||
// Supported types: `func(Canvas)`.
|
// Supported types: `func(Canvas)`.
|
||||||
const DrawFunction = "draw-function"
|
const DrawFunction PropertyName = "draw-function"
|
||||||
|
|
||||||
// CanvasView interface of a custom draw view
|
// CanvasView interface of a custom draw view
|
||||||
type CanvasView interface {
|
type CanvasView interface {
|
||||||
|
|
@ -20,7 +18,6 @@ type CanvasView interface {
|
||||||
|
|
||||||
type canvasViewData struct {
|
type canvasViewData struct {
|
||||||
viewData
|
viewData
|
||||||
drawer func(Canvas)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCanvasView creates the new custom draw view
|
// NewCanvasView creates the new custom draw view
|
||||||
|
|
@ -32,21 +29,21 @@ func NewCanvasView(session Session, params Params) CanvasView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCanvasView(session Session) View {
|
func newCanvasView(session Session) View {
|
||||||
return NewCanvasView(session, nil)
|
return new(canvasViewData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of ViewsContainer by default values
|
// Init initialize fields of ViewsContainer by default values
|
||||||
func (canvasView *canvasViewData) init(session Session) {
|
func (canvasView *canvasViewData) init(session Session) {
|
||||||
canvasView.viewData.init(session)
|
canvasView.viewData.init(session)
|
||||||
canvasView.tag = "CanvasView"
|
canvasView.tag = "CanvasView"
|
||||||
|
canvasView.normalize = normalizeCanvasViewTag
|
||||||
|
canvasView.set = canvasView.setFunc
|
||||||
|
canvasView.remove = canvasView.removeFunc
|
||||||
|
canvasView.changed = canvasView.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) String() string {
|
func normalizeCanvasViewTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(canvasView, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (canvasView *canvasViewData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case "draw-func":
|
case "draw-func":
|
||||||
tag = DrawFunction
|
tag = DrawFunction
|
||||||
|
|
@ -54,51 +51,39 @@ func (canvasView *canvasViewData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) Remove(tag string) {
|
func (canvasView *canvasViewData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
canvasView.remove(canvasView.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (canvasView *canvasViewData) remove(tag string) {
|
|
||||||
if tag == DrawFunction {
|
if tag == DrawFunction {
|
||||||
canvasView.drawer = nil
|
if canvasView.getRaw(DrawFunction) != nil {
|
||||||
canvasView.Redraw()
|
canvasView.setRaw(DrawFunction, nil)
|
||||||
canvasView.propertyChangedEvent(tag)
|
canvasView.Redraw()
|
||||||
} else {
|
return []PropertyName{DrawFunction}
|
||||||
canvasView.viewData.remove(tag)
|
}
|
||||||
|
return []PropertyName{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return canvasView.viewData.removeFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) Set(tag string, value any) bool {
|
func (canvasView *canvasViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
return canvasView.set(canvasView.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (canvasView *canvasViewData) set(tag string, value any) bool {
|
|
||||||
if tag == DrawFunction {
|
if tag == DrawFunction {
|
||||||
if value == nil {
|
if fn, ok := value.(func(Canvas)); ok {
|
||||||
canvasView.drawer = nil
|
canvasView.setRaw(DrawFunction, fn)
|
||||||
} else if fn, ok := value.(func(Canvas)); ok {
|
|
||||||
canvasView.drawer = fn
|
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
canvasView.Redraw()
|
return []PropertyName{DrawFunction}
|
||||||
canvasView.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvasView.viewData.set(tag, value)
|
return canvasView.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) Get(tag string) any {
|
func (canvasView *canvasViewData) propertyChanged(tag PropertyName) {
|
||||||
return canvasView.get(canvasView.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (canvasView *canvasViewData) get(tag string) any {
|
|
||||||
if tag == DrawFunction {
|
if tag == DrawFunction {
|
||||||
return canvasView.drawer
|
canvasView.Redraw()
|
||||||
|
} else {
|
||||||
|
canvasView.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
return canvasView.viewData.get(tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) htmlTag() string {
|
func (canvasView *canvasViewData) htmlTag() string {
|
||||||
|
|
@ -106,14 +91,14 @@ func (canvasView *canvasViewData) htmlTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) Redraw() {
|
func (canvasView *canvasViewData) Redraw() {
|
||||||
if canvasView.drawer != nil {
|
canvas := newCanvas(canvasView)
|
||||||
canvas := newCanvas(canvasView)
|
canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height)
|
||||||
canvas.ClearRect(0, 0, canvasView.frame.Width, canvasView.frame.Height)
|
if value := canvasView.getRaw(DrawFunction); value != nil {
|
||||||
if canvasView.drawer != nil {
|
if drawer, ok := value.(func(Canvas)); ok {
|
||||||
canvasView.drawer(canvas)
|
drawer(canvas)
|
||||||
}
|
}
|
||||||
canvas.finishDraw()
|
|
||||||
}
|
}
|
||||||
|
canvas.finishDraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (canvasView *canvasViewData) onResize(self View, x, y, width, height float64) {
|
func (canvasView *canvasViewData) onResize(self View, x, y, width, height float64) {
|
||||||
|
|
|
||||||
222
checkbox.go
222
checkbox.go
|
|
@ -10,17 +10,19 @@ import (
|
||||||
// Event occurs when the checkbox becomes checked/unchecked.
|
// Event occurs when the checkbox becomes checked/unchecked.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(checkbox rui.Checkbox, checked bool)`.
|
//
|
||||||
|
// func(checkbox rui.Checkbox, checked bool)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// checkbox - Interface of a checkbox which generated this event,
|
// - checkbox - Interface of a checkbox which generated this event,
|
||||||
// checked - Checkbox state.
|
// - checked - Checkbox state.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(checkbox rui.Checkbox)`,
|
//
|
||||||
// `func(checked bool)`,
|
// func(checkbox rui.Checkbox)
|
||||||
// `func()`.
|
// func(checked bool)
|
||||||
const CheckboxChangedEvent = "checkbox-event"
|
// func()
|
||||||
|
const CheckboxChangedEvent PropertyName = "checkbox-event"
|
||||||
|
|
||||||
// Checkbox represent a Checkbox view
|
// Checkbox represent a Checkbox view
|
||||||
type Checkbox interface {
|
type Checkbox interface {
|
||||||
|
|
@ -29,180 +31,123 @@ type Checkbox interface {
|
||||||
|
|
||||||
type checkboxData struct {
|
type checkboxData struct {
|
||||||
viewsContainerData
|
viewsContainerData
|
||||||
checkedListeners []func(Checkbox, bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCheckbox create new Checkbox object and return it
|
// NewCheckbox create new Checkbox object and return it
|
||||||
func NewCheckbox(session Session, params Params) Checkbox {
|
func NewCheckbox(session Session, params Params) Checkbox {
|
||||||
view := new(checkboxData)
|
view := new(checkboxData)
|
||||||
view.init(session)
|
view.init(session)
|
||||||
setInitParams(view, Params{
|
|
||||||
ClickEvent: checkboxClickListener,
|
|
||||||
KeyDownEvent: checkboxKeyListener,
|
|
||||||
})
|
|
||||||
setInitParams(view, params)
|
setInitParams(view, params)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCheckbox(session Session) View {
|
func newCheckbox(session Session) View {
|
||||||
return NewCheckbox(session, nil)
|
return new(checkboxData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) init(session Session) {
|
func (button *checkboxData) init(session Session) {
|
||||||
button.viewsContainerData.init(session)
|
button.viewsContainerData.init(session)
|
||||||
button.tag = "Checkbox"
|
button.tag = "Checkbox"
|
||||||
button.systemClass = "ruiGridLayout ruiCheckbox"
|
button.systemClass = "ruiGridLayout ruiCheckbox"
|
||||||
button.checkedListeners = []func(Checkbox, bool){}
|
button.set = button.setFunc
|
||||||
}
|
button.remove = button.removeFunc
|
||||||
|
button.changed = button.propertyChanged
|
||||||
|
|
||||||
func (button *checkboxData) String() string {
|
button.setRaw(ClickEvent, []func(View, MouseEvent){checkboxClickListener})
|
||||||
return getViewString(button, nil)
|
button.setRaw(KeyDownEvent, []func(View, KeyEvent){checkboxKeyListener})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) Focusable() bool {
|
func (button *checkboxData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) Get(tag string) any {
|
func (button *checkboxData) propertyChanged(tag PropertyName) {
|
||||||
switch strings.ToLower(tag) {
|
|
||||||
case CheckboxChangedEvent:
|
|
||||||
return button.checkedListeners
|
|
||||||
}
|
|
||||||
|
|
||||||
return button.viewsContainerData.Get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (button *checkboxData) Set(tag string, value any) bool {
|
|
||||||
return button.set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (button *checkboxData) set(tag string, value any) bool {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case CheckboxChangedEvent:
|
|
||||||
if !button.setChangedListener(value) {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
case Checked:
|
case Checked:
|
||||||
oldChecked := button.checked()
|
session := button.Session()
|
||||||
if !button.setBoolProperty(Checked, value) {
|
checked := IsCheckboxChecked(button)
|
||||||
return false
|
if listeners := GetCheckboxChangedListeners(button); len(listeners) > 0 {
|
||||||
}
|
for _, listener := range listeners {
|
||||||
if button.created {
|
listener(button, checked)
|
||||||
checked := button.checked()
|
|
||||||
if checked != oldChecked {
|
|
||||||
button.changedCheckboxState(checked)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
checkboxHtml(button, buffer, checked)
|
||||||
|
session.updateInnerHTML(button.htmlID()+"checkbox", buffer.String())
|
||||||
|
|
||||||
case CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
case CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
||||||
if !button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
htmlID := button.htmlID()
|
||||||
return false
|
session := button.Session()
|
||||||
}
|
updateCSSStyle(htmlID, session)
|
||||||
if button.created {
|
updateInnerHTML(htmlID, session)
|
||||||
htmlID := button.htmlID()
|
|
||||||
updateCSSStyle(htmlID, button.session)
|
|
||||||
updateInnerHTML(htmlID, button.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case VerticalAlign:
|
case VerticalAlign:
|
||||||
if !button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
button.Session().updateCSSProperty(button.htmlID()+"content", "align-items", checkboxVerticalAlignCSS(button))
|
||||||
return false
|
|
||||||
}
|
|
||||||
if button.created {
|
|
||||||
button.session.updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign())
|
|
||||||
}
|
|
||||||
|
|
||||||
case HorizontalAlign:
|
case HorizontalAlign:
|
||||||
if !button.setEnumProperty(tag, value, enumProperties[tag].values) {
|
button.Session().updateCSSProperty(button.htmlID()+"content", "justify-items", checkboxHorizontalAlignCSS(button))
|
||||||
return false
|
|
||||||
}
|
|
||||||
if button.created {
|
|
||||||
button.session.updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign())
|
|
||||||
}
|
|
||||||
|
|
||||||
case CellVerticalAlign, CellHorizontalAlign, CellWidth, CellHeight:
|
|
||||||
return false
|
|
||||||
|
|
||||||
case AccentColor:
|
case AccentColor:
|
||||||
if !button.setColorProperty(AccentColor, value) {
|
updateInnerHTML(button.htmlID(), button.Session())
|
||||||
return false
|
|
||||||
}
|
|
||||||
if button.created {
|
|
||||||
updateInnerHTML(button.htmlID(), button.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return button.viewsContainerData.set(tag, value)
|
button.viewsContainerData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
button.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) Remove(tag string) {
|
func (button *checkboxData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
button.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (button *checkboxData) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case ClickEvent:
|
case ClickEvent:
|
||||||
if !button.viewsContainerData.set(ClickEvent, checkboxClickListener) {
|
if listeners, ok := valueToOneArgEventListeners[View, MouseEvent](value); ok && listeners != nil {
|
||||||
delete(button.properties, tag)
|
listeners = append(listeners, checkboxClickListener)
|
||||||
|
button.setRaw(tag, listeners)
|
||||||
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
case KeyDownEvent:
|
case KeyDownEvent:
|
||||||
if !button.viewsContainerData.set(KeyDownEvent, checkboxKeyListener) {
|
if listeners, ok := valueToOneArgEventListeners[View, KeyEvent](value); ok && listeners != nil {
|
||||||
delete(button.properties, tag)
|
listeners = append(listeners, checkboxKeyListener)
|
||||||
|
button.setRaw(tag, listeners)
|
||||||
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
case CheckboxChangedEvent:
|
case CheckboxChangedEvent:
|
||||||
if len(button.checkedListeners) > 0 {
|
return setOneArgEventListener[Checkbox, bool](button, tag, value)
|
||||||
button.checkedListeners = []func(Checkbox, bool){}
|
|
||||||
}
|
|
||||||
|
|
||||||
case Checked:
|
case Checked:
|
||||||
oldChecked := button.checked()
|
return setBoolProperty(button, Checked, value)
|
||||||
delete(button.properties, tag)
|
|
||||||
if button.created && oldChecked {
|
|
||||||
button.changedCheckboxState(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
case CheckboxHorizontalAlign, CheckboxVerticalAlign:
|
case CellVerticalAlign, CellHorizontalAlign, CellWidth, CellHeight:
|
||||||
delete(button.properties, tag)
|
ErrorLogF(`"%s" property is not compatible with the BoundsProperty`, string(tag))
|
||||||
if button.created {
|
return nil
|
||||||
htmlID := button.htmlID()
|
|
||||||
updateCSSStyle(htmlID, button.session)
|
|
||||||
updateInnerHTML(htmlID, button.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case VerticalAlign:
|
|
||||||
delete(button.properties, tag)
|
|
||||||
if button.created {
|
|
||||||
button.session.updateCSSProperty(button.htmlID()+"content", "align-items", button.cssVerticalAlign())
|
|
||||||
}
|
|
||||||
|
|
||||||
case HorizontalAlign:
|
|
||||||
delete(button.properties, tag)
|
|
||||||
if button.created {
|
|
||||||
button.session.updateCSSProperty(button.htmlID()+"content", "justify-items", button.cssHorizontalAlign())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
button.viewsContainerData.remove(tag)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
button.propertyChangedEvent(tag)
|
|
||||||
|
return button.viewsContainerData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) checked() bool {
|
func (button *checkboxData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
checked, _ := boolProperty(button, Checked, button.Session())
|
switch tag {
|
||||||
return checked
|
case ClickEvent:
|
||||||
|
button.setRaw(ClickEvent, []func(View, MouseEvent){checkboxClickListener})
|
||||||
|
return []PropertyName{ClickEvent}
|
||||||
|
|
||||||
|
case KeyDownEvent:
|
||||||
|
button.setRaw(KeyDownEvent, []func(View, KeyEvent){checkboxKeyListener})
|
||||||
|
return []PropertyName{ClickEvent}
|
||||||
|
}
|
||||||
|
|
||||||
|
return button.viewsContainerData.removeFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (button *checkboxData) changedCheckboxState(state bool) {
|
func (button *checkboxData) changedCheckboxState(state bool) {
|
||||||
for _, listener := range button.checkedListeners {
|
for _, listener := range GetCheckboxChangedListeners(button) {
|
||||||
listener(button, state)
|
listener(button, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,8 +157,9 @@ func (button *checkboxData) changedCheckboxState(state bool) {
|
||||||
button.htmlCheckbox(buffer, state)
|
button.htmlCheckbox(buffer, state)
|
||||||
button.Session().updateInnerHTML(button.htmlID()+"checkbox", buffer.String())
|
button.Session().updateInnerHTML(button.htmlID()+"checkbox", buffer.String())
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func checkboxClickListener(view View) {
|
func checkboxClickListener(view View, _ MouseEvent) {
|
||||||
view.Set(Checked, !IsCheckboxChecked(view))
|
view.Set(Checked, !IsCheckboxChecked(view))
|
||||||
BlurView(view)
|
BlurView(view)
|
||||||
}
|
}
|
||||||
|
|
@ -225,17 +171,6 @@ func checkboxKeyListener(view View, event KeyEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) setChangedListener(value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[Checkbox, bool](value)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(Checkbox, bool){}
|
|
||||||
}
|
|
||||||
button.checkedListeners = listeners
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
|
func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
|
||||||
session := button.Session()
|
session := button.Session()
|
||||||
vAlign := GetCheckboxVerticalAlign(button)
|
vAlign := GetCheckboxVerticalAlign(button)
|
||||||
|
|
@ -265,7 +200,8 @@ func (button *checkboxData) cssStyle(self View, builder cssBuilder) {
|
||||||
button.viewsContainerData.cssStyle(self, builder)
|
button.viewsContainerData.cssStyle(self, builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) {
|
func checkboxHtml(button View, buffer *strings.Builder, checked bool) (int, int) {
|
||||||
|
//func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) {
|
||||||
vAlign := GetCheckboxVerticalAlign(button)
|
vAlign := GetCheckboxVerticalAlign(button)
|
||||||
hAlign := GetCheckboxHorizontalAlign(button)
|
hAlign := GetCheckboxHorizontalAlign(button)
|
||||||
|
|
||||||
|
|
@ -317,7 +253,7 @@ func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool)
|
||||||
|
|
||||||
func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
||||||
vCheckboxAlign, hCheckboxAlign := button.htmlCheckbox(buffer, IsCheckboxChecked(button))
|
vCheckboxAlign, hCheckboxAlign := checkboxHtml(button, buffer, IsCheckboxChecked(button))
|
||||||
|
|
||||||
buffer.WriteString(`<div id="`)
|
buffer.WriteString(`<div id="`)
|
||||||
buffer.WriteString(button.htmlID())
|
buffer.WriteString(button.htmlID())
|
||||||
|
|
@ -335,11 +271,11 @@ func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(" align-items: ")
|
buffer.WriteString(" align-items: ")
|
||||||
buffer.WriteString(button.cssVerticalAlign())
|
buffer.WriteString(checkboxVerticalAlignCSS(button))
|
||||||
buffer.WriteRune(';')
|
buffer.WriteRune(';')
|
||||||
|
|
||||||
buffer.WriteString(" justify-items: ")
|
buffer.WriteString(" justify-items: ")
|
||||||
buffer.WriteString(button.cssHorizontalAlign())
|
buffer.WriteString(checkboxHorizontalAlignCSS(button))
|
||||||
buffer.WriteRune(';')
|
buffer.WriteRune(';')
|
||||||
|
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
|
|
@ -347,8 +283,8 @@ func (button *checkboxData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
buffer.WriteString(`</div>`)
|
buffer.WriteString(`</div>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) cssHorizontalAlign() string {
|
func checkboxHorizontalAlignCSS(view View) string {
|
||||||
align := GetHorizontalAlign(button)
|
align := GetHorizontalAlign(view)
|
||||||
values := enumProperties[CellHorizontalAlign].cssValues
|
values := enumProperties[CellHorizontalAlign].cssValues
|
||||||
if align >= 0 && align < len(values) {
|
if align >= 0 && align < len(values) {
|
||||||
return values[align]
|
return values[align]
|
||||||
|
|
@ -356,8 +292,8 @@ func (button *checkboxData) cssHorizontalAlign() string {
|
||||||
return values[0]
|
return values[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (button *checkboxData) cssVerticalAlign() string {
|
func checkboxVerticalAlignCSS(view View) string {
|
||||||
align := GetVerticalAlign(button)
|
align := GetVerticalAlign(view)
|
||||||
values := enumProperties[CellVerticalAlign].cssValues
|
values := enumProperties[CellVerticalAlign].cssValues
|
||||||
if align >= 0 && align < len(values) {
|
if align >= 0 && align < len(values) {
|
||||||
return values[align]
|
return values[align]
|
||||||
|
|
@ -387,5 +323,5 @@ func GetCheckboxHorizontalAlign(view View, subviewID ...string) int {
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetCheckboxChangedListeners(view View, subviewID ...string) []func(Checkbox, bool) {
|
||||||
return getEventListeners[Checkbox, bool](view, subviewID, CheckboxChangedEvent)
|
return getOneArgEventListeners[Checkbox, bool](view, subviewID, CheckboxChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,690 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClipShape string
|
||||||
|
|
||||||
|
const (
|
||||||
|
InsetClip ClipShape = "inset"
|
||||||
|
CircleClip ClipShape = "circle"
|
||||||
|
EllipseClip ClipShape = "ellipse"
|
||||||
|
PolygonClip ClipShape = "polygon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClipShapeProperty defines a View clipping area
|
||||||
|
type ClipShapeProperty interface {
|
||||||
|
Properties
|
||||||
|
fmt.Stringer
|
||||||
|
stringWriter
|
||||||
|
|
||||||
|
// Shape returns the clip shape type
|
||||||
|
Shape() ClipShape
|
||||||
|
cssStyle(session Session) string
|
||||||
|
valid(session Session) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type insetClipData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
type ellipseClipData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
type circleClipData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
type polygonClipData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClipShapeProperty creates ClipShapeProperty.
|
||||||
|
//
|
||||||
|
// The following properties can be used for shapes:
|
||||||
|
//
|
||||||
|
// InsetClip:
|
||||||
|
// - "top" (Top) - offset (SizeUnit) from the top border of a View;
|
||||||
|
// - "right" (Right) - offset (SizeUnit) from the right border of a View;
|
||||||
|
// - "bottom" (Bottom) - offset (SizeUnit) from the bottom border of a View;
|
||||||
|
// - "left" (Left) - offset (SizeUnit) from the left border of a View;
|
||||||
|
// - "radius" (Radius) - corner radius (RadiusProperty).
|
||||||
|
//
|
||||||
|
// CircleClip:
|
||||||
|
// - "x" (X) - x-axis position (SizeUnit) of the circle clip center;
|
||||||
|
// - "y" (Y) - y-axis position (SizeUnit) of the circle clip center;
|
||||||
|
// - "radius" (Radius) - radius (SizeUnit) of the circle clip center.
|
||||||
|
//
|
||||||
|
// EllipseClip:
|
||||||
|
// - "x" (X) - x-axis position (SizeUnit) of the ellipse clip center;
|
||||||
|
// - "y" (Y) - y-axis position (SizeUnit) of the ellipse clip center;
|
||||||
|
// - "radius-x" (RadiusX) - x-axis radius (SizeUnit) of the ellipse clip center;
|
||||||
|
// - "radius-y" (RadiusY) - y-axis radius (SizeUnit) of the ellipse clip center.
|
||||||
|
//
|
||||||
|
// PolygonClip:
|
||||||
|
// - "points" (Points) - an array ([]SizeUnit) of corner points of the polygon in the following order: x1, y1, x2, y2, ….
|
||||||
|
//
|
||||||
|
// The function will return nil if no properties are specified, unsupported properties are specified, or at least one property has an invalid value.
|
||||||
|
func NewClipShapeProperty(shape ClipShape, params Params) ClipShapeProperty {
|
||||||
|
if len(params) == 0 {
|
||||||
|
ErrorLog("No ClipShapeProperty params")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result ClipShapeProperty
|
||||||
|
|
||||||
|
switch shape {
|
||||||
|
case InsetClip:
|
||||||
|
clip := new(insetClipData)
|
||||||
|
clip.init()
|
||||||
|
result = clip
|
||||||
|
|
||||||
|
case CircleClip:
|
||||||
|
clip := new(circleClipData)
|
||||||
|
clip.init()
|
||||||
|
result = clip
|
||||||
|
|
||||||
|
case EllipseClip:
|
||||||
|
clip := new(ellipseClipData)
|
||||||
|
clip.init()
|
||||||
|
result = clip
|
||||||
|
|
||||||
|
case PolygonClip:
|
||||||
|
clip := new(polygonClipData)
|
||||||
|
clip.init()
|
||||||
|
result = clip
|
||||||
|
|
||||||
|
default:
|
||||||
|
ErrorLog("Unknown ClipShape: " + string(shape))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag, value := range params {
|
||||||
|
if !result.Set(tag, value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInsetClip creates a rectangle View clipping area.
|
||||||
|
// - top - offset from the top border of a View;
|
||||||
|
// - right - offset from the right border of a View;
|
||||||
|
// - bottom - offset from the bottom border of a View;
|
||||||
|
// - left - offset from the left border of a View;
|
||||||
|
// - radius - corner radius, pass nil if you don't need to round corners
|
||||||
|
func NewInsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShapeProperty {
|
||||||
|
clip := new(insetClipData)
|
||||||
|
clip.init()
|
||||||
|
clip.setRaw(Top, top)
|
||||||
|
clip.setRaw(Right, right)
|
||||||
|
clip.setRaw(Bottom, bottom)
|
||||||
|
clip.setRaw(Left, left)
|
||||||
|
if radius != nil {
|
||||||
|
clip.setRaw(Radius, radius)
|
||||||
|
}
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCircleClip creates a circle View clipping area.
|
||||||
|
// - x - x-axis position of the circle clip center;
|
||||||
|
// - y - y-axis position of the circle clip center;
|
||||||
|
// - radius - radius of the circle clip center.
|
||||||
|
func NewCircleClip(x, y, radius SizeUnit) ClipShapeProperty {
|
||||||
|
clip := new(circleClipData)
|
||||||
|
clip.init()
|
||||||
|
clip.setRaw(X, x)
|
||||||
|
clip.setRaw(Y, y)
|
||||||
|
clip.setRaw(Radius, radius)
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEllipseClip creates a ellipse View clipping area.
|
||||||
|
// - x - x-axis position of the ellipse clip center;
|
||||||
|
// - y - y-axis position of the ellipse clip center;
|
||||||
|
// - rx - x-axis radius of the ellipse clip center;
|
||||||
|
// - ry - y-axis radius of the ellipse clip center.
|
||||||
|
func NewEllipseClip(x, y, rx, ry SizeUnit) ClipShapeProperty {
|
||||||
|
clip := new(ellipseClipData)
|
||||||
|
clip.init()
|
||||||
|
clip.setRaw(X, x)
|
||||||
|
clip.setRaw(Y, y)
|
||||||
|
clip.setRaw(RadiusX, rx)
|
||||||
|
clip.setRaw(RadiusY, ry)
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPolygonClip creates a polygon View clipping area.
|
||||||
|
// - points - an array of corner points of the polygon in the following order: x1, y1, x2, y2, …
|
||||||
|
//
|
||||||
|
// The elements of the function argument can be or text constants,
|
||||||
|
// or the text representation of SizeUnit, or elements of SizeUnit type.
|
||||||
|
func NewPolygonClip(points []any) ClipShapeProperty {
|
||||||
|
clip := new(polygonClipData)
|
||||||
|
clip.init()
|
||||||
|
if polygonClipDataSet(clip, Points, points) != nil {
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPolygonPointsClip creates a polygon View clipping area.
|
||||||
|
// - points - an array of corner points of the polygon in the following order: x1, y1, x2, y2, …
|
||||||
|
func NewPolygonPointsClip(points []SizeUnit) ClipShapeProperty {
|
||||||
|
clip := new(polygonClipData)
|
||||||
|
clip.init()
|
||||||
|
if polygonClipDataSet(clip, Points, points) != nil {
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) init() {
|
||||||
|
clip.dataProperty.init()
|
||||||
|
clip.set = insetClipDataSet
|
||||||
|
clip.supportedProperties = []PropertyName{
|
||||||
|
Top, Right, Bottom, Left, Radius,
|
||||||
|
RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
||||||
|
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
||||||
|
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
||||||
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) Shape() ClipShape {
|
||||||
|
return InsetClip
|
||||||
|
}
|
||||||
|
|
||||||
|
func insetClipDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case Top, Right, Bottom, Left:
|
||||||
|
return setSizeProperty(properties, tag, value)
|
||||||
|
|
||||||
|
case Radius:
|
||||||
|
return setRadiusProperty(properties, value)
|
||||||
|
|
||||||
|
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
||||||
|
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
||||||
|
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
||||||
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
||||||
|
if setRadiusPropertyElement(properties, tag, value) {
|
||||||
|
return []PropertyName{tag, Radius}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"%s" property is not supported by the inset clip shape`, tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) String() string {
|
||||||
|
return runStringWriter(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
buffer.WriteString("inset { ")
|
||||||
|
comma := false
|
||||||
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius} {
|
||||||
|
if value, ok := clip.properties[tag]; ok {
|
||||||
|
if comma {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
|
comma = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) cssStyle(session Session) string {
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
leadText := "inset("
|
||||||
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left} {
|
||||||
|
value, _ := sizeProperty(clip, tag, session)
|
||||||
|
buffer.WriteString(leadText)
|
||||||
|
buffer.WriteString(value.cssString("0px", session))
|
||||||
|
leadText = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
if radius := getRadiusProperty(clip); radius != nil {
|
||||||
|
buffer.WriteString(" round ")
|
||||||
|
buffer.WriteString(radius.BoxRadius(session).cssString(session))
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *insetClipData) valid(session Session) bool {
|
||||||
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
|
||||||
|
if value, ok := sizeProperty(clip, tag, session); ok && value.Type != Auto && value.Value != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) init() {
|
||||||
|
clip.dataProperty.init()
|
||||||
|
clip.set = circleClipDataSet
|
||||||
|
clip.supportedProperties = []PropertyName{X, Y, Radius}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) Shape() ClipShape {
|
||||||
|
return CircleClip
|
||||||
|
}
|
||||||
|
|
||||||
|
func circleClipDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case X, Y, Radius:
|
||||||
|
return setSizeProperty(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"%s" property is not supported by the circle clip shape`, tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) String() string {
|
||||||
|
return runStringWriter(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
buffer.WriteString("circle { ")
|
||||||
|
comma := false
|
||||||
|
for _, tag := range []PropertyName{Radius, X, Y} {
|
||||||
|
if value, ok := clip.properties[tag]; ok {
|
||||||
|
if comma {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
|
comma = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) cssStyle(session Session) string {
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
buffer.WriteString("circle(")
|
||||||
|
r, _ := sizeProperty(clip, Radius, session)
|
||||||
|
buffer.WriteString(r.cssString("50%", session))
|
||||||
|
|
||||||
|
buffer.WriteString(" at ")
|
||||||
|
x, _ := sizeProperty(clip, X, session)
|
||||||
|
buffer.WriteString(x.cssString("50%", session))
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
|
||||||
|
y, _ := sizeProperty(clip, Y, session)
|
||||||
|
buffer.WriteString(y.cssString("50%", session))
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *circleClipData) valid(session Session) bool {
|
||||||
|
if value, ok := sizeProperty(clip, Radius, session); ok && value.Value == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) init() {
|
||||||
|
clip.dataProperty.init()
|
||||||
|
clip.set = ellipseClipDataSet
|
||||||
|
clip.supportedProperties = []PropertyName{X, Y, Radius, RadiusX, RadiusY}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) Shape() ClipShape {
|
||||||
|
return EllipseClip
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipseClipDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case X, Y, RadiusX, RadiusY:
|
||||||
|
return setSizeProperty(properties, tag, value)
|
||||||
|
|
||||||
|
case Radius:
|
||||||
|
if result := setSizeProperty(properties, RadiusX, value); result != nil {
|
||||||
|
properties.setRaw(RadiusY, properties.getRaw(RadiusX))
|
||||||
|
return append(result, RadiusY)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"%s" property is not supported by the ellipse clip shape`, tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) String() string {
|
||||||
|
return runStringWriter(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
buffer.WriteString("ellipse { ")
|
||||||
|
comma := false
|
||||||
|
for _, tag := range []PropertyName{RadiusX, RadiusY, X, Y} {
|
||||||
|
if value, ok := clip.properties[tag]; ok {
|
||||||
|
if comma {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
|
comma = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) cssStyle(session Session) string {
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
rx, _ := sizeProperty(clip, RadiusX, session)
|
||||||
|
ry, _ := sizeProperty(clip, RadiusX, session)
|
||||||
|
buffer.WriteString("ellipse(")
|
||||||
|
buffer.WriteString(rx.cssString("50%", session))
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(ry.cssString("50%", session))
|
||||||
|
|
||||||
|
buffer.WriteString(" at ")
|
||||||
|
x, _ := sizeProperty(clip, X, session)
|
||||||
|
buffer.WriteString(x.cssString("50%", session))
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
|
||||||
|
y, _ := sizeProperty(clip, Y, session)
|
||||||
|
buffer.WriteString(y.cssString("50%", session))
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *ellipseClipData) valid(session Session) bool {
|
||||||
|
rx, _ := sizeProperty(clip, RadiusX, session)
|
||||||
|
ry, _ := sizeProperty(clip, RadiusY, session)
|
||||||
|
return rx.Value != 0 && ry.Value != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) init() {
|
||||||
|
clip.dataProperty.init()
|
||||||
|
clip.set = polygonClipDataSet
|
||||||
|
clip.supportedProperties = []PropertyName{Points}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) Shape() ClipShape {
|
||||||
|
return PolygonClip
|
||||||
|
}
|
||||||
|
|
||||||
|
func polygonClipDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
if Points == tag {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case []any:
|
||||||
|
points := make([]any, len(value))
|
||||||
|
for i, val := range value {
|
||||||
|
switch val := val.(type) {
|
||||||
|
case string:
|
||||||
|
if isConstantName(val) {
|
||||||
|
points[i] = val
|
||||||
|
} else if size, ok := StringToSizeUnit(val); ok {
|
||||||
|
points[i] = size
|
||||||
|
} else {
|
||||||
|
notCompatibleType(tag, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
points[i] = val
|
||||||
|
|
||||||
|
default:
|
||||||
|
notCompatibleType(tag, val)
|
||||||
|
points[i] = AutoSize()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
properties.setRaw(Points, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case []SizeUnit:
|
||||||
|
points := make([]any, len(value))
|
||||||
|
for i, point := range value {
|
||||||
|
points[i] = point
|
||||||
|
}
|
||||||
|
properties.setRaw(Points, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
values := strings.Split(value, ",")
|
||||||
|
points := make([]any, len(values))
|
||||||
|
for i, val := range values {
|
||||||
|
val = strings.Trim(val, " \t\n\r")
|
||||||
|
if isConstantName(val) {
|
||||||
|
points[i] = val
|
||||||
|
} else if size, ok := StringToSizeUnit(val); ok {
|
||||||
|
points[i] = size
|
||||||
|
} else {
|
||||||
|
notCompatibleType(tag, val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
properties.setRaw(Points, points)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) String() string {
|
||||||
|
return runStringWriter(clip)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) points() []any {
|
||||||
|
if value := clip.getRaw(Points); value != nil {
|
||||||
|
if points, ok := value.([]any); ok {
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
|
||||||
|
buffer.WriteString("inset { ")
|
||||||
|
|
||||||
|
if points := clip.points(); points != nil {
|
||||||
|
buffer.WriteString(string(Points))
|
||||||
|
buffer.WriteString(` = "`)
|
||||||
|
for i, value := range points {
|
||||||
|
if i > 0 {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
writePropertyValue(buffer, "", value, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString(`" `)
|
||||||
|
}
|
||||||
|
buffer.WriteRune('}')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) cssStyle(session Session) string {
|
||||||
|
|
||||||
|
points := clip.points()
|
||||||
|
count := len(points)
|
||||||
|
if count < 2 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
writePoint := func(value any) {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if val, ok := session.resolveConstants(value); ok {
|
||||||
|
if size, ok := StringToSizeUnit(val); ok {
|
||||||
|
buffer.WriteString(size.cssString("0px", session))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case SizeUnit:
|
||||||
|
buffer.WriteString(value.cssString("0px", session))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString("0px")
|
||||||
|
}
|
||||||
|
|
||||||
|
leadText := "polygon("
|
||||||
|
for i := 1; i < count; i += 2 {
|
||||||
|
buffer.WriteString(leadText)
|
||||||
|
writePoint(points[i-1])
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
writePoint(points[i])
|
||||||
|
leadText = ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clip *polygonClipData) valid(session Session) bool {
|
||||||
|
return len(clip.points()) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClipShapeProperty(obj DataObject) ClipShapeProperty {
|
||||||
|
switch obj.Tag() {
|
||||||
|
case "inset":
|
||||||
|
clip := new(insetClipData)
|
||||||
|
clip.init()
|
||||||
|
for _, tag := range []PropertyName{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
|
||||||
|
if value, ok := obj.PropertyValue(string(tag)); ok {
|
||||||
|
insetClipDataSet(clip, tag, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clip
|
||||||
|
|
||||||
|
case "circle":
|
||||||
|
clip := new(circleClipData)
|
||||||
|
clip.init()
|
||||||
|
for _, tag := range []PropertyName{X, Y, Radius} {
|
||||||
|
if value, ok := obj.PropertyValue(string(tag)); ok {
|
||||||
|
circleClipDataSet(clip, tag, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clip
|
||||||
|
|
||||||
|
case "ellipse":
|
||||||
|
clip := new(ellipseClipData)
|
||||||
|
clip.init()
|
||||||
|
for _, tag := range []PropertyName{X, Y, RadiusX, RadiusY} {
|
||||||
|
if value, ok := obj.PropertyValue(string(tag)); ok {
|
||||||
|
ellipseClipDataSet(clip, tag, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clip
|
||||||
|
|
||||||
|
case "polygon":
|
||||||
|
clip := new(polygonClipData)
|
||||||
|
clip.init()
|
||||||
|
if value, ok := obj.PropertyValue(string(Points)); ok {
|
||||||
|
polygonClipDataSet(clip, Points, value)
|
||||||
|
}
|
||||||
|
return clip
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClipShapePropertyProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case ClipShapeProperty:
|
||||||
|
properties.setRaw(tag, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if isConstantName(value) {
|
||||||
|
properties.setRaw(tag, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj := NewDataObject(value); obj == nil {
|
||||||
|
if clip := parseClipShapeProperty(obj); clip != nil {
|
||||||
|
properties.setRaw(tag, clip)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataObject:
|
||||||
|
if clip := parseClipShapeProperty(value); clip != nil {
|
||||||
|
properties.setRaw(tag, clip)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataValue:
|
||||||
|
if value.IsObject() {
|
||||||
|
if clip := parseClipShapeProperty(value.Object()); clip != nil {
|
||||||
|
properties.setRaw(tag, clip)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClipShapeProperty(prop Properties, tag PropertyName, session Session) ClipShapeProperty {
|
||||||
|
if value := prop.getRaw(tag); value != nil {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case ClipShapeProperty:
|
||||||
|
return value
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if text, ok := session.resolveConstants(value); ok {
|
||||||
|
if obj := NewDataObject(text); obj == nil {
|
||||||
|
return parseClipShapeProperty(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClip returns a View clipping area.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||||||
|
func GetClip(view View, subviewID ...string) ClipShapeProperty {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
return getClipShapeProperty(view, Clip, view.Session())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShapeOutside returns a shape around which adjacent inline content.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||||||
|
func GetShapeOutside(view View, subviewID ...string) ClipShapeProperty {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
return getClipShapeProperty(view, ShapeOutside, view.Session())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
16
color.go
16
color.go
|
|
@ -11,6 +11,22 @@ import (
|
||||||
// Color - represent color in argb format
|
// Color - represent color in argb format
|
||||||
type Color uint32
|
type Color uint32
|
||||||
|
|
||||||
|
// ARGB creates Color using alpha, red, green and blue components
|
||||||
|
func ARGB[T int | uint | int8 | uint8](alpha, red, green, blue T) Color {
|
||||||
|
return ((Color(alpha) & 0xFF) << 24) +
|
||||||
|
((Color(red) & 0xFF) << 16) +
|
||||||
|
((Color(green) & 0xFF) << 8) +
|
||||||
|
(Color(blue) & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB creates Color using red, green and blue components
|
||||||
|
func RGB[T int | uint | int8 | uint8](red, green, blue T) Color {
|
||||||
|
return (Color(0xFF) << 24) +
|
||||||
|
((Color(red) & 0xFF) << 16) +
|
||||||
|
((Color(green) & 0xFF) << 8) +
|
||||||
|
(Color(blue) & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
// ARGB - return alpha, red, green and blue components of the color
|
// ARGB - return alpha, red, green and blue components of the color
|
||||||
func (color Color) ARGB() (uint8, uint8, uint8, uint8) {
|
func (color Color) ARGB() (uint8, uint8, uint8, uint8) {
|
||||||
return uint8(color >> 24),
|
return uint8(color >> 24),
|
||||||
|
|
|
||||||
167
colorPicker.go
167
colorPicker.go
|
|
@ -12,20 +12,20 @@ const (
|
||||||
// Event generated when color picker value has been changed.
|
// Event generated when color picker value has been changed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(picker rui.ColorPicker, newColor, oldColor rui.Color)`.
|
// func(picker rui.ColorPicker, newColor, oldColor rui.Color)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// picker - Interface of a color picker which generated this event,
|
// - picker - Interface of a color picker which generated this event,
|
||||||
// newColor - New color value,
|
// - newColor - New color value,
|
||||||
// oldColor - Old color value.
|
// - oldColor - Old color value.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(picker rui.ColorPicker, newColor rui.Color)`,
|
// func(picker rui.ColorPicker, newColor rui.Color)
|
||||||
// `func(newColor, oldColor rui.Color)`,
|
// func(newColor, oldColor rui.Color)
|
||||||
// `func(newColor rui.Color)`,
|
// func(newColor rui.Color)
|
||||||
// `func(picker rui.ColorPicker)`,
|
// func(picker rui.ColorPicker)
|
||||||
// `func()`.
|
// func()
|
||||||
ColorChangedEvent = "color-changed"
|
ColorChangedEvent PropertyName = "color-changed"
|
||||||
|
|
||||||
// ColorPickerValue is the constant for "color-picker-value" property tag.
|
// ColorPickerValue is the constant for "color-picker-value" property tag.
|
||||||
//
|
//
|
||||||
|
|
@ -36,7 +36,7 @@ const (
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is `Color`, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See `Color` description for more details.
|
||||||
ColorPickerValue = "color-picker-value"
|
ColorPickerValue PropertyName = "color-picker-value"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ColorPicker represent a ColorPicker view
|
// ColorPicker represent a ColorPicker view
|
||||||
|
|
@ -46,8 +46,6 @@ type ColorPicker interface {
|
||||||
|
|
||||||
type colorPickerData struct {
|
type colorPickerData struct {
|
||||||
viewData
|
viewData
|
||||||
dataList
|
|
||||||
colorChangedListeners []func(ColorPicker, Color, Color)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewColorPicker create new ColorPicker object and return it
|
// NewColorPicker create new ColorPicker object and return it
|
||||||
|
|
@ -59,125 +57,69 @@ func NewColorPicker(session Session, params Params) ColorPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newColorPicker(session Session) View {
|
func newColorPicker(session Session) View {
|
||||||
return NewColorPicker(session, nil)
|
return new(colorPickerData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) init(session Session) {
|
func (picker *colorPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "ColorPicker"
|
picker.tag = "ColorPicker"
|
||||||
picker.hasHtmlDisabled = true
|
picker.hasHtmlDisabled = true
|
||||||
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
|
||||||
picker.properties[Padding] = Px(0)
|
picker.properties[Padding] = Px(0)
|
||||||
picker.dataListInit()
|
picker.normalize = normalizeColorPickerTag
|
||||||
|
picker.set = picker.setFunc
|
||||||
|
picker.changed = picker.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) String() string {
|
func normalizeColorPickerTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(picker, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Value, ColorTag:
|
case Value, ColorTag:
|
||||||
return ColorPickerValue
|
return ColorPickerValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return picker.normalizeDataListTag(tag)
|
return normalizeDataListTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) Remove(tag string) {
|
func (picker *colorPickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
picker.remove(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case ColorChangedEvent:
|
case ColorChangedEvent:
|
||||||
if len(picker.colorChangedListeners) > 0 {
|
return setTwoArgEventListener[ColorPicker, Color](picker, tag, value)
|
||||||
picker.colorChangedListeners = []func(ColorPicker, Color, Color){}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ColorPickerValue:
|
case ColorPickerValue:
|
||||||
oldColor := GetColorPickerValue(picker)
|
oldColor := GetColorPickerValue(picker)
|
||||||
delete(picker.properties, ColorPickerValue)
|
result := setColorProperty(picker, ColorPickerValue, value)
|
||||||
picker.colorChanged(oldColor)
|
if result != nil {
|
||||||
|
picker.setRaw("old-color", oldColor)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
case DataList:
|
case DataList:
|
||||||
if len(picker.dataList.dataList) > 0 {
|
return setDataList(picker, value, "")
|
||||||
picker.setDataList(picker, []string{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
picker.viewData.remove(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) Set(tag string, value any) bool {
|
|
||||||
return picker.set(picker.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
picker.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return picker.viewData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (picker *colorPickerData) propertyChanged(tag PropertyName) {
|
||||||
switch tag {
|
switch tag {
|
||||||
case ColorChangedEvent:
|
|
||||||
listeners, ok := valueToEventWithOldListeners[ColorPicker, Color](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(ColorPicker, Color, Color){}
|
|
||||||
}
|
|
||||||
picker.colorChangedListeners = listeners
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case ColorPickerValue:
|
case ColorPickerValue:
|
||||||
oldColor := GetColorPickerValue(picker)
|
color := GetColorPickerValue(picker)
|
||||||
if picker.setColorProperty(ColorPickerValue, value) {
|
picker.Session().callFunc("setInputValue", picker.htmlID(), color.rgbString())
|
||||||
picker.colorChanged(oldColor)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataList:
|
if listeners := GetColorChangedListeners(picker); len(listeners) > 0 {
|
||||||
return picker.setDataList(picker, value, picker.created)
|
oldColor := Color(0)
|
||||||
|
if value := picker.getRaw("old-color"); value != nil {
|
||||||
|
oldColor = value.(Color)
|
||||||
|
}
|
||||||
|
for _, listener := range listeners {
|
||||||
|
listener(picker, color, oldColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return picker.viewData.set(tag, value)
|
picker.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) colorChanged(oldColor Color) {
|
|
||||||
if newColor := GetColorPickerValue(picker); oldColor != newColor {
|
|
||||||
if picker.created {
|
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), newColor.rgbString())
|
|
||||||
}
|
|
||||||
for _, listener := range picker.colorChangedListeners {
|
|
||||||
listener(picker, newColor, oldColor)
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(ColorTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) Get(tag string) any {
|
|
||||||
return picker.get(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *colorPickerData) get(tag string) any {
|
|
||||||
switch tag {
|
|
||||||
case ColorChangedEvent:
|
|
||||||
return picker.colorChangedListeners
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return picker.dataList.dataList
|
|
||||||
|
|
||||||
default:
|
|
||||||
return picker.viewData.get(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) htmlTag() string {
|
func (picker *colorPickerData) htmlTag() string {
|
||||||
|
|
@ -185,7 +127,10 @@ func (picker *colorPickerData) htmlTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (picker *colorPickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
picker.dataListHtmlSubviews(self, buffer)
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
||||||
|
text, _ = session.resolveConstants(text)
|
||||||
|
return text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
|
|
@ -200,20 +145,23 @@ func (picker *colorPickerData) htmlProperties(self View, buffer *strings.Builder
|
||||||
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.dataListHtmlProperties(picker, buffer)
|
dataListHtmlProperties(picker, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *colorPickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *colorPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
oldColor := GetColorPickerValue(picker)
|
|
||||||
if color, ok := StringToColor(text); ok {
|
if color, ok := StringToColor(text); ok {
|
||||||
|
oldColor := GetColorPickerValue(picker)
|
||||||
picker.properties[ColorPickerValue] = color
|
picker.properties[ColorPickerValue] = color
|
||||||
if color != oldColor {
|
if color != oldColor {
|
||||||
for _, listener := range picker.colorChangedListeners {
|
for _, listener := range GetColorChangedListeners(picker) {
|
||||||
listener(picker, color, oldColor)
|
listener(picker, color, oldColor)
|
||||||
}
|
}
|
||||||
|
if listener, ok := picker.changeListener[ColorPickerValue]; ok {
|
||||||
|
listener(picker, ColorPickerValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,14 +174,11 @@ func (picker *colorPickerData) handleCommand(self View, command string, data Dat
|
||||||
// GetColorPickerValue returns the value of ColorPicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetColorPickerValue(view View, subviewID ...string) Color {
|
func GetColorPickerValue(view View, subviewID ...string) Color {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok {
|
if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
for _, tag := range []string{ColorPickerValue, Value, ColorTag} {
|
for _, tag := range []PropertyName{ColorPickerValue, Value, ColorTag} {
|
||||||
if value := valueFromStyle(view, tag); value != nil {
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
if result, ok := valueToColor(value, view.Session()); ok {
|
if result, ok := valueToColor(value, view.Session()); ok {
|
||||||
return result
|
return result
|
||||||
|
|
@ -248,5 +193,5 @@ func GetColorPickerValue(view View, subviewID ...string) Color {
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color, Color) {
|
||||||
return getEventWithOldListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
return getTwoArgEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
197
columnLayout.go
197
columnLayout.go
|
|
@ -2,118 +2,117 @@ package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants for [ColumnLayout] specific properties and events
|
// Constants for [ColumnLayout] specific properties and events
|
||||||
const (
|
const (
|
||||||
// ColumnCount is the constant for "column-count" property tag.
|
// ColumnCount is the constant for "column-count" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Specifies number of columns into which the content is break. Values less than zero are not valid. If this property
|
// Specifies number of columns into which the content is break. Values less than zero are not valid. If this property
|
||||||
// value is 0 then the number of columns is calculated based on the "column-width" property.
|
// value is 0 then the number of columns is calculated based on the "column-width" property.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0` or "0" - Use "column-width" to control how many columns will be created.
|
// - 0 or "0" - Use "column-width" to control how many columns will be created.
|
||||||
// >= `0` or >= "0" - Тhe number of columns into which the content is divided.
|
// - positive value - Тhe number of columns into which the content is divided.
|
||||||
ColumnCount = "column-count"
|
ColumnCount PropertyName = "column-count"
|
||||||
|
|
||||||
// ColumnWidth is the constant for "column-width" property tag.
|
// ColumnWidth is the constant for "column-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Specifies the width of each column.
|
// Specifies the width of each column.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
ColumnWidth = "column-width"
|
ColumnWidth PropertyName = "column-width"
|
||||||
|
|
||||||
// ColumnGap is the constant for "column-gap" property tag.
|
// ColumnGap is the constant for "column-gap" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Set the size of the gap (gutter) between columns.
|
// Set the size of the gap (gutter) between columns.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See [SizeUnit] description for more details.
|
||||||
ColumnGap = "column-gap"
|
ColumnGap PropertyName = "column-gap"
|
||||||
|
|
||||||
// ColumnSeparator is the constant for "column-separator" property tag.
|
// ColumnSeparator is the constant for "column-separator" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Specifies the line drawn between columns in a multi-column layout.
|
// Specifies the line drawn between columns in a multi-column layout.
|
||||||
//
|
//
|
||||||
// Supported types: `ColumnSeparatorProperty`, `ViewBorder`.
|
// Supported types: ColumnSeparatorProperty, ViewBorder.
|
||||||
//
|
//
|
||||||
// Internal type is `ColumnSeparatorProperty`, other types converted to it during assignment.
|
// Internal type is ColumnSeparatorProperty, other types converted to it during assignment.
|
||||||
// See `ColumnSeparatorProperty` and `ViewBorder` description for more details.
|
// See [ColumnSeparatorProperty] and [ViewBorder] description for more details.
|
||||||
ColumnSeparator = "column-separator"
|
ColumnSeparator PropertyName = "column-separator"
|
||||||
|
|
||||||
// ColumnSeparatorStyle is the constant for "column-separator-style" property tag.
|
// ColumnSeparatorStyle is the constant for "column-separator-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Controls the style of the line drawn between columns in a multi-column layout.
|
// Controls the style of the line drawn between columns in a multi-column layout.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneLine`) or "none" - The separator will not be drawn.
|
// - 0 (NoneLine) or "none" - The separator will not be drawn.
|
||||||
// `1`(`SolidLine`) or "solid" - Solid line as a separator.
|
// - 1 (SolidLine) or "solid" - Solid line as a separator.
|
||||||
// `2`(`DashedLine`) or "dashed" - Dashed line as a separator.
|
// - 2 (DashedLine) or "dashed" - Dashed line as a separator.
|
||||||
// `3`(`DottedLine`) or "dotted" - Dotted line as a separator.
|
// - 3 (DottedLine) or "dotted" - Dotted line as a separator.
|
||||||
// `4`(`DoubleLine`) or "double" - Double line as a separator.
|
// - 4 (DoubleLine) or "double" - Double line as a separator.
|
||||||
ColumnSeparatorStyle = "column-separator-style"
|
ColumnSeparatorStyle PropertyName = "column-separator-style"
|
||||||
|
|
||||||
// ColumnSeparatorWidth is the constant for "column-separator-width" property tag.
|
// ColumnSeparatorWidth is the constant for "column-separator-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Set the width of the line drawn between columns in a multi-column layout.
|
// Set the width of the line drawn between columns in a multi-column layout.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
ColumnSeparatorWidth = "column-separator-width"
|
ColumnSeparatorWidth PropertyName = "column-separator-width"
|
||||||
|
|
||||||
// ColumnSeparatorColor is the constant for "column-separator-color" property tag.
|
// ColumnSeparatorColor is the constant for "column-separator-color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Set the color of the line drawn between columns in a multi-column layout.
|
// Set the color of the line drawn between columns in a multi-column layout.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See Color description for more details.
|
||||||
ColumnSeparatorColor = "column-separator-color"
|
ColumnSeparatorColor PropertyName = "column-separator-color"
|
||||||
|
|
||||||
// ColumnFill is the constant for "column-fill" property tag.
|
// ColumnFill is the constant for "column-fill" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Controls how a `ColumnLayout`'s content is balanced when broken into columns. Default value is "balance".
|
// Controls how a ColumnLayout's content is balanced when broken into columns. Default value is "balance".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`ColumnFillBalance`) or "balance" - Content is equally divided between columns.
|
// - 0 (ColumnFillBalance) or "balance" - Content is equally divided between columns.
|
||||||
// `1`(`ColumnFillAuto`) or "auto" - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
|
// - 1 (ColumnFillAuto) or "auto" - Columns are filled sequentially. Content takes up only the room it needs, possibly resulting in some columns remaining empty.
|
||||||
ColumnFill = "column-fill"
|
ColumnFill PropertyName = "column-fill"
|
||||||
|
|
||||||
// ColumnSpanAll is the constant for "column-span-all" property tag.
|
// ColumnSpanAll is the constant for "column-span-all" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnLayout`.
|
// Used by ColumnLayout.
|
||||||
// Property used in views placed inside the column layout container. Makes it possible for a view to span across all
|
// Property used in views placed inside the column layout container. Makes it possible for a view to span across all
|
||||||
// columns. Default value is `false`.
|
// columns. Default value is false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - View will span across all columns.
|
// - true, 1, "true", "yes", "on", or "1" - View will span across all columns.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - View will be a part of a column.
|
// - false, 0, "false", "no", "off", or "0" - View will be a part of a column.
|
||||||
ColumnSpanAll = "column-span-all"
|
ColumnSpanAll PropertyName = "column-span-all"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ColumnLayout represent a ColumnLayout view
|
// ColumnLayout represent a ColumnLayout view
|
||||||
|
|
@ -134,22 +133,20 @@ func NewColumnLayout(session Session, params Params) ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newColumnLayout(session Session) View {
|
func newColumnLayout(session Session) View {
|
||||||
return NewColumnLayout(session, nil)
|
return new(columnLayoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of ColumnLayout by default values
|
// Init initialize fields of ColumnLayout by default values
|
||||||
func (ColumnLayout *columnLayoutData) init(session Session) {
|
func (columnLayout *columnLayoutData) init(session Session) {
|
||||||
ColumnLayout.viewsContainerData.init(session)
|
columnLayout.viewsContainerData.init(session)
|
||||||
ColumnLayout.tag = "ColumnLayout"
|
columnLayout.tag = "ColumnLayout"
|
||||||
//ColumnLayout.systemClass = "ruiColumnLayout"
|
columnLayout.normalize = normalizeColumnLayoutTag
|
||||||
|
columnLayout.changed = columnLayout.propertyChanged
|
||||||
|
//columnLayout.systemClass = "ruiColumnLayout"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (columnLayout *columnLayoutData) String() string {
|
func normalizeColumnLayoutTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(columnLayout, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Gap:
|
case Gap:
|
||||||
return ColumnGap
|
return ColumnGap
|
||||||
|
|
@ -157,62 +154,28 @@ func (columnLayout *columnLayoutData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (columnLayout *columnLayoutData) Get(tag string) any {
|
func (columnLayout *columnLayoutData) propertyChanged(tag PropertyName) {
|
||||||
return columnLayout.get(columnLayout.normalizeTag(tag))
|
switch tag {
|
||||||
}
|
case ColumnSeparator:
|
||||||
|
css := ""
|
||||||
func (columnLayout *columnLayoutData) Remove(tag string) {
|
session := columnLayout.Session()
|
||||||
columnLayout.remove(columnLayout.normalizeTag(tag))
|
if value := columnLayout.getRaw(ColumnSeparator); value != nil {
|
||||||
}
|
separator := value.(ColumnSeparatorProperty)
|
||||||
|
css = separator.cssValue(session)
|
||||||
func (columnLayout *columnLayoutData) remove(tag string) {
|
|
||||||
columnLayout.viewsContainerData.remove(tag)
|
|
||||||
if columnLayout.created {
|
|
||||||
switch tag {
|
|
||||||
case ColumnCount, ColumnWidth, ColumnGap:
|
|
||||||
columnLayout.session.updateCSSProperty(columnLayout.htmlID(), tag, "")
|
|
||||||
|
|
||||||
case ColumnSeparator:
|
|
||||||
columnLayout.session.updateCSSProperty(columnLayout.htmlID(), "column-rule", "")
|
|
||||||
}
|
}
|
||||||
}
|
session.updateCSSProperty(columnLayout.htmlID(), "column-rule", css)
|
||||||
}
|
|
||||||
|
|
||||||
func (columnLayout *columnLayoutData) Set(tag string, value any) bool {
|
case ColumnCount:
|
||||||
return columnLayout.set(columnLayout.normalizeTag(tag), value)
|
session := columnLayout.Session()
|
||||||
}
|
if count := GetColumnCount(columnLayout); count > 0 {
|
||||||
|
session.updateCSSProperty(columnLayout.htmlID(), string(ColumnCount), strconv.Itoa(count))
|
||||||
func (columnLayout *columnLayoutData) set(tag string, value any) bool {
|
} else {
|
||||||
if value == nil {
|
session.updateCSSProperty(columnLayout.htmlID(), string(ColumnCount), "auto")
|
||||||
columnLayout.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !columnLayout.viewsContainerData.set(tag, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if columnLayout.created {
|
|
||||||
switch tag {
|
|
||||||
case ColumnSeparator:
|
|
||||||
css := ""
|
|
||||||
session := columnLayout.Session()
|
|
||||||
if val, ok := columnLayout.properties[ColumnSeparator]; ok {
|
|
||||||
separator := val.(ColumnSeparatorProperty)
|
|
||||||
css = separator.cssValue(columnLayout.Session())
|
|
||||||
}
|
|
||||||
session.updateCSSProperty(columnLayout.htmlID(), "column-rule", css)
|
|
||||||
|
|
||||||
case ColumnCount:
|
|
||||||
session := columnLayout.Session()
|
|
||||||
if count, ok := intProperty(columnLayout, tag, session, 0); ok && count > 0 {
|
|
||||||
session.updateCSSProperty(columnLayout.htmlID(), tag, strconv.Itoa(count))
|
|
||||||
} else {
|
|
||||||
session.updateCSSProperty(columnLayout.htmlID(), tag, "auto")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
columnLayout.viewsContainerData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetColumnCount returns int value which specifies number of columns into which the content of
|
// GetColumnCount returns int value which specifies number of columns into which the content of
|
||||||
|
|
@ -236,11 +199,7 @@ func GetColumnGap(view View, subviewID ...string) SizeUnit {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getColumnSeparator(view View, subviewID []string) ViewBorder {
|
func getColumnSeparator(view View, subviewID []string) ViewBorder {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
value := view.Get(ColumnSeparator)
|
value := view.Get(ColumnSeparator)
|
||||||
if value == nil {
|
if value == nil {
|
||||||
value = valueFromStyle(view, ColumnSeparator)
|
value = valueFromStyle(view, ColumnSeparator)
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ type ColumnSeparatorProperty interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type columnSeparatorProperty struct {
|
type columnSeparatorProperty struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
|
func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
separator := new(columnSeparatorProperty)
|
separator := new(columnSeparatorProperty)
|
||||||
separator.properties = map[string]any{}
|
separator.init()
|
||||||
return separator
|
return separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,17 +35,18 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
separator := new(columnSeparatorProperty)
|
separator := new(columnSeparatorProperty)
|
||||||
separator.properties = map[string]any{}
|
separator.init()
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if val, ok := value.PropertyValue(tag); ok && val != "" {
|
if val, ok := value.PropertyValue(string(tag)); ok && val != "" {
|
||||||
separator.set(tag, value)
|
propertiesSet(separator, tag, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return separator
|
return separator
|
||||||
|
|
||||||
case ViewBorder:
|
case ViewBorder:
|
||||||
separator := new(columnSeparatorProperty)
|
separator := new(columnSeparatorProperty)
|
||||||
separator.properties = map[string]any{
|
separator.init()
|
||||||
|
separator.properties = map[PropertyName]any{
|
||||||
Style: value.Style,
|
Style: value.Style,
|
||||||
Width: value.Width,
|
Width: value.Width,
|
||||||
ColorTag: value.Color,
|
ColorTag: value.Color,
|
||||||
|
|
@ -57,19 +58,17 @@ func newColumnSeparatorProperty(value any) ColumnSeparatorProperty {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewColumnSeparator creates the new ColumnSeparatorProperty.
|
// NewColumnSeparatorProperty creates the new ColumnSeparatorProperty.
|
||||||
|
//
|
||||||
// The following properties can be used:
|
// The following properties can be used:
|
||||||
//
|
// - "style" (Style) - Determines the line style (type is int). Valid values: 0 (NoneLine), 1 (SolidLine), 2 (DashedLine), 3 (DottedLine), or 4 (DoubleLine);
|
||||||
// "style" (Style). Determines the line style (int). Valid values: 0 (NoneLine), 1 (SolidLine), 2 (DashedLine), 3 (DottedLine), or 4 (DoubleLine);
|
// - "color" (ColorTag) - Determines the line color (type is [Color]);
|
||||||
//
|
// - "width" (Width) - Determines the line thickness (type is [SizeUnit]).
|
||||||
// "color" (ColorTag). Determines the line color (Color);
|
func NewColumnSeparatorProperty(params Params) ColumnSeparatorProperty {
|
||||||
//
|
|
||||||
// "width" (Width). Determines the line thickness (SizeUnit).
|
|
||||||
func NewColumnSeparator(params Params) ColumnSeparatorProperty {
|
|
||||||
separator := new(columnSeparatorProperty)
|
separator := new(columnSeparatorProperty)
|
||||||
separator.properties = map[string]any{}
|
separator.init()
|
||||||
if params != nil {
|
if params != nil {
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if value, ok := params[tag]; ok && value != nil {
|
if value, ok := params[tag]; ok && value != nil {
|
||||||
separator.Set(tag, value)
|
separator.Set(tag, value)
|
||||||
}
|
}
|
||||||
|
|
@ -78,8 +77,29 @@ func NewColumnSeparator(params Params) ColumnSeparatorProperty {
|
||||||
return separator
|
return separator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
|
// NewColumnSeparator creates the new ColumnSeparatorProperty.
|
||||||
tag = strings.ToLower(tag)
|
//
|
||||||
|
// Arguments:
|
||||||
|
// - style - determines the line style. Valid values: 0 [NoneLine], 1 [SolidLine], 2 [DashedLine], 3 [DottedLine], or 4 [DoubleLine];
|
||||||
|
// - color - determines the line color;
|
||||||
|
// - width - determines the line thickness.
|
||||||
|
func NewColumnSeparator(style int, color Color, width SizeUnit) ColumnSeparatorProperty {
|
||||||
|
return NewColumnSeparatorProperty(Params{
|
||||||
|
Width: width,
|
||||||
|
Style: style,
|
||||||
|
ColorTag: color,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (separator *columnSeparatorProperty) init() {
|
||||||
|
separator.dataProperty.init()
|
||||||
|
separator.normalize = normalizeVolumnSeparatorTag
|
||||||
|
separator.set = columnSeparatorSet
|
||||||
|
separator.supportedProperties = []PropertyName{Style, Width, ColorTag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeVolumnSeparatorTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case ColumnSeparatorStyle, "separator-style":
|
case ColumnSeparatorStyle, "separator-style":
|
||||||
return Style
|
return Style
|
||||||
|
|
@ -97,12 +117,12 @@ func (separator *columnSeparatorProperty) normalizeTag(tag string) string {
|
||||||
func (separator *columnSeparatorProperty) writeString(buffer *strings.Builder, indent string) {
|
func (separator *columnSeparatorProperty) writeString(buffer *strings.Builder, indent string) {
|
||||||
buffer.WriteString("_{ ")
|
buffer.WriteString("_{ ")
|
||||||
comma := false
|
comma := false
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if value, ok := separator.properties[tag]; ok {
|
if value, ok := separator.properties[tag]; ok {
|
||||||
if comma {
|
if comma {
|
||||||
buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, BorderStyle, value, indent)
|
writePropertyValue(buffer, BorderStyle, value, indent)
|
||||||
comma = true
|
comma = true
|
||||||
|
|
@ -116,47 +136,12 @@ func (separator *columnSeparatorProperty) String() string {
|
||||||
return runStringWriter(separator)
|
return runStringWriter(separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (separator *columnSeparatorProperty) Remove(tag string) {
|
func getColumnSeparatorProperty(properties Properties) ColumnSeparatorProperty {
|
||||||
|
if val := properties.getRaw(ColumnSeparator); val != nil {
|
||||||
switch tag = separator.normalizeTag(tag); tag {
|
if separator, ok := val.(ColumnSeparatorProperty); ok {
|
||||||
case Style, Width, ColorTag:
|
return separator
|
||||||
delete(separator.properties, tag)
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (separator *columnSeparatorProperty) Set(tag string, value any) bool {
|
|
||||||
tag = separator.normalizeTag(tag)
|
|
||||||
|
|
||||||
if value == nil {
|
|
||||||
separator.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case Style:
|
|
||||||
return separator.setEnumProperty(Style, value, enumProperties[BorderStyle].values)
|
|
||||||
|
|
||||||
case Width:
|
|
||||||
return separator.setSizeProperty(Width, value)
|
|
||||||
|
|
||||||
case ColorTag:
|
|
||||||
return separator.setColorProperty(ColorTag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not compatible with the ColumnSeparatorProperty`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (separator *columnSeparatorProperty) Get(tag string) any {
|
|
||||||
tag = separator.normalizeTag(tag)
|
|
||||||
|
|
||||||
if result, ok := separator.properties[tag]; ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,3 +184,10 @@ func (separator *columnSeparatorProperty) cssValue(session Session) string {
|
||||||
|
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func columnSeparatorSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
if tag == Style {
|
||||||
|
return setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values)
|
||||||
|
}
|
||||||
|
return propertiesSet(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ func InitCustomView(customView CustomView, tag string, session Session, params P
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (customView *CustomViewData) init(session Session) {
|
||||||
|
}
|
||||||
|
|
||||||
// SuperView returns a super view
|
// SuperView returns a super view
|
||||||
func (customView *CustomViewData) SuperView() View {
|
func (customView *CustomViewData) SuperView() View {
|
||||||
return customView.superView
|
return customView.superView
|
||||||
|
|
@ -57,29 +60,36 @@ func (customView *CustomViewData) setTag(tag string) {
|
||||||
|
|
||||||
// Get returns a value of the property with name defined by the argument.
|
// Get returns a value of the property with name defined by the argument.
|
||||||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||||||
func (customView *CustomViewData) Get(tag string) any {
|
func (customView *CustomViewData) Get(tag PropertyName) any {
|
||||||
return customView.superView.Get(tag)
|
return customView.superView.Get(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) getRaw(tag string) any {
|
func (customView *CustomViewData) getRaw(tag PropertyName) any {
|
||||||
return customView.superView.getRaw(tag)
|
return customView.superView.getRaw(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) setRaw(tag string, value any) {
|
func (customView *CustomViewData) setRaw(tag PropertyName, value any) {
|
||||||
customView.superView.setRaw(tag, value)
|
customView.superView.setRaw(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (customView *CustomViewData) setContent(value any) bool {
|
||||||
|
if container, ok := customView.superView.(ViewsContainer); ok {
|
||||||
|
return container.setContent(value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Set sets the value (second argument) of the property with name defined by the first argument.
|
// Set sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
func (customView *CustomViewData) Set(tag string, value any) bool {
|
func (customView *CustomViewData) Set(tag PropertyName, value any) bool {
|
||||||
return customView.superView.Set(tag, value)
|
return customView.superView.Set(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
// SetAnimated sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
func (customView *CustomViewData) SetAnimated(tag string, value any, animation Animation) bool {
|
func (customView *CustomViewData) SetAnimated(tag PropertyName, value any, animation AnimationProperty) bool {
|
||||||
return customView.superView.SetAnimated(tag, value, animation)
|
return customView.superView.SetAnimated(tag, value, animation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,20 +98,24 @@ func (customView *CustomViewData) SetParams(params Params) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChangeListener set the function to track the change of the View property
|
// SetChangeListener set the function to track the change of the View property
|
||||||
func (customView *CustomViewData) SetChangeListener(tag string, listener func(View, string)) {
|
func (customView *CustomViewData) SetChangeListener(tag PropertyName, listener func(View, PropertyName)) {
|
||||||
customView.superView.SetChangeListener(tag, listener)
|
customView.superView.SetChangeListener(tag, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the property with name defined by the argument
|
// Remove removes the property with name defined by the argument
|
||||||
func (customView *CustomViewData) Remove(tag string) {
|
func (customView *CustomViewData) Remove(tag PropertyName) {
|
||||||
customView.superView.Remove(tag)
|
customView.superView.Remove(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllTags returns an array of the set properties
|
// AllTags returns an array of the set properties
|
||||||
func (customView *CustomViewData) AllTags() []string {
|
func (customView *CustomViewData) AllTags() []PropertyName {
|
||||||
return customView.superView.AllTags()
|
return customView.superView.AllTags()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (customView *CustomViewData) empty() bool {
|
||||||
|
return customView.superView.empty()
|
||||||
|
}
|
||||||
|
|
||||||
// Clear removes all properties
|
// Clear removes all properties
|
||||||
func (customView *CustomViewData) Clear() {
|
func (customView *CustomViewData) Clear() {
|
||||||
customView.superView.Clear()
|
customView.superView.Clear()
|
||||||
|
|
@ -182,7 +196,7 @@ func (customView *CustomViewData) onItemResize(self View, index string, x, y, wi
|
||||||
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
|
customView.superView.onItemResize(customView.superView, index, x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) handleCommand(self View, command string, data DataObject) bool {
|
func (customView *CustomViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
return customView.superView.handleCommand(customView.superView, command, data)
|
return customView.superView.handleCommand(customView.superView, command, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,6 +224,10 @@ func (customView *CustomViewData) htmlProperties(self View, buffer *strings.Buil
|
||||||
customView.superView.htmlProperties(customView.superView, buffer)
|
customView.superView.htmlProperties(customView.superView, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (customView *CustomViewData) htmlDisabledProperty() bool {
|
||||||
|
return customView.superView.htmlDisabledProperty()
|
||||||
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
|
func (customView *CustomViewData) cssStyle(self View, builder cssBuilder) {
|
||||||
customView.superView.cssStyle(customView.superView, builder)
|
customView.superView.cssStyle(customView.superView, builder)
|
||||||
}
|
}
|
||||||
|
|
@ -274,9 +292,9 @@ func (customView *CustomViewData) ViewIndex(view View) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (customView *CustomViewData) exscludeTags() []string {
|
func (customView *CustomViewData) exscludeTags() []PropertyName {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
exsclude := []string{}
|
exsclude := []PropertyName{}
|
||||||
for tag, value := range customView.defaultParams {
|
for tag, value := range customView.defaultParams {
|
||||||
if value == customView.superView.getRaw(tag) {
|
if value == customView.superView.getRaw(tag) {
|
||||||
exsclude = append(exsclude, tag)
|
exsclude = append(exsclude, tag)
|
||||||
|
|
@ -290,7 +308,10 @@ func (customView *CustomViewData) exscludeTags() []string {
|
||||||
// String convert internal representation of a [CustomViewData] into a string.
|
// String convert internal representation of a [CustomViewData] into a string.
|
||||||
func (customView *CustomViewData) String() string {
|
func (customView *CustomViewData) String() string {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
return getViewString(customView, customView.exscludeTags())
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
writeViewStyle(customView.tag, customView, buffer, "", customView.exscludeTags())
|
||||||
|
return buffer.String()
|
||||||
}
|
}
|
||||||
return customView.tag + " { }"
|
return customView.tag + " { }"
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +323,7 @@ func (customView *CustomViewData) setScroll(x, y, width, height float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition returns the transition animation of the property(tag). Returns nil is there is no transition animation.
|
// Transition returns the transition animation of the property(tag). Returns nil is there is no transition animation.
|
||||||
func (customView *CustomViewData) Transition(tag string) Animation {
|
func (customView *CustomViewData) Transition(tag PropertyName) AnimationProperty {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
return customView.superView.Transition(tag)
|
return customView.superView.Transition(tag)
|
||||||
}
|
}
|
||||||
|
|
@ -310,17 +331,17 @@ func (customView *CustomViewData) Transition(tag string) Animation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transitions returns a map of transition animations. The result is always non-nil.
|
// Transitions returns a map of transition animations. The result is always non-nil.
|
||||||
func (customView *CustomViewData) Transitions() map[string]Animation {
|
func (customView *CustomViewData) Transitions() map[PropertyName]AnimationProperty {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
return customView.superView.Transitions()
|
return customView.superView.Transitions()
|
||||||
}
|
}
|
||||||
return map[string]Animation{}
|
return map[PropertyName]AnimationProperty{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
||||||
// removes the transition animation of the property if "animation" argument is nil.
|
// removes the transition animation of the property if "animation" argument is nil.
|
||||||
// The "tag" argument is the property name.
|
// The "tag" argument is the property name.
|
||||||
func (customView *CustomViewData) SetTransition(tag string, animation Animation) {
|
func (customView *CustomViewData) SetTransition(tag PropertyName, animation AnimationProperty) {
|
||||||
if customView.superView != nil {
|
if customView.superView != nil {
|
||||||
customView.superView.SetTransition(tag, animation)
|
customView.superView.SetTransition(tag, animation)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
data.go
6
data.go
|
|
@ -212,12 +212,12 @@ func (object *dataObject) ToParams() Params {
|
||||||
switch node.Type() {
|
switch node.Type() {
|
||||||
case TextNode:
|
case TextNode:
|
||||||
if text := node.Text(); text != "" {
|
if text := node.Text(); text != "" {
|
||||||
params[node.Tag()] = text
|
params[PropertyName(node.Tag())] = text
|
||||||
}
|
}
|
||||||
|
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
if obj := node.Object(); obj != nil {
|
if obj := node.Object(); obj != nil {
|
||||||
params[node.Tag()] = node.Object()
|
params[PropertyName(node.Tag())] = node.Object()
|
||||||
}
|
}
|
||||||
|
|
||||||
case ArrayNode:
|
case ArrayNode:
|
||||||
|
|
@ -234,7 +234,7 @@ func (object *dataObject) ToParams() Params {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(array) > 0 {
|
if len(array) > 0 {
|
||||||
params[node.Tag()] = array
|
params[PropertyName(node.Tag())] = array
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
387
dataList.go
387
dataList.go
|
|
@ -1,120 +1,105 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DataList is the constant for "data-list" property tag.
|
// DataList is the constant for "data-list" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColorPicker`, `DatePicker`, `EditView`, `NumberPicker`, `TimePicker`.
|
// Used by ColorPicker, DatePicker, EditView, NumberPicker, TimePicker.
|
||||||
|
//
|
||||||
|
// # Usage in ColorPicker
|
||||||
//
|
//
|
||||||
// Usage in `ColorPicker`:
|
|
||||||
// List of pre-defined colors.
|
// List of pre-defined colors.
|
||||||
//
|
//
|
||||||
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
|
// Supported types: []string, string, []fmt.Stringer, []Color, []any containing
|
||||||
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
|
// elements of string, fmt.Stringer, int, int8…int64, uint, uint8…uint64.
|
||||||
// `uint64`.
|
|
||||||
//
|
//
|
||||||
// Internal type is `[]string`, other types converted to it during assignment.
|
// Internal type is []string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - contain single item.
|
// - string - contain single item.
|
||||||
// `[]string` - an array of items.
|
// - []string - an array of items.
|
||||||
// `[]fmt.Stringer` - an array of objects convertible to a string.
|
// - []fmt.Stringer - an array of objects convertible to a string.
|
||||||
// `[]Color` - An array of color values which will be converted to a string array.
|
// - []Color - An array of color values which will be converted to a string array.
|
||||||
// `[]SizeUnit` - an array of size unit values which will be converted to a string array.
|
// - []any - this array must contain only types which were listed in Types section.
|
||||||
// `[]any` - this array must contain only types which were listed in Types section.
|
//
|
||||||
|
// # Usage in DatePicker
|
||||||
//
|
//
|
||||||
// Usage in `DatePicker`:
|
|
||||||
// List of predefined dates. If we set this property, date picker may have a drop-down menu with a list of these values.
|
// List of predefined dates. If we set this property, date picker may have a drop-down menu with a list of these values.
|
||||||
// Some browsers may ignore this property, such as Safari for macOS. The value of this property must be an array of
|
// Some browsers may ignore this property, such as Safari for macOS. The value of this property must be an array of
|
||||||
// strings in the format "YYYY-MM-DD".
|
// strings in the format "YYYY-MM-DD".
|
||||||
//
|
//
|
||||||
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
|
// Supported types: []string, string, []fmt.Stringer, []time.Time, []any containing elements of string, fmt.Stringer, time.Time.
|
||||||
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
|
|
||||||
// `uint64`.
|
|
||||||
//
|
//
|
||||||
// Internal type is `[]string`, other types converted to it during assignment.
|
// Internal type is []string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - contain single item.
|
// - string - contain single item.
|
||||||
// `[]string` - an array of items.
|
// - []string - an array of items.
|
||||||
// `[]fmt.Stringer` - an array of objects convertible to a string.
|
// - []fmt.Stringer - an array of objects convertible to a string.
|
||||||
// `[]Color` - An array of color values which will be converted to a string array.
|
// - []time.Time - an array of Time values which will be converted to a string array.
|
||||||
// `[]SizeUnit` - an array of size unit values which will be converted to a string array.
|
// - []any - this array must contain only types which were listed in Types section.
|
||||||
// `[]any` - this array must contain only types which were listed in Types section.
|
//
|
||||||
|
// # Usage in EditView
|
||||||
//
|
//
|
||||||
// Usage in `EditView`:
|
|
||||||
// Array of recommended values.
|
// Array of recommended values.
|
||||||
//
|
//
|
||||||
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
|
// Supported types: []string, string, []fmt.Stringer, and []any containing
|
||||||
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
|
// elements of string, fmt.Stringer, bool, rune, float32, float64, int, int8…int64, uint, uint8…uint64.
|
||||||
// `uint64`.
|
|
||||||
//
|
//
|
||||||
// Internal type is `[]string`, other types converted to it during assignment.
|
// Internal type is []string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - contain single item.
|
// - string - contain single item.
|
||||||
// `[]string` - an array of items.
|
// - []string - an array of items.
|
||||||
// `[]fmt.Stringer` - an array of objects convertible to a string.
|
// - []fmt.Stringer - an array of objects convertible to a string.
|
||||||
// `[]Color` - An array of color values which will be converted to a string array.
|
// - []any - this array must contain only types which were listed in Types section.
|
||||||
// `[]SizeUnit` - an array of size unit values which will be converted to a string array.
|
//
|
||||||
// `[]any` - this array must contain only types which were listed in Types section.
|
// # Usage in NumberPicker
|
||||||
//
|
//
|
||||||
// Usage in `NumberPicker`:
|
|
||||||
// Specify an array of recommended values.
|
// Specify an array of recommended values.
|
||||||
//
|
//
|
||||||
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]float`, `[]int`,
|
// Supported types: []string, string, []fmt.Stringer, []float, []int, []bool, []any containing elements
|
||||||
// `[]bool`, `[]any` containing elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8`
|
// of string, fmt.Stringer, rune, float32, float64, int, int8…int64, uint, uint8…uint64.
|
||||||
// … `int64`, `uint`, `uint8` … `uint64`.
|
|
||||||
//
|
//
|
||||||
// Internal type is `[]string`, other types converted to it during assignment.
|
// Internal type is []string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - must contain integer or floating point number, converted to `[]string`.
|
// - string - must contain integer or floating point number, converted to []string.
|
||||||
// `[]string` - an array of strings which must contain integer or floating point numbers, stored as is.
|
// - []string - an array of strings which must contain integer or floating point numbers, stored as is.
|
||||||
// `[]fmt.Stringer` - object which implement this interface must contain integer or floating point numbers, converted to a `[]string`.
|
// - []fmt.Stringer - object which implement this interface must contain integer or floating point numbers, converted to a []string.
|
||||||
// `[]Color` - an array of color values, converted to `[]string`.
|
// - []float - converted to []string.
|
||||||
// `[]SizeUnit` - an array of size unit, converted to `[]string`.
|
// - []int - converted to []string.
|
||||||
// `[]AngleUnit` - an array of angle unit, converted to `[]string`.
|
// - []any - an array which may contain types listed in Types section above, each value will be converted to a string and wrapped to array.
|
||||||
// `[]float` - converted to `[]string`.
|
//
|
||||||
// `[]int` - converted to `[]string`.
|
// # Usage in TimePicker
|
||||||
// `[]bool` - converted to `[]string`.
|
|
||||||
// `[]any` - an array which may contain types listed in Types section above, each value will be converted to a `string` and wrapped to array.
|
|
||||||
//
|
//
|
||||||
// Usage in `TimePicker`:
|
|
||||||
// An array of recommended values. The value of this property must be an array of strings in the format "HH:MM:SS" or
|
// An array of recommended values. The value of this property must be an array of strings in the format "HH:MM:SS" or
|
||||||
// "HH:MM".
|
// "HH:MM".
|
||||||
//
|
//
|
||||||
// Supported types: `[]string`, `string`, `[]fmt.Stringer`, `[]Color`, `[]SizeUnit`, `[]AngleUnit`, `[]any` containing
|
// Supported types: []string, string, []fmt.Stringer, []time.Time, []any containing elements of string, fmt.Stringer, time.Time.
|
||||||
// elements of `string`, `fmt.Stringer`, `bool`, `rune`, `float32`, `float64`, `int`, `int8` … `int64`, `uint`, `uint8` …
|
|
||||||
// `uint64`.
|
|
||||||
//
|
//
|
||||||
// Internal type is `[]string`, other types converted to it during assignment.
|
// Internal type is []string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - contain single item.
|
// - string - contain single item.
|
||||||
// `[]string` - an array of items.
|
// - []string - an array of items.
|
||||||
// `[]fmt.Stringer` - an array of objects convertible to a string.
|
// - []fmt.Stringer - an array of objects convertible to a string.
|
||||||
// `[]Color` - An array of color values which will be converted to a string array.
|
// - []time.Time - An array of Time values which will be converted to a string array.
|
||||||
// `[]SizeUnit` - an array of size unit values which will be converted to a string array.
|
// - []any - this array must contain only types which were listed in Types section.
|
||||||
// `[]any` - this array must contain only types which were listed in Types section.
|
DataList PropertyName = "data-list"
|
||||||
DataList = "data-list"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dataList struct {
|
func dataListID(view View) string {
|
||||||
dataList []string
|
|
||||||
dataListHtml bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dataList) dataListInit() {
|
|
||||||
list.dataList = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dataList) dataListID(view View) string {
|
|
||||||
return view.htmlID() + "-datalist"
|
return view.htmlID() + "-datalist"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dataList) normalizeDataListTag(tag string) string {
|
func normalizeDataListTag(tag PropertyName) PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case "datalist":
|
case "datalist":
|
||||||
return DataList
|
return DataList
|
||||||
|
|
@ -123,69 +108,205 @@ func (list *dataList) normalizeDataListTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dataList) setDataList(view View, value any, created bool) bool {
|
func setDataList(properties Properties, value any, dateTimeFormat string) []PropertyName {
|
||||||
items, ok := anyToStringArray(value)
|
if items, ok := anyToStringArray(value, dateTimeFormat); ok {
|
||||||
if !ok {
|
properties.setRaw(DataList, items)
|
||||||
notCompatibleType(DataList, value)
|
return []PropertyName{DataList}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list.dataList = items
|
notCompatibleType(DataList, value)
|
||||||
if created {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func anyToStringArray(value any, dateTimeFormat string) ([]string, bool) {
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
return []string{value}, true
|
||||||
|
|
||||||
|
case []string:
|
||||||
|
return value, true
|
||||||
|
|
||||||
|
case []DataValue:
|
||||||
|
items := make([]string, 0, len(value))
|
||||||
|
for _, val := range value {
|
||||||
|
if !val.IsObject() {
|
||||||
|
items = append(items, val.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []fmt.Stringer:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, str := range value {
|
||||||
|
items[i] = str.String()
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []Color:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, str := range value {
|
||||||
|
items[i] = str.String()
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []SizeUnit:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, str := range value {
|
||||||
|
items[i] = str.String()
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []AngleUnit:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, str := range value {
|
||||||
|
items[i] = str.String()
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []float32:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, val := range value {
|
||||||
|
items[i] = fmt.Sprintf("%g", float64(val))
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []float64:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, val := range value {
|
||||||
|
items[i] = fmt.Sprintf("%g", val)
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []int:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []uint:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []int8:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []uint8:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []int16:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []uint16:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []int32:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []uint32:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []int64:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []uint64:
|
||||||
|
return intArrayToStringArray(value), true
|
||||||
|
|
||||||
|
case []bool:
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, val := range value {
|
||||||
|
if val {
|
||||||
|
items[i] = "true"
|
||||||
|
} else {
|
||||||
|
items[i] = "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []time.Time:
|
||||||
|
if dateTimeFormat == "" {
|
||||||
|
dateTimeFormat = dateFormat + " " + timeFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]string, len(value))
|
||||||
|
for i, val := range value {
|
||||||
|
items[i] = val.Format(dateTimeFormat)
|
||||||
|
}
|
||||||
|
return items, true
|
||||||
|
|
||||||
|
case []any:
|
||||||
|
items := make([]string, 0, len(value))
|
||||||
|
for _, v := range value {
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
items = append(items, val)
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
items = append(items, val.String())
|
||||||
|
|
||||||
|
case bool:
|
||||||
|
if val {
|
||||||
|
items = append(items, "true")
|
||||||
|
} else {
|
||||||
|
items = append(items, "false")
|
||||||
|
}
|
||||||
|
|
||||||
|
case float32:
|
||||||
|
items = append(items, fmt.Sprintf("%g", float64(val)))
|
||||||
|
|
||||||
|
case float64:
|
||||||
|
items = append(items, fmt.Sprintf("%g", val))
|
||||||
|
|
||||||
|
case rune:
|
||||||
|
items = append(items, string(val))
|
||||||
|
|
||||||
|
default:
|
||||||
|
if n, ok := isInt(v); ok {
|
||||||
|
items = append(items, strconv.Itoa(n))
|
||||||
|
} else {
|
||||||
|
return []string{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataListProperty(properties Properties) []string {
|
||||||
|
if value := properties.getRaw(DataList); value != nil {
|
||||||
|
if items, ok := value.([]string); ok {
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dataListHtmlSubviews(view View, buffer *strings.Builder, normalizeItem func(text string, session Session) string) {
|
||||||
|
if items := getDataListProperty(view); len(items) > 0 {
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
dataListID := list.dataListID(view)
|
buffer.WriteString(`<datalist id="`)
|
||||||
buffer := allocStringBuilder()
|
buffer.WriteString(dataListID(view))
|
||||||
defer freeStringBuilder(buffer)
|
buffer.WriteString(`">`)
|
||||||
|
for _, text := range items {
|
||||||
|
text = normalizeItem(text, session)
|
||||||
|
|
||||||
if list.dataListHtml {
|
if strings.ContainsRune(text, '"') {
|
||||||
list.dataListItemsHtml(buffer)
|
text = strings.ReplaceAll(text, `"`, `"`)
|
||||||
session.updateInnerHTML(dataListID, buffer.String())
|
}
|
||||||
} else {
|
if strings.ContainsRune(text, '\n') {
|
||||||
list.dataListHtmlCode(view, buffer)
|
text = strings.ReplaceAll(text, "\n", `\n`)
|
||||||
session.appendToInnerHTML(view.parentHTMLID(), buffer.String())
|
}
|
||||||
list.dataListHtml = true
|
buffer.WriteString(`<option value="`)
|
||||||
session.updateProperty(view.htmlID(), "list", dataListID)
|
buffer.WriteString(text)
|
||||||
|
buffer.WriteString(`"></option>`)
|
||||||
}
|
}
|
||||||
}
|
buffer.WriteString(`</datalist>`)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dataList) dataListHtmlSubviews(view View, buffer *strings.Builder) {
|
|
||||||
if len(list.dataList) > 0 {
|
|
||||||
list.dataListHtmlCode(view, buffer)
|
|
||||||
list.dataListHtml = true
|
|
||||||
} else {
|
|
||||||
list.dataListHtml = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dataList) dataListHtmlCode(view View, buffer *strings.Builder) {
|
func dataListHtmlProperties(view View, buffer *strings.Builder) {
|
||||||
buffer.WriteString(`<datalist id="`)
|
if len(getDataListProperty(view)) > 0 {
|
||||||
buffer.WriteString(list.dataListID(view))
|
|
||||||
buffer.WriteString(`">`)
|
|
||||||
list.dataListItemsHtml(buffer)
|
|
||||||
buffer.WriteString(`</datalist>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dataList) dataListItemsHtml(buffer *strings.Builder) {
|
|
||||||
for _, text := range list.dataList {
|
|
||||||
if strings.ContainsRune(text, '"') {
|
|
||||||
text = strings.ReplaceAll(text, `"`, `"`)
|
|
||||||
}
|
|
||||||
if strings.ContainsRune(text, '\n') {
|
|
||||||
text = strings.ReplaceAll(text, "\n", `\n`)
|
|
||||||
}
|
|
||||||
buffer.WriteString(`<option value="`)
|
|
||||||
buffer.WriteString(text)
|
|
||||||
buffer.WriteString(`"></option>`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dataList) dataListHtmlProperties(view View, buffer *strings.Builder) {
|
|
||||||
if len(list.dataList) > 0 {
|
|
||||||
buffer.WriteString(` list="`)
|
buffer.WriteString(` list="`)
|
||||||
buffer.WriteString(list.dataListID(view))
|
buffer.WriteString(dataListID(view))
|
||||||
buffer.WriteString(`"`)
|
buffer.WriteString(`"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,16 +314,8 @@ func (list *dataList) dataListHtmlProperties(view View, buffer *strings.Builder)
|
||||||
// GetDataList returns the data list of an editor.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDataList(view View, subviewID ...string) []string {
|
func GetDataList(view View, subviewID ...string) []string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
return getDataListProperty(view)
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(DataList); value != nil {
|
|
||||||
if list, ok := value.([]string); ok {
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{}
|
return []string{}
|
||||||
|
|
|
||||||
464
datePicker.go
464
datePicker.go
|
|
@ -10,104 +10,104 @@ import (
|
||||||
const (
|
const (
|
||||||
// DateChangedEvent is the constant for "date-changed" property tag.
|
// DateChangedEvent is the constant for "date-changed" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DatePicker`.
|
// Used by DatePicker.
|
||||||
// Occur when date picker value has been changed.
|
// Occur when date picker value has been changed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(picker rui.DatePicker, newDate, oldDate time.Time)`.
|
// func(picker rui.DatePicker, newDate time.Time, oldDate time.Time)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// picker - Interface of a date picker which generated this event,
|
// - picker - Interface of a date picker which generated this event,
|
||||||
// newDate - New date value,
|
// - newDate - New date value,
|
||||||
// oldDate - Old date value.
|
// - oldDate - Old date value.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(picker rui.DatePicker, newDate time.Time)`,
|
// func(picker rui.DatePicker, newDate time.Time)
|
||||||
// `func(newDate, oldDate time.Time)`,
|
// func(newDate time.Time, oldDate time.Time)
|
||||||
// `func(newDate time.Time)`,
|
// func(newDate time.Time)
|
||||||
// `func(picker rui.DatePicker)`,
|
// func(picker rui.DatePicker)
|
||||||
// `func()`.
|
// func()
|
||||||
DateChangedEvent = "date-changed"
|
DateChangedEvent PropertyName = "date-changed"
|
||||||
|
|
||||||
// DatePickerMin is the constant for "date-picker-min" property tag.
|
// DatePickerMin is the constant for "date-picker-min" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DatePicker`.
|
// Used by DatePicker.
|
||||||
// Minimum date value.
|
// Minimum date value.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "YYYYMMDD" - "20240102".
|
// - "YYYYMMDD" - "20240102".
|
||||||
// "Mon-DD-YYYY" - "Jan-02-24".
|
// - "Mon-DD-YYYY" - "Jan-02-24".
|
||||||
// "Mon-DD-YY" - "Jan-02-2024".
|
// - "Mon-DD-YY" - "Jan-02-2024".
|
||||||
// "DD-Mon-YYYY" - "02-Jan-2024".
|
// - "DD-Mon-YYYY" - "02-Jan-2024".
|
||||||
// "YYYY-MM-DD" - "2024-01-02".
|
// - "YYYY-MM-DD" - "2024-01-02".
|
||||||
// "Month DD, YYYY" - "January 02, 2024".
|
// - "Month DD, YYYY" - "January 02, 2024".
|
||||||
// "DD Month YYYY" - "02 January 2024".
|
// - "DD Month YYYY" - "02 January 2024".
|
||||||
// "MM/DD/YYYY" - "01/02/2024".
|
// - "MM/DD/YYYY" - "01/02/2024".
|
||||||
// "MM/DD/YY" - "01/02/24".
|
// - "MM/DD/YY" - "01/02/24".
|
||||||
// "MMDDYY" - "010224".
|
// - "MMDDYY" - "010224".
|
||||||
DatePickerMin = "date-picker-min"
|
DatePickerMin PropertyName = "date-picker-min"
|
||||||
|
|
||||||
// DatePickerMax is the constant for "date-picker-max" property tag.
|
// DatePickerMax is the constant for "date-picker-max" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DatePicker`.
|
// Used by DatePicker.
|
||||||
// Maximum date value.
|
// Maximum date value.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "YYYYMMDD" - "20240102".
|
// - "YYYYMMDD" - "20240102".
|
||||||
// "Mon-DD-YYYY" - "Jan-02-24".
|
// - "Mon-DD-YYYY" - "Jan-02-24".
|
||||||
// "Mon-DD-YY" - "Jan-02-2024".
|
// - "Mon-DD-YY" - "Jan-02-2024".
|
||||||
// "DD-Mon-YYYY" - "02-Jan-2024".
|
// - "DD-Mon-YYYY" - "02-Jan-2024".
|
||||||
// "YYYY-MM-DD" - "2024-01-02".
|
// - "YYYY-MM-DD" - "2024-01-02".
|
||||||
// "Month DD, YYYY" - "January 02, 2024".
|
// - "Month DD, YYYY" - "January 02, 2024".
|
||||||
// "DD Month YYYY" - "02 January 2024".
|
// - "DD Month YYYY" - "02 January 2024".
|
||||||
// "MM/DD/YYYY" - "01/02/2024".
|
// - "MM/DD/YYYY" - "01/02/2024".
|
||||||
// "MM/DD/YY" - "01/02/24".
|
// - "MM/DD/YY" - "01/02/24".
|
||||||
// "MMDDYY" - "010224".
|
// - "MMDDYY" - "010224".
|
||||||
DatePickerMax = "date-picker-max"
|
DatePickerMax PropertyName = "date-picker-max"
|
||||||
|
|
||||||
// DatePickerStep is the constant for "date-picker-step" property tag.
|
// DatePickerStep is the constant for "date-picker-step" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DatePicker`.
|
// Used by DatePicker.
|
||||||
// Date change step in days.
|
// Date change step in days.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// >= `0` or >= "0" - Step value in days used to increment or decrement date.
|
// positive value - Step value in days used to increment or decrement date.
|
||||||
DatePickerStep = "date-picker-step"
|
DatePickerStep PropertyName = "date-picker-step"
|
||||||
|
|
||||||
// DatePickerValue is the constant for "date-picker-value" property tag.
|
// DatePickerValue is the constant for "date-picker-value" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DatePicker`.
|
// Used by DatePicker.
|
||||||
// Current value.
|
// Current value.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "YYYYMMDD" - "20240102".
|
// - "YYYYMMDD" - "20240102".
|
||||||
// "Mon-DD-YYYY" - "Jan-02-24".
|
// - "Mon-DD-YYYY" - "Jan-02-24".
|
||||||
// "Mon-DD-YY" - "Jan-02-2024".
|
// - "Mon-DD-YY" - "Jan-02-2024".
|
||||||
// "DD-Mon-YYYY" - "02-Jan-2024".
|
// - "DD-Mon-YYYY" - "02-Jan-2024".
|
||||||
// "YYYY-MM-DD" - "2024-01-02".
|
// - "YYYY-MM-DD" - "2024-01-02".
|
||||||
// "Month DD, YYYY" - "January 02, 2024".
|
// - "Month DD, YYYY" - "January 02, 2024".
|
||||||
// "DD Month YYYY" - "02 January 2024".
|
// - "DD Month YYYY" - "02 January 2024".
|
||||||
// "MM/DD/YYYY" - "01/02/2024".
|
// - "MM/DD/YYYY" - "01/02/2024".
|
||||||
// "MM/DD/YY" - "01/02/24".
|
// - "MM/DD/YY" - "01/02/24".
|
||||||
// "MMDDYY" - "010224".
|
// - "MMDDYY" - "010224".
|
||||||
DatePickerValue = "date-picker-value"
|
DatePickerValue PropertyName = "date-picker-value"
|
||||||
|
|
||||||
dateFormat = "2006-01-02"
|
dateFormat = "2006-01-02"
|
||||||
)
|
)
|
||||||
|
|
@ -119,8 +119,6 @@ type DatePicker interface {
|
||||||
|
|
||||||
type datePickerData struct {
|
type datePickerData struct {
|
||||||
viewData
|
viewData
|
||||||
dataList
|
|
||||||
dateChangedListeners []func(DatePicker, time.Time, time.Time)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatePicker create new DatePicker object and return it
|
// NewDatePicker create new DatePicker object and return it
|
||||||
|
|
@ -132,248 +130,164 @@ func NewDatePicker(session Session, params Params) DatePicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDatePicker(session Session) View {
|
func newDatePicker(session Session) View {
|
||||||
return NewDatePicker(session, nil)
|
return new(datePickerData) // NewDatePicker(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) init(session Session) {
|
func (picker *datePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "DatePicker"
|
picker.tag = "DatePicker"
|
||||||
picker.hasHtmlDisabled = true
|
picker.hasHtmlDisabled = true
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
picker.normalize = normalizeDatePickerTag
|
||||||
picker.dataListInit()
|
picker.set = picker.setFunc
|
||||||
}
|
picker.changed = picker.propertyChanged
|
||||||
|
|
||||||
func (picker *datePickerData) String() string {
|
|
||||||
return getViewString(picker, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) Focusable() bool {
|
func (picker *datePickerData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) normalizeTag(tag string) string {
|
func normalizeDatePickerTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case Type, Min, Max, Step, Value:
|
case Type, Min, Max, Step, Value:
|
||||||
return "date-picker-" + tag
|
return "date-picker-" + tag
|
||||||
}
|
}
|
||||||
|
|
||||||
return tag
|
return normalizeDataListTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) Remove(tag string) {
|
func stringToDate(value string) (time.Time, bool) {
|
||||||
picker.remove(picker.normalizeTag(tag))
|
format := "20060102"
|
||||||
}
|
if strings.ContainsRune(value, '-') {
|
||||||
|
if part := strings.Split(value, "-"); len(part) == 3 {
|
||||||
func (picker *datePickerData) remove(tag string) {
|
if part[0] != "" && part[0][0] > '9' {
|
||||||
switch tag {
|
if len(part[2]) == 2 {
|
||||||
case DateChangedEvent:
|
format = "Jan-02-06"
|
||||||
if len(picker.dateChangedListeners) > 0 {
|
} else {
|
||||||
picker.dateChangedListeners = []func(DatePicker, time.Time, time.Time){}
|
format = "Jan-02-2006"
|
||||||
picker.propertyChangedEvent(tag)
|
}
|
||||||
}
|
} else if part[1] != "" && part[1][0] > '9' {
|
||||||
return
|
format = "02-Jan-2006"
|
||||||
|
} else {
|
||||||
case DatePickerMin:
|
format = "2006-01-02"
|
||||||
delete(picker.properties, DatePickerMin)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DatePickerMax:
|
|
||||||
delete(picker.properties, DatePickerMax)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Max)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DatePickerStep:
|
|
||||||
delete(picker.properties, DatePickerStep)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Step)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DatePickerValue:
|
|
||||||
if _, ok := picker.properties[DatePickerValue]; ok {
|
|
||||||
oldDate := GetDatePickerValue(picker)
|
|
||||||
delete(picker.properties, DatePickerValue)
|
|
||||||
date := GetDatePickerValue(picker)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
|
||||||
}
|
}
|
||||||
for _, listener := range picker.dateChangedListeners {
|
}
|
||||||
listener(picker, date, oldDate)
|
} else if strings.ContainsRune(value, ' ') {
|
||||||
|
if part := strings.Split(value, " "); len(part) == 3 {
|
||||||
|
if part[0] != "" && part[0][0] > '9' {
|
||||||
|
format = "January 02, 2006"
|
||||||
|
} else {
|
||||||
|
format = "02 January 2006"
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
} else if strings.ContainsRune(value, '/') {
|
||||||
case DataList:
|
if part := strings.Split(value, "/"); len(part) == 3 {
|
||||||
if len(picker.dataList.dataList) > 0 {
|
if len(part[2]) == 2 {
|
||||||
picker.setDataList(picker, []string{}, true)
|
format = "01/02/06"
|
||||||
|
} else {
|
||||||
|
format = "01/02/2006"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if len(value) == 6 {
|
||||||
default:
|
format = "010206"
|
||||||
picker.viewData.remove(tag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *datePickerData) Set(tag string, value any) bool {
|
|
||||||
return picker.set(picker.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *datePickerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
picker.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeValue := func(tag string) (time.Time, bool) {
|
if date, err := time.Parse(format, value); err == nil {
|
||||||
|
return date, true
|
||||||
|
}
|
||||||
|
return time.Now(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (picker *datePickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
|
|
||||||
|
setDateValue := func(tag PropertyName) []PropertyName {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
picker.properties[tag] = value
|
picker.setRaw(tag, value)
|
||||||
return value, true
|
return []PropertyName{tag}
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if text, ok := picker.Session().resolveConstants(value); ok {
|
if isConstantName(value) {
|
||||||
format := "20060102"
|
picker.setRaw(tag, value)
|
||||||
if strings.ContainsRune(text, '-') {
|
return []PropertyName{tag}
|
||||||
if part := strings.Split(text, "-"); len(part) == 3 {
|
}
|
||||||
if part[0] != "" && part[0][0] > '9' {
|
|
||||||
if len(part[2]) == 2 {
|
|
||||||
format = "Jan-02-06"
|
|
||||||
} else {
|
|
||||||
format = "Jan-02-2006"
|
|
||||||
}
|
|
||||||
} else if part[1] != "" && part[1][0] > '9' {
|
|
||||||
format = "02-Jan-2006"
|
|
||||||
} else {
|
|
||||||
format = "2006-01-02"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if strings.ContainsRune(text, ' ') {
|
|
||||||
if part := strings.Split(text, " "); len(part) == 3 {
|
|
||||||
if part[0] != "" && part[0][0] > '9' {
|
|
||||||
format = "January 02, 2006"
|
|
||||||
} else {
|
|
||||||
format = "02 January 2006"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if strings.ContainsRune(text, '/') {
|
|
||||||
if part := strings.Split(text, "/"); len(part) == 3 {
|
|
||||||
if len(part[2]) == 2 {
|
|
||||||
format = "01/02/06"
|
|
||||||
} else {
|
|
||||||
format = "01/02/2006"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if len(text) == 6 {
|
|
||||||
format = "010206"
|
|
||||||
}
|
|
||||||
|
|
||||||
if date, err := time.Parse(format, text); err == nil {
|
if date, ok := stringToDate(value); ok {
|
||||||
picker.properties[tag] = value
|
picker.setRaw(tag, date)
|
||||||
return date, true
|
return []PropertyName{tag}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return time.Now(), false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
|
case DatePickerMin, DatePickerMax:
|
||||||
|
return setDateValue(tag)
|
||||||
|
|
||||||
|
case DatePickerStep:
|
||||||
|
return setIntProperty(picker, DatePickerStep, value)
|
||||||
|
|
||||||
|
case DatePickerValue:
|
||||||
|
picker.setRaw("old-date", GetDatePickerValue(picker))
|
||||||
|
return setDateValue(tag)
|
||||||
|
|
||||||
|
case DateChangedEvent:
|
||||||
|
return setTwoArgEventListener[DatePicker, time.Time](picker, tag, value)
|
||||||
|
|
||||||
|
case DataList:
|
||||||
|
return setDataList(picker, value, dateFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return picker.viewData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (picker *datePickerData) propertyChanged(tag PropertyName) {
|
||||||
|
|
||||||
|
session := picker.Session()
|
||||||
|
|
||||||
|
switch tag {
|
||||||
|
|
||||||
case DatePickerMin:
|
case DatePickerMin:
|
||||||
old, oldOK := getDateProperty(picker, DatePickerMin, Min)
|
if date, ok := GetDatePickerMin(picker); ok {
|
||||||
if date, ok := setTimeValue(DatePickerMin); ok {
|
session.updateProperty(picker.htmlID(), "min", date.Format(dateFormat))
|
||||||
if !oldOK || date != old {
|
} else {
|
||||||
if picker.created {
|
session.removeProperty(picker.htmlID(), "min")
|
||||||
picker.session.updateProperty(picker.htmlID(), Min, date.Format(dateFormat))
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatePickerMax:
|
case DatePickerMax:
|
||||||
old, oldOK := getDateProperty(picker, DatePickerMax, Max)
|
if date, ok := GetDatePickerMax(picker); ok {
|
||||||
if date, ok := setTimeValue(DatePickerMax); ok {
|
session.updateProperty(picker.htmlID(), "max", date.Format(dateFormat))
|
||||||
if !oldOK || date != old {
|
} else {
|
||||||
if picker.created {
|
session.removeProperty(picker.htmlID(), "max")
|
||||||
picker.session.updateProperty(picker.htmlID(), Max, date.Format(dateFormat))
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatePickerStep:
|
case DatePickerStep:
|
||||||
oldStep := GetDatePickerStep(picker)
|
if step := GetDatePickerStep(picker); step > 0 {
|
||||||
if picker.setIntProperty(DatePickerStep, value) {
|
session.updateProperty(picker.htmlID(), "step", strconv.Itoa(step))
|
||||||
if step := GetDatePickerStep(picker); oldStep != step {
|
} else {
|
||||||
if picker.created {
|
session.removeProperty(picker.htmlID(), "step")
|
||||||
if step > 0 {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Step, strconv.Itoa(step))
|
|
||||||
} else {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Step)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case DatePickerValue:
|
case DatePickerValue:
|
||||||
oldDate := GetDatePickerValue(picker)
|
date := GetDatePickerValue(picker)
|
||||||
if date, ok := setTimeValue(DatePickerValue); ok {
|
session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
||||||
if date != oldDate {
|
|
||||||
if picker.created {
|
if listeners := GetDateChangedListeners(picker); len(listeners) > 0 {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
|
oldDate := time.Now()
|
||||||
|
if value := picker.getRaw("old-date"); value != nil {
|
||||||
|
if date, ok := value.(time.Time); ok {
|
||||||
|
oldDate = date
|
||||||
}
|
}
|
||||||
for _, listener := range picker.dateChangedListeners {
|
|
||||||
listener(picker, date, oldDate)
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
return true
|
for _, listener := range listeners {
|
||||||
|
listener(picker, date, oldDate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case DateChangedEvent:
|
|
||||||
listeners, ok := valueToEventWithOldListeners[DatePicker, time.Time](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(DatePicker, time.Time, time.Time){}
|
|
||||||
}
|
|
||||||
picker.dateChangedListeners = listeners
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return picker.setDataList(picker, value, picker.created)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return picker.viewData.set(tag, value)
|
picker.viewData.propertyChanged(tag)
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *datePickerData) Get(tag string) any {
|
|
||||||
return picker.get(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *datePickerData) get(tag string) any {
|
|
||||||
switch tag {
|
|
||||||
case DateChangedEvent:
|
|
||||||
return picker.dateChangedListeners
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return picker.dataList.dataList
|
|
||||||
|
|
||||||
default:
|
|
||||||
return picker.viewData.get(tag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,7 +296,13 @@ func (picker *datePickerData) htmlTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (picker *datePickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
picker.dataListHtmlSubviews(self, buffer)
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
||||||
|
text, _ = session.resolveConstants(text)
|
||||||
|
if date, ok := stringToDate(text); ok {
|
||||||
|
return date.Format(dateFormat)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
|
|
@ -417,10 +337,10 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.dataListHtmlProperties(picker, buffer)
|
dataListHtmlProperties(picker, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *datePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *datePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
|
|
@ -428,9 +348,12 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
|
||||||
oldValue := GetDatePickerValue(picker)
|
oldValue := GetDatePickerValue(picker)
|
||||||
picker.properties[DatePickerValue] = value
|
picker.properties[DatePickerValue] = value
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.dateChangedListeners {
|
for _, listener := range GetDateChangedListeners(picker) {
|
||||||
listener(picker, value, oldValue)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
|
if listener, ok := picker.changeListener[DatePickerValue]; ok {
|
||||||
|
listener(picker, DatePickerValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +363,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data
|
||||||
return picker.viewData.handleCommand(self, command, data)
|
return picker.viewData.handleCommand(self, command, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
func getDateProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool) {
|
||||||
valueToTime := func(value any) (time.Time, bool) {
|
valueToTime := func(value any) (time.Time, bool) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -449,7 +372,7 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if text, ok := view.Session().resolveConstants(value); ok {
|
if text, ok := view.Session().resolveConstants(value); ok {
|
||||||
if result, err := time.Parse(dateFormat, text); err == nil {
|
if result, ok := stringToDate(text); ok {
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -463,9 +386,11 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if value := valueFromStyle(view, shortTag); value != nil {
|
for _, tag := range []PropertyName{mainTag, shortTag} {
|
||||||
if result, ok := valueToTime(value); ok {
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
return result, true
|
if result, ok := valueToTime(value); ok {
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -477,10 +402,7 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
// "false" as the second value otherwise.
|
// "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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getDateProperty(view, DatePickerMin, Min)
|
return getDateProperty(view, DatePickerMin, Min)
|
||||||
}
|
}
|
||||||
return time.Now(), false
|
return time.Now(), false
|
||||||
|
|
@ -490,10 +412,7 @@ func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
||||||
// "false" as the second value otherwise.
|
// "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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) {
|
func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getDateProperty(view, DatePickerMax, Max)
|
return getDateProperty(view, DatePickerMax, Max)
|
||||||
}
|
}
|
||||||
return time.Now(), false
|
return time.Now(), false
|
||||||
|
|
@ -508,10 +427,7 @@ func GetDatePickerStep(view View, subviewID ...string) int {
|
||||||
// GetDatePickerValue returns the date of DatePicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDatePickerValue(view View, subviewID ...string) time.Time {
|
func GetDatePickerValue(view View, subviewID ...string) time.Time {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view == nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return time.Now()
|
return time.Now()
|
||||||
}
|
}
|
||||||
date, _ := getDateProperty(view, DatePickerValue, Value)
|
date, _ := getDateProperty(view, DatePickerValue, Value)
|
||||||
|
|
@ -522,5 +438,5 @@ func GetDatePickerValue(view View, subviewID ...string) time.Time {
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time, time.Time) {
|
||||||
return getEventWithOldListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
return getTwoArgEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
177
detailsView.go
177
detailsView.go
|
|
@ -6,26 +6,37 @@ import "strings"
|
||||||
const (
|
const (
|
||||||
// Summary is the constant for "summary" property tag.
|
// Summary is the constant for "summary" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DetailsView`.
|
// Used by DetailsView.
|
||||||
// The content of this property is used as the label for the disclosure widget.
|
// The content of this property is used as the label for the disclosure widget.
|
||||||
//
|
//
|
||||||
// Supported types: `string`, `View`.
|
// Supported types:
|
||||||
//
|
// - string - Summary as a text.
|
||||||
// `string` - Summary as a text.
|
// - View - Summary as a view, in this case it can be quite complex if needed.
|
||||||
// `View` - Summary as a view, in this case it can be quite complex if needed.
|
Summary PropertyName = "summary"
|
||||||
Summary = "summary"
|
|
||||||
|
|
||||||
// Expanded is the constant for "expanded" property tag.
|
// Expanded is the constant for "expanded" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DetailsView`.
|
// Used by DetailsView.
|
||||||
// Controls the content expanded state of the details view. Default value is `false`.
|
// Controls the content expanded state of the details view. Default value is false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Content is visible.
|
// - true, 1, "true", "yes", "on", or "1" - Content is visible.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Content is collapsed(hidden).
|
// - false, 0, "false", "no", "off", or "0" - Content is collapsed (hidden).
|
||||||
Expanded = "expanded"
|
Expanded PropertyName = "expanded"
|
||||||
|
|
||||||
|
// HideSummaryMarker is the constant for "hide-summary-marker" property tag.
|
||||||
|
//
|
||||||
|
// Used by DetailsView.
|
||||||
|
// Allows you to hide the summary marker (▶︎). Default value is false.
|
||||||
|
//
|
||||||
|
// Supported types: bool, int, string.
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// - true, 1, "true", "yes", "on", or "1" - The summary marker is hidden.
|
||||||
|
// - false, 0, "false", "no", "off", or "0" - The summary marker is displayed (default value).
|
||||||
|
HideSummaryMarker PropertyName = "hide-summary-marker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DetailsView represent a DetailsView view, which is a collapsible container of views
|
// DetailsView represent a DetailsView view, which is a collapsible container of views
|
||||||
|
|
@ -46,19 +57,21 @@ func NewDetailsView(session Session, params Params) DetailsView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDetailsView(session Session) View {
|
func newDetailsView(session Session) View {
|
||||||
return NewDetailsView(session, nil)
|
return new(detailsViewData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of DetailsView by default values
|
// Init initialize fields of DetailsView by default values
|
||||||
func (detailsView *detailsViewData) init(session Session) {
|
func (detailsView *detailsViewData) init(session Session) {
|
||||||
detailsView.viewsContainerData.init(session)
|
detailsView.viewsContainerData.init(session)
|
||||||
detailsView.tag = "DetailsView"
|
detailsView.tag = "DetailsView"
|
||||||
|
detailsView.set = detailsView.setFunc
|
||||||
|
detailsView.changed = detailsView.propertyChanged
|
||||||
//detailsView.systemClass = "ruiDetailsView"
|
//detailsView.systemClass = "ruiDetailsView"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (detailsView *detailsViewData) Views() []View {
|
func (detailsView *detailsViewData) Views() []View {
|
||||||
views := detailsView.viewsContainerData.Views()
|
views := detailsView.viewsContainerData.Views()
|
||||||
if summary := detailsView.get(Summary); summary != nil {
|
if summary := detailsView.Get(Summary); summary != nil {
|
||||||
switch summary := summary.(type) {
|
switch summary := summary.(type) {
|
||||||
case View:
|
case View:
|
||||||
return append([]View{summary}, views...)
|
return append([]View{summary}, views...)
|
||||||
|
|
@ -67,94 +80,53 @@ func (detailsView *detailsViewData) Views() []View {
|
||||||
return views
|
return views
|
||||||
}
|
}
|
||||||
|
|
||||||
func (detailsView *detailsViewData) Remove(tag string) {
|
func (detailsView *detailsViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
detailsView.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (detailsView *detailsViewData) remove(tag string) {
|
|
||||||
detailsView.viewsContainerData.remove(tag)
|
|
||||||
if detailsView.created {
|
|
||||||
switch tag {
|
|
||||||
case Summary:
|
|
||||||
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
|
||||||
|
|
||||||
case Expanded:
|
|
||||||
detailsView.session.removeProperty(detailsView.htmlID(), "open")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (detailsView *detailsViewData) Set(tag string, value any) bool {
|
|
||||||
return detailsView.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (detailsView *detailsViewData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
detailsView.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Summary:
|
case Summary:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
detailsView.properties[Summary] = value
|
detailsView.setRaw(Summary, value)
|
||||||
|
|
||||||
case View:
|
case View:
|
||||||
detailsView.properties[Summary] = value
|
detailsView.setRaw(Summary, value)
|
||||||
value.setParentID(detailsView.htmlID())
|
value.setParentID(detailsView.htmlID())
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
|
if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
|
||||||
detailsView.properties[Summary] = view
|
detailsView.setRaw(Summary, view)
|
||||||
view.setParentID(detailsView.htmlID())
|
view.setParentID(detailsView.htmlID())
|
||||||
} else {
|
} else {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
|
||||||
if detailsView.created {
|
|
||||||
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
|
||||||
}
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
return detailsView.viewsContainerData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (detailsView *detailsViewData) propertyChanged(tag PropertyName) {
|
||||||
|
switch tag {
|
||||||
|
case Summary, HideSummaryMarker:
|
||||||
|
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
||||||
|
|
||||||
case Expanded:
|
case Expanded:
|
||||||
if !detailsView.setBoolProperty(tag, value) {
|
if IsDetailsExpanded(detailsView) {
|
||||||
notCompatibleType(tag, value)
|
detailsView.Session().updateProperty(detailsView.htmlID(), "open", "")
|
||||||
return false
|
} else {
|
||||||
}
|
detailsView.Session().removeProperty(detailsView.htmlID(), "open")
|
||||||
if detailsView.created {
|
|
||||||
if IsDetailsExpanded(detailsView) {
|
|
||||||
detailsView.session.updateProperty(detailsView.htmlID(), "open", "")
|
|
||||||
} else {
|
|
||||||
detailsView.session.removeProperty(detailsView.htmlID(), "open")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case NotTranslate:
|
case NotTranslate:
|
||||||
if !detailsView.viewData.set(tag, value) {
|
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
||||||
return false
|
|
||||||
}
|
|
||||||
if detailsView.created {
|
|
||||||
updateInnerHTML(detailsView.htmlID(), detailsView.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return detailsView.viewsContainerData.Set(tag, value)
|
detailsView.viewsContainerData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
detailsView.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (detailsView *detailsViewData) Get(tag string) any {
|
|
||||||
return detailsView.get(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (detailsView *detailsViewData) get(tag string) any {
|
|
||||||
return detailsView.viewsContainerData.get(tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (detailsView *detailsViewData) htmlTag() string {
|
func (detailsView *detailsViewData) htmlTag() string {
|
||||||
|
|
@ -170,31 +142,59 @@ func (detailsView *detailsViewData) htmlProperties(self View, buffer *strings.Bu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
summary := false
|
||||||
|
hidden := IsSummaryMarkerHidden(detailsView)
|
||||||
|
|
||||||
if value, ok := detailsView.properties[Summary]; ok {
|
if value, ok := detailsView.properties[Summary]; ok {
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if !GetNotTranslate(detailsView) {
|
if !GetNotTranslate(detailsView) {
|
||||||
value, _ = detailsView.session.GetString(value)
|
value, _ = detailsView.session.GetString(value)
|
||||||
}
|
}
|
||||||
buffer.WriteString("<summary>")
|
if hidden {
|
||||||
|
buffer.WriteString(`<summary class="hiddenMarker">`)
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("<summary>")
|
||||||
|
}
|
||||||
buffer.WriteString(value)
|
buffer.WriteString(value)
|
||||||
buffer.WriteString("</summary>")
|
buffer.WriteString("</summary>")
|
||||||
|
summary = true
|
||||||
|
|
||||||
case View:
|
case View:
|
||||||
buffer.WriteString("<summary>")
|
if hidden {
|
||||||
viewHTML(value, buffer)
|
buffer.WriteString(`<summary class="hiddenMarker">`)
|
||||||
buffer.WriteString("</summary>")
|
viewHTML(value, buffer, "")
|
||||||
|
buffer.WriteString("</summary>")
|
||||||
|
} else if value.htmlTag() == "div" {
|
||||||
|
viewHTML(value, buffer, "summary")
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(`<summary><div style="display: inline-block;">`)
|
||||||
|
viewHTML(value, buffer, "")
|
||||||
|
buffer.WriteString("</div></summary>")
|
||||||
|
}
|
||||||
|
summary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !summary {
|
||||||
|
if hidden {
|
||||||
|
buffer.WriteString(`<summary class="hiddenMarker"></summary>`)
|
||||||
|
} else {
|
||||||
|
buffer.WriteString("<summary></summary>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detailsView.viewsContainerData.htmlSubviews(self, buffer)
|
detailsView.viewsContainerData.htmlSubviews(self, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (detailsView *detailsViewData) handleCommand(self View, command string, data DataObject) bool {
|
func (detailsView *detailsViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
if command == "details-open" {
|
if command == "details-open" {
|
||||||
if n, ok := dataIntProperty(data, "open"); ok {
|
if n, ok := dataIntProperty(data, "open"); ok {
|
||||||
detailsView.properties[Expanded] = (n != 0)
|
detailsView.properties[Expanded] = (n != 0)
|
||||||
detailsView.propertyChangedEvent(Expanded)
|
if listener, ok := detailsView.changeListener[Expanded]; ok {
|
||||||
|
listener(detailsView, Expanded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -204,10 +204,7 @@ func (detailsView *detailsViewData) handleCommand(self View, command string, dat
|
||||||
// GetDetailsSummary returns a value of the Summary property of DetailsView.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDetailsSummary(view View, subviewID ...string) View {
|
func GetDetailsSummary(view View, subviewID ...string) View {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(Summary); value != nil {
|
if value := view.Get(Summary); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
@ -226,3 +223,9 @@ func GetDetailsSummary(view View, subviewID ...string) View {
|
||||||
func IsDetailsExpanded(view View, subviewID ...string) bool {
|
func IsDetailsExpanded(view View, subviewID ...string) bool {
|
||||||
return boolStyledProperty(view, subviewID, Expanded, false)
|
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.
|
||||||
|
func IsSummaryMarkerHidden(view View, subviewID ...string) bool {
|
||||||
|
return boolStyledProperty(view, subviewID, HideSummaryMarker, false)
|
||||||
|
}
|
||||||
|
|
|
||||||
487
dropDownList.go
487
dropDownList.go
|
|
@ -1,38 +1,37 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DropDownEvent is the constant for "drop-down-event" property tag.
|
// DropDownEvent is the constant for "drop-down-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `DropDownList`.
|
// Used by DropDownList.
|
||||||
// Occur when a list item becomes selected.
|
// Occur when a list item becomes selected.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(list rui.DropDownList, index int)`.
|
//
|
||||||
|
// func(list rui.DropDownList, index int)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// list - Interface of a drop down list which generated this event,
|
// - list - Interface of a drop down list which generated this event,
|
||||||
// index - Index of a newly selected item.
|
// - index - Index of a newly selected item.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
const DropDownEvent = "drop-down-event"
|
//
|
||||||
|
// func(index int)
|
||||||
|
// func(list rui.DropDownList)
|
||||||
|
// func()
|
||||||
|
const DropDownEvent PropertyName = "drop-down-event"
|
||||||
|
|
||||||
// DropDownList represent a DropDownList view
|
// DropDownList represent a DropDownList view
|
||||||
type DropDownList interface {
|
type DropDownList interface {
|
||||||
View
|
View
|
||||||
getItems() []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type dropDownListData struct {
|
type dropDownListData struct {
|
||||||
viewData
|
viewData
|
||||||
items []string
|
|
||||||
disabledItems []any
|
|
||||||
itemSeparators []any
|
|
||||||
dropDownListener []func(DropDownList, int, int)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDropDownList create new DropDownList object and return it
|
// NewDropDownList create new DropDownList object and return it
|
||||||
|
|
@ -44,167 +43,76 @@ func NewDropDownList(session Session, params Params) DropDownList {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDropDownList(session Session) View {
|
func newDropDownList(session Session) View {
|
||||||
return NewDropDownList(session, nil)
|
return new(dropDownListData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) init(session Session) {
|
func (list *dropDownListData) init(session Session) {
|
||||||
list.viewData.init(session)
|
list.viewData.init(session)
|
||||||
list.tag = "DropDownList"
|
list.tag = "DropDownList"
|
||||||
list.hasHtmlDisabled = true
|
list.hasHtmlDisabled = true
|
||||||
list.items = []string{}
|
list.normalize = normalizeDropDownListTag
|
||||||
list.disabledItems = []any{}
|
list.set = list.setFunc
|
||||||
list.itemSeparators = []any{}
|
list.changed = list.propertyChanged
|
||||||
list.dropDownListener = []func(DropDownList, int, int){}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) String() string {
|
|
||||||
return getViewString(list, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) Focusable() bool {
|
func (list *dropDownListData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) Remove(tag string) {
|
func normalizeDropDownListTag(tag PropertyName) PropertyName {
|
||||||
list.remove(strings.ToLower(tag))
|
tag = defaultNormalize(tag)
|
||||||
|
if tag == "separators" {
|
||||||
|
return ItemSeparators
|
||||||
|
}
|
||||||
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) remove(tag string) {
|
func (list *dropDownListData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case Items:
|
case Items:
|
||||||
if len(list.items) > 0 {
|
if items, ok := anyToStringArray(value, ""); ok {
|
||||||
list.items = []string{}
|
return setArrayPropertyValue(list, tag, items)
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
|
notCompatibleType(Items, value)
|
||||||
|
return nil
|
||||||
|
|
||||||
case DisabledItems:
|
case DisabledItems, ItemSeparators:
|
||||||
if len(list.disabledItems) > 0 {
|
if items, ok := parseIndicesArray(value); ok {
|
||||||
list.disabledItems = []any{}
|
return setArrayPropertyValue(list, tag, items)
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ItemSeparators, "separators":
|
|
||||||
if len(list.itemSeparators) > 0 {
|
|
||||||
list.itemSeparators = []any{}
|
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(ItemSeparators)
|
|
||||||
}
|
}
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
|
||||||
case DropDownEvent:
|
case DropDownEvent:
|
||||||
if len(list.dropDownListener) > 0 {
|
return setTwoArgEventListener[DropDownList, int](list, tag, value)
|
||||||
list.dropDownListener = []func(DropDownList, int, int){}
|
|
||||||
list.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Current:
|
case Current:
|
||||||
oldCurrent := GetCurrent(list)
|
list.setRaw("old-current", GetCurrent(list))
|
||||||
delete(list.properties, Current)
|
return setIntProperty(list, Current, value)
|
||||||
if oldCurrent != 0 {
|
}
|
||||||
if list.created {
|
|
||||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), 0)
|
return list.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
list.onSelectedItemChanged(0, oldCurrent)
|
|
||||||
|
func (list *dropDownListData) propertyChanged(tag PropertyName) {
|
||||||
|
switch tag {
|
||||||
|
case Items, DisabledItems, ItemSeparators:
|
||||||
|
updateInnerHTML(list.htmlID(), list.Session())
|
||||||
|
|
||||||
|
case Current:
|
||||||
|
current := GetCurrent(list)
|
||||||
|
list.Session().callFunc("selectDropDownListItem", list.htmlID(), current)
|
||||||
|
|
||||||
|
oldCurrent, _ := intProperty(list, "old-current", list.Session(), -1)
|
||||||
|
for _, listener := range GetDropDownListeners(list) {
|
||||||
|
listener(list, current, oldCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
list.viewData.remove(tag)
|
list.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) Set(tag string, value any) bool {
|
|
||||||
return list.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
list.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case Items:
|
|
||||||
return list.setItems(value)
|
|
||||||
|
|
||||||
case DisabledItems:
|
|
||||||
items, ok := list.parseIndicesArray(value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
list.disabledItems = items
|
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case ItemSeparators, "separators":
|
|
||||||
items, ok := list.parseIndicesArray(value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(ItemSeparators, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
list.itemSeparators = items
|
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(ItemSeparators)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case DropDownEvent:
|
|
||||||
listeners, ok := valueToEventWithOldListeners[DropDownList, int](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(DropDownList, int, int){}
|
|
||||||
}
|
|
||||||
list.dropDownListener = listeners
|
|
||||||
list.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case Current:
|
|
||||||
oldCurrent := GetCurrent(list)
|
|
||||||
if !list.setIntProperty(Current, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if current := GetCurrent(list); oldCurrent != current {
|
|
||||||
if list.created {
|
|
||||||
list.session.callFunc("selectDropDownListItem", list.htmlID(), current)
|
|
||||||
}
|
|
||||||
list.onSelectedItemChanged(current, oldCurrent)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.viewData.set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) setItems(value any) bool {
|
|
||||||
items, ok := anyToStringArray(value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(Items, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
list.items = items
|
|
||||||
if list.created {
|
|
||||||
updateInnerHTML(list.htmlID(), list.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
list.propertyChangedEvent(Items)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](array []T) []string {
|
func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](array []T) []string {
|
||||||
items := make([]string, len(array))
|
items := make([]string, len(array))
|
||||||
for i, val := range array {
|
for i, val := range array {
|
||||||
|
|
@ -213,150 +121,11 @@ func intArrayToStringArray[T int | uint | int8 | uint8 | int16 | uint16 | int32
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func anyToStringArray(value any) ([]string, bool) {
|
func parseIndicesArray(value any) ([]any, bool) {
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case int:
|
||||||
return []string{value}, true
|
return []any{value}, true
|
||||||
|
|
||||||
case []string:
|
|
||||||
return value, true
|
|
||||||
|
|
||||||
case []DataValue:
|
|
||||||
items := make([]string, 0, len(value))
|
|
||||||
for _, val := range value {
|
|
||||||
if !val.IsObject() {
|
|
||||||
items = append(items, val.Value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []fmt.Stringer:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, str := range value {
|
|
||||||
items[i] = str.String()
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []Color:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, str := range value {
|
|
||||||
items[i] = str.String()
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []SizeUnit:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, str := range value {
|
|
||||||
items[i] = str.String()
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []AngleUnit:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, str := range value {
|
|
||||||
items[i] = str.String()
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []float32:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, val := range value {
|
|
||||||
items[i] = fmt.Sprintf("%g", float64(val))
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []float64:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, val := range value {
|
|
||||||
items[i] = fmt.Sprintf("%g", val)
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []int:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []uint:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []int8:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []uint8:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []int16:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []uint16:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []int32:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []uint32:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []int64:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []uint64:
|
|
||||||
return intArrayToStringArray(value), true
|
|
||||||
|
|
||||||
case []bool:
|
|
||||||
items := make([]string, len(value))
|
|
||||||
for i, val := range value {
|
|
||||||
if val {
|
|
||||||
items[i] = "true"
|
|
||||||
} else {
|
|
||||||
items[i] = "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []any:
|
|
||||||
items := make([]string, 0, len(value))
|
|
||||||
for _, v := range value {
|
|
||||||
switch val := v.(type) {
|
|
||||||
case string:
|
|
||||||
items = append(items, val)
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
items = append(items, val.String())
|
|
||||||
|
|
||||||
case bool:
|
|
||||||
if val {
|
|
||||||
items = append(items, "true")
|
|
||||||
} else {
|
|
||||||
items = append(items, "false")
|
|
||||||
}
|
|
||||||
|
|
||||||
case float32:
|
|
||||||
items = append(items, fmt.Sprintf("%g", float64(val)))
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
items = append(items, fmt.Sprintf("%g", val))
|
|
||||||
|
|
||||||
case rune:
|
|
||||||
items = append(items, string(val))
|
|
||||||
|
|
||||||
default:
|
|
||||||
if n, ok := isInt(v); ok {
|
|
||||||
items = append(items, strconv.Itoa(n))
|
|
||||||
} else {
|
|
||||||
return []string{}, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) parseIndicesArray(value any) ([]any, bool) {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case []int:
|
case []int:
|
||||||
items := make([]any, len(value))
|
items := make([]any, len(value))
|
||||||
for i, n := range value {
|
for i, n := range value {
|
||||||
|
|
@ -365,108 +134,72 @@ func (list *dropDownListData) parseIndicesArray(value any) ([]any, bool) {
|
||||||
return items, true
|
return items, true
|
||||||
|
|
||||||
case []any:
|
case []any:
|
||||||
items := make([]any, len(value))
|
items := make([]any, 0, len(value))
|
||||||
for i, val := range value {
|
for _, val := range value {
|
||||||
if val == nil {
|
if val != nil {
|
||||||
return nil, false
|
switch val := val.(type) {
|
||||||
}
|
case string:
|
||||||
|
if isConstantName(val) {
|
||||||
switch val := val.(type) {
|
items = append(items, val)
|
||||||
case string:
|
} else if n, err := strconv.Atoi(val); err == nil {
|
||||||
if isConstantName(val) {
|
items = append(items, n)
|
||||||
items[i] = val
|
} else {
|
||||||
} else {
|
return nil, false
|
||||||
n, err := strconv.Atoi(val)
|
}
|
||||||
if err != nil {
|
|
||||||
|
default:
|
||||||
|
if n, ok := isInt(val); ok {
|
||||||
|
items = append(items, n)
|
||||||
|
} else {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
items[i] = n
|
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
if n, ok := isInt(val); ok {
|
}
|
||||||
items[i] = n
|
return items, true
|
||||||
|
|
||||||
|
case []string:
|
||||||
|
items := make([]any, 0, len(value))
|
||||||
|
for _, str := range value {
|
||||||
|
if str = strings.Trim(str, " \t"); str != "" {
|
||||||
|
if isConstantName(str) {
|
||||||
|
items = append(items, str)
|
||||||
|
} else if n, err := strconv.Atoi(str); err == nil {
|
||||||
|
items = append(items, n)
|
||||||
} else {
|
} else {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return items, true
|
return items, true
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
values := strings.Split(value, ",")
|
return parseIndicesArray(strings.Split(value, ","))
|
||||||
items := make([]any, len(values))
|
|
||||||
for i, str := range values {
|
|
||||||
str = strings.Trim(str, " ")
|
|
||||||
if str == "" {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if isConstantName(str) {
|
|
||||||
items[i] = str
|
|
||||||
} else {
|
|
||||||
n, err := strconv.Atoi(str)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
items[i] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items, true
|
|
||||||
|
|
||||||
case []DataValue:
|
case []DataValue:
|
||||||
items := make([]any, 0, len(value))
|
items := make([]string, 0, len(value))
|
||||||
for _, val := range value {
|
for _, val := range value {
|
||||||
if !val.IsObject() {
|
if !val.IsObject() {
|
||||||
items = append(items, val.Value())
|
items = append(items, val.Value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list.parseIndicesArray(items)
|
return parseIndicesArray(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) Get(tag string) any {
|
|
||||||
return list.get(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) get(tag string) any {
|
|
||||||
switch tag {
|
|
||||||
case Items:
|
|
||||||
return list.items
|
|
||||||
|
|
||||||
case DisabledItems:
|
|
||||||
return list.disabledItems
|
|
||||||
|
|
||||||
case ItemSeparators:
|
|
||||||
return list.itemSeparators
|
|
||||||
|
|
||||||
case Current:
|
|
||||||
result, _ := intProperty(list, Current, list.session, 0)
|
|
||||||
return result
|
|
||||||
|
|
||||||
case DropDownEvent:
|
|
||||||
return list.dropDownListener
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.viewData.get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) getItems() []string {
|
|
||||||
return list.items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) htmlTag() string {
|
func (list *dropDownListData) htmlTag() string {
|
||||||
return "select"
|
return "select"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
if list.items != nil {
|
if items := GetDropDownItems(list); len(items) > 0 {
|
||||||
current := GetCurrent(list)
|
current := GetCurrent(list)
|
||||||
notTranslate := GetNotTranslate(list)
|
notTranslate := GetNotTranslate(list)
|
||||||
disabledItems := GetDropDownDisabledItems(list)
|
disabledItems := GetDropDownDisabledItems(list)
|
||||||
separators := GetDropDownItemSeparators(list)
|
separators := GetDropDownItemSeparators(list)
|
||||||
for i, item := range list.items {
|
for i, item := range items {
|
||||||
disabled := false
|
disabled := false
|
||||||
for _, index := range disabledItems {
|
for _, index := range disabledItems {
|
||||||
if i == index {
|
if i == index {
|
||||||
|
|
@ -503,22 +236,21 @@ func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
|
buffer.WriteString(` size="1" onchange="dropDownListEvent(this, event)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *dropDownListData) onSelectedItemChanged(number, old int) {
|
func (list *dropDownListData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
for _, listener := range list.dropDownListener {
|
|
||||||
listener(list, number, old)
|
|
||||||
}
|
|
||||||
list.propertyChangedEvent(Current)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *dropDownListData) handleCommand(self View, command string, data DataObject) bool {
|
|
||||||
switch command {
|
switch command {
|
||||||
case "itemSelected":
|
case "itemSelected":
|
||||||
if text, ok := data.PropertyValue("number"); ok {
|
if text, ok := data.PropertyValue("number"); ok {
|
||||||
if number, err := strconv.Atoi(text); err == nil {
|
if number, err := strconv.Atoi(text); err == nil {
|
||||||
if GetCurrent(list) != number && number >= 0 && number < len(list.items) {
|
items := GetDropDownItems(list)
|
||||||
|
if GetCurrent(list) != number && number >= 0 && number < len(items) {
|
||||||
old := GetCurrent(list)
|
old := GetCurrent(list)
|
||||||
list.properties[Current] = number
|
list.properties[Current] = number
|
||||||
list.onSelectedItemChanged(number, old)
|
for _, listener := range GetDropDownListeners(list) {
|
||||||
|
listener(list, number, old)
|
||||||
|
}
|
||||||
|
if listener, ok := list.changeListener[Current]; ok {
|
||||||
|
listener(list, Current)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ErrorLog(err.Error())
|
ErrorLog(err.Error())
|
||||||
|
|
@ -534,24 +266,23 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data
|
||||||
// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int, int) {
|
||||||
return getEventWithOldListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
return getTwoArgEventListeners[DropDownList, int](view, subviewID, DropDownEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDropDownItems return the DropDownList items list.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDropDownItems(view View, subviewID ...string) []string {
|
func GetDropDownItems(view View, subviewID ...string) []string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if value := view.Get(Items); value != nil {
|
||||||
}
|
if items, ok := value.([]string); ok {
|
||||||
if view != nil {
|
return items
|
||||||
if list, ok := view.(DropDownList); ok {
|
}
|
||||||
return list.getItems()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIndicesArray(view View, tag string) []int {
|
func getIndicesArray(view View, tag PropertyName) []int {
|
||||||
if view != nil {
|
if view != nil {
|
||||||
if value := view.Get(tag); value != nil {
|
if value := view.Get(tag); value != nil {
|
||||||
if values, ok := value.([]any); ok {
|
if values, ok := value.([]any); ok {
|
||||||
|
|
@ -584,19 +315,13 @@ func getIndicesArray(view View, tag string) []int {
|
||||||
// GetDropDownDisabledItems return an array of disabled(non selectable) items indices of DropDownList.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDropDownDisabledItems(view View, subviewID ...string) []int {
|
func GetDropDownDisabledItems(view View, subviewID ...string) []int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return getIndicesArray(view, DisabledItems)
|
return getIndicesArray(view, DisabledItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDropDownItemSeparators return an array of indices of DropDownList items after which a separator should be added.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetDropDownItemSeparators(view View, subviewID ...string) []int {
|
func GetDropDownItemSeparators(view View, subviewID ...string) []int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return getIndicesArray(view, ItemSeparators)
|
return getIndicesArray(view, ItemSeparators)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
405
editView.go
405
editView.go
|
|
@ -9,62 +9,62 @@ import (
|
||||||
const (
|
const (
|
||||||
// EditTextChangedEvent is the constant for "edit-text-changed" property tag.
|
// EditTextChangedEvent is the constant for "edit-text-changed" property tag.
|
||||||
//
|
//
|
||||||
// Used by `EditView`.
|
// Used by EditView.
|
||||||
// Occur when edit view text has been changed.
|
// Occur when edit view text has been changed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(editView rui.EditView, newText, oldText string)`.
|
// func(editView rui.EditView, newText string, oldText string).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// editView - Interface of an edit view which generated this event,
|
// - editView - Interface of an edit view which generated this event,
|
||||||
// newText - New edit view text,
|
// - newText - New edit view text,
|
||||||
// oldText - Previous edit view text.
|
// - oldText - Previous edit view text.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(editView rui.EditView, newText string)`,
|
// - func(editView rui.EditView, newText string)
|
||||||
// `func(newText, oldText string)`,
|
// - func(newText string, oldText string)
|
||||||
// `func(newText string)`,
|
// - func(newText string)
|
||||||
// `func(editView rui.EditView)`,
|
// - func(editView rui.EditView)
|
||||||
// `func()`.
|
// - func()
|
||||||
EditTextChangedEvent = "edit-text-changed"
|
EditTextChangedEvent PropertyName = "edit-text-changed"
|
||||||
|
|
||||||
// EditViewType is the constant for "edit-view-type" property tag.
|
// EditViewType is the constant for "edit-view-type" property tag.
|
||||||
//
|
//
|
||||||
// Used by `EditView`.
|
// Used by EditView.
|
||||||
// Type of the text input. Default value is "text".
|
// Type of the text input. Default value is "text".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`SingleLineText`) or "text" - One-line text editor.
|
// - 0 (SingleLineText) or "text" - One-line text editor.
|
||||||
// `1`(`PasswordText`) or "password" - Password editor. The text is hidden by asterisks.
|
// - 1 (PasswordText) or "password" - Password editor. The text is hidden by asterisks.
|
||||||
// `2`(`EmailText`) or "email" - Single e-mail editor.
|
// - 2 (EmailText) or "email" - Single e-mail editor.
|
||||||
// `3`(`EmailsText`) or "emails" - Multiple e-mail editor.
|
// - 3 (EmailsText) or "emails" - Multiple e-mail editor.
|
||||||
// `4`(`URLText`) or "url" - Internet address input editor.
|
// - 4 (URLText) or "url" - Internet address input editor.
|
||||||
// `5`(`PhoneText`) or "phone" - Phone number editor.
|
// - 5 (PhoneText) or "phone" - Phone number editor.
|
||||||
// `6`(`MultiLineText`) or "multiline" - Multi-line text editor.
|
// - 6 (MultiLineText) or "multiline" - Multi-line text editor.
|
||||||
EditViewType = "edit-view-type"
|
EditViewType PropertyName = "edit-view-type"
|
||||||
|
|
||||||
// EditViewPattern is the constant for "edit-view-pattern" property tag.
|
// EditViewPattern is the constant for "edit-view-pattern" property tag.
|
||||||
//
|
//
|
||||||
// Used by `EditView`.
|
// Used by EditView.
|
||||||
// Regular expression to limit editing of a text.
|
// Regular expression to limit editing of a text.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
EditViewPattern = "edit-view-pattern"
|
EditViewPattern PropertyName = "edit-view-pattern"
|
||||||
|
|
||||||
// Spellcheck is the constant for "spellcheck" property tag.
|
// Spellcheck is the constant for "spellcheck" property tag.
|
||||||
//
|
//
|
||||||
// Used by `EditView`.
|
// Used by EditView.
|
||||||
// Enable or disable spell checker. Available in `SingleLineText` and `MultiLineText` types of edit view. Default value is
|
// Enable or disable spell checker. Available in SingleLineText and MultiLineText types of edit view. Default value is
|
||||||
// `false`.
|
// false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Enable spell checker for text.
|
// - true, 1, "true", "yes", "on", "1" - Enable spell checker for text.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Disable spell checker for text.
|
// - false, 0, "false", "no", "off", "0" - Disable spell checker for text.
|
||||||
Spellcheck = "spellcheck"
|
Spellcheck PropertyName = "spellcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants for the values of an [EditView] "edit-view-type" property
|
// Constants for the values of an [EditView] "edit-view-type" property
|
||||||
|
|
@ -97,12 +97,11 @@ type EditView interface {
|
||||||
|
|
||||||
// AppendText appends text to the current text of an EditView view
|
// AppendText appends text to the current text of an EditView view
|
||||||
AppendText(text string)
|
AppendText(text string)
|
||||||
|
textChanged(newText, oldText string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type editViewData struct {
|
type editViewData struct {
|
||||||
viewData
|
viewData
|
||||||
dataList
|
|
||||||
textChangeListeners []func(EditView, string, string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEditView create new EditView object and return it
|
// NewEditView create new EditView object and return it
|
||||||
|
|
@ -114,27 +113,24 @@ func NewEditView(session Session, params Params) EditView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEditView(session Session) View {
|
func newEditView(session Session) View {
|
||||||
return NewEditView(session, nil)
|
return new(editViewData) // NewEditView(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) init(session Session) {
|
func (edit *editViewData) init(session Session) {
|
||||||
edit.viewData.init(session)
|
edit.viewData.init(session)
|
||||||
edit.hasHtmlDisabled = true
|
edit.hasHtmlDisabled = true
|
||||||
edit.textChangeListeners = []func(EditView, string, string){}
|
|
||||||
edit.tag = "EditView"
|
edit.tag = "EditView"
|
||||||
edit.dataListInit()
|
edit.normalize = normalizeEditViewTag
|
||||||
}
|
edit.set = edit.setFunc
|
||||||
|
edit.changed = edit.propertyChanged
|
||||||
func (edit *editViewData) String() string {
|
|
||||||
return getViewString(edit, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) Focusable() bool {
|
func (edit *editViewData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) normalizeTag(tag string) string {
|
func normalizeEditViewTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case Type, "edit-type":
|
case Type, "edit-type":
|
||||||
return EditViewType
|
return EditViewType
|
||||||
|
|
@ -149,279 +145,107 @@ func (edit *editViewData) normalizeTag(tag string) string {
|
||||||
return EditWrap
|
return EditWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
return edit.normalizeDataListTag(tag)
|
return normalizeDataListTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) Remove(tag string) {
|
func (edit *editViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
edit.remove(edit.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edit *editViewData) remove(tag string) {
|
|
||||||
_, exists := edit.properties[tag]
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Hint:
|
|
||||||
if exists {
|
|
||||||
delete(edit.properties, Hint)
|
|
||||||
if edit.created {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), "placeholder")
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case MaxLength:
|
|
||||||
if exists {
|
|
||||||
delete(edit.properties, MaxLength)
|
|
||||||
if edit.created {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), "maxlength")
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ReadOnly, Spellcheck:
|
|
||||||
if exists {
|
|
||||||
delete(edit.properties, tag)
|
|
||||||
if edit.created {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), tag, false)
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditTextChangedEvent:
|
|
||||||
if len(edit.textChangeListeners) > 0 {
|
|
||||||
edit.textChangeListeners = []func(EditView, string, string){}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Text:
|
case Text:
|
||||||
if exists {
|
if text, ok := value.(string); ok {
|
||||||
oldText := GetText(edit)
|
old := ""
|
||||||
delete(edit.properties, tag)
|
if val := edit.getRaw(Text); val != nil {
|
||||||
if oldText != "" {
|
if txt, ok := val.(string); ok {
|
||||||
edit.textChanged("", oldText)
|
old = txt
|
||||||
if edit.created {
|
|
||||||
edit.session.callFunc("setInputValue", edit.htmlID(), "")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
edit.setRaw("old-text", old)
|
||||||
|
edit.setRaw(tag, text)
|
||||||
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
case EditViewPattern:
|
notCompatibleType(tag, value)
|
||||||
if exists {
|
return nil
|
||||||
oldText := GetEditViewPattern(edit)
|
|
||||||
delete(edit.properties, tag)
|
|
||||||
if oldText != "" {
|
|
||||||
if edit.created {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), Pattern)
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditViewType:
|
case Hint:
|
||||||
if exists {
|
if text, ok := value.(string); ok {
|
||||||
oldType := GetEditViewType(edit)
|
return setStringPropertyValue(edit, tag, strings.Trim(text, " \t\n"))
|
||||||
delete(edit.properties, tag)
|
|
||||||
if oldType != 0 {
|
|
||||||
if edit.created {
|
|
||||||
updateInnerHTML(edit.parentHTMLID(), edit.session)
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case EditWrap:
|
|
||||||
if exists {
|
|
||||||
oldWrap := IsEditViewWrap(edit)
|
|
||||||
delete(edit.properties, tag)
|
|
||||||
if GetEditViewType(edit) == MultiLineText {
|
|
||||||
if wrap := IsEditViewWrap(edit); wrap != oldWrap {
|
|
||||||
if edit.created {
|
|
||||||
if wrap {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "wrap", "soft")
|
|
||||||
} else {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "wrap", "off")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
|
||||||
case DataList:
|
case DataList:
|
||||||
if len(edit.dataList.dataList) > 0 {
|
setDataList(edit, value, "")
|
||||||
edit.setDataList(edit, []string{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
case EditTextChangedEvent:
|
||||||
edit.viewData.remove(tag)
|
return setTwoArgEventListener[EditView, string](edit, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return edit.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) Set(tag string, value any) bool {
|
func (edit *editViewData) propertyChanged(tag PropertyName) {
|
||||||
return edit.set(edit.normalizeTag(tag), value)
|
session := edit.Session()
|
||||||
}
|
|
||||||
|
|
||||||
func (edit *editViewData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
edit.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Text:
|
case Text:
|
||||||
if text, ok := value.(string); ok {
|
text := GetText(edit)
|
||||||
oldText := GetText(edit)
|
session.callFunc("setInputValue", edit.htmlID(), text)
|
||||||
edit.properties[Text] = text
|
|
||||||
if text = GetText(edit); oldText != text {
|
old := ""
|
||||||
edit.textChanged(text, oldText)
|
if val := edit.getRaw("old-text"); val != nil {
|
||||||
if edit.created {
|
if txt, ok := val.(string); ok {
|
||||||
edit.session.callFunc("setInputValue", edit.htmlID(), text)
|
old = txt
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
edit.textChanged(text, old)
|
||||||
|
|
||||||
case Hint:
|
case Hint:
|
||||||
if text, ok := value.(string); ok {
|
if text := GetHint(edit); text != "" {
|
||||||
oldText := GetHint(edit)
|
session.updateProperty(edit.htmlID(), "placeholder", text)
|
||||||
edit.properties[Hint] = text
|
} else {
|
||||||
if text = GetHint(edit); oldText != text {
|
session.removeProperty(edit.htmlID(), "placeholder")
|
||||||
if edit.created {
|
|
||||||
if text != "" {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "placeholder", text)
|
|
||||||
} else {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), "placeholder")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
|
||||||
case MaxLength:
|
case MaxLength:
|
||||||
oldMaxLength := GetMaxLength(edit)
|
if maxLength := GetMaxLength(edit); maxLength > 0 {
|
||||||
if edit.setIntProperty(MaxLength, value) {
|
session.updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength))
|
||||||
if maxLength := GetMaxLength(edit); maxLength != oldMaxLength {
|
} else {
|
||||||
if edit.created {
|
session.removeProperty(edit.htmlID(), "maxlength")
|
||||||
if maxLength > 0 {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength))
|
|
||||||
} else {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), "maxlength")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
|
||||||
case ReadOnly:
|
case ReadOnly:
|
||||||
if edit.setBoolProperty(ReadOnly, value) {
|
if IsReadOnly(edit) {
|
||||||
if edit.created {
|
session.updateProperty(edit.htmlID(), "readonly", "")
|
||||||
if IsReadOnly(edit) {
|
} else {
|
||||||
edit.session.updateProperty(edit.htmlID(), ReadOnly, "")
|
session.removeProperty(edit.htmlID(), "readonly")
|
||||||
} else {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), ReadOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
|
||||||
case Spellcheck:
|
case Spellcheck:
|
||||||
if edit.setBoolProperty(Spellcheck, value) {
|
session.updateProperty(edit.htmlID(), "spellcheck", IsSpellcheck(edit))
|
||||||
if edit.created {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit))
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
|
|
||||||
case EditViewPattern:
|
case EditViewPattern:
|
||||||
oldText := GetEditViewPattern(edit)
|
if text := GetEditViewPattern(edit); text != "" {
|
||||||
if text, ok := value.(string); ok {
|
session.updateProperty(edit.htmlID(), "pattern", text)
|
||||||
edit.properties[EditViewPattern] = text
|
} else {
|
||||||
if text = GetEditViewPattern(edit); oldText != text {
|
session.removeProperty(edit.htmlID(), "pattern")
|
||||||
if edit.created {
|
|
||||||
if text != "" {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), Pattern, text)
|
|
||||||
} else {
|
|
||||||
edit.session.removeProperty(edit.htmlID(), Pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
|
||||||
case EditViewType:
|
case EditViewType:
|
||||||
oldType := GetEditViewType(edit)
|
updateInnerHTML(edit.parentHTMLID(), session)
|
||||||
if edit.setEnumProperty(EditViewType, value, enumProperties[EditViewType].values) {
|
|
||||||
if GetEditViewType(edit) != oldType {
|
|
||||||
if edit.created {
|
|
||||||
updateInnerHTML(edit.parentHTMLID(), edit.session)
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
|
|
||||||
case EditWrap:
|
case EditWrap:
|
||||||
oldWrap := IsEditViewWrap(edit)
|
if wrap := IsEditViewWrap(edit); wrap {
|
||||||
if edit.setBoolProperty(EditWrap, value) {
|
session.updateProperty(edit.htmlID(), "wrap", "soft")
|
||||||
if GetEditViewType(edit) == MultiLineText {
|
} else {
|
||||||
if wrap := IsEditViewWrap(edit); wrap != oldWrap {
|
session.updateProperty(edit.htmlID(), "wrap", "off")
|
||||||
if edit.created {
|
|
||||||
if wrap {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "wrap", "soft")
|
|
||||||
} else {
|
|
||||||
edit.session.updateProperty(edit.htmlID(), "wrap", "off")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
|
||||||
case DataList:
|
case DataList:
|
||||||
return edit.setDataList(edit, value, edit.created)
|
updateInnerHTML(edit.htmlID(), session)
|
||||||
|
|
||||||
case EditTextChangedEvent:
|
default:
|
||||||
listeners, ok := valueToEventWithOldListeners[EditView, string](value)
|
edit.viewData.propertyChanged(tag)
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(EditView, string, string){}
|
|
||||||
}
|
|
||||||
edit.textChangeListeners = listeners
|
|
||||||
edit.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return edit.viewData.set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edit *editViewData) Get(tag string) any {
|
|
||||||
return edit.get(edit.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edit *editViewData) get(tag string) any {
|
|
||||||
switch tag {
|
|
||||||
case EditTextChangedEvent:
|
|
||||||
return edit.textChangeListeners
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return edit.dataList.dataList
|
|
||||||
}
|
|
||||||
return edit.viewData.get(tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) AppendText(text string) {
|
func (edit *editViewData) AppendText(text string) {
|
||||||
|
|
@ -432,21 +256,24 @@ func (edit *editViewData) AppendText(text string) {
|
||||||
textValue += text
|
textValue += text
|
||||||
edit.properties[Text] = textValue
|
edit.properties[Text] = textValue
|
||||||
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
|
edit.session.callFunc("appendToInnerHTML", edit.htmlID(), text)
|
||||||
|
edit.session.callFunc("appendToInputValue", edit.htmlID(), text)
|
||||||
edit.textChanged(textValue, oldText)
|
edit.textChanged(textValue, oldText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edit.set(Text, text)
|
edit.setRaw(Text, text)
|
||||||
} else {
|
} else {
|
||||||
edit.set(Text, GetText(edit)+text)
|
edit.setRaw(Text, GetText(edit)+text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) textChanged(newText, oldText string) {
|
func (edit *editViewData) textChanged(newText, oldText string) {
|
||||||
for _, listener := range edit.textChangeListeners {
|
for _, listener := range GetTextChangedListeners(edit) {
|
||||||
listener(edit, newText, oldText)
|
listener(edit, newText, oldText)
|
||||||
}
|
}
|
||||||
edit.propertyChangedEvent(Text)
|
if listener, ok := edit.changeListener[Text]; ok {
|
||||||
|
listener(edit, Text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) htmlTag() string {
|
func (edit *editViewData) htmlTag() string {
|
||||||
|
|
@ -462,7 +289,9 @@ func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
buffer.WriteString(text)
|
buffer.WriteString(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
edit.dataListHtmlSubviews(self, buffer)
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
||||||
|
return text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
|
func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
|
|
@ -547,16 +376,16 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edit.dataListHtmlProperties(edit, buffer)
|
dataListHtmlProperties(edit, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (edit *editViewData) handleCommand(self View, command string, data DataObject) bool {
|
func (edit *editViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
oldText := GetText(edit)
|
oldText := GetText(edit)
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
edit.properties[Text] = text
|
edit.setRaw(Text, text)
|
||||||
if text := GetText(edit); text != oldText {
|
if text != oldText {
|
||||||
edit.textChanged(text, oldText)
|
edit.textChanged(text, oldText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -569,10 +398,7 @@ func (edit *editViewData) handleCommand(self View, command string, data DataObje
|
||||||
// GetText returns a text of the EditView subview.
|
// GetText returns a text of the EditView subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a text of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a text of the first argument (view) is returned.
|
||||||
func GetText(view View, subviewID ...string) string {
|
func GetText(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value := view.getRaw(Text); value != nil {
|
if value := view.getRaw(Text); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
return text
|
return text
|
||||||
|
|
@ -585,9 +411,7 @@ func GetText(view View, subviewID ...string) string {
|
||||||
// GetHint returns a hint text of the subview.
|
// GetHint returns a hint text of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a text of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a text of the first argument (view) is returned.
|
||||||
func GetHint(view View, subviewID ...string) string {
|
func GetHint(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
text := ""
|
text := ""
|
||||||
|
|
@ -636,7 +460,7 @@ func IsSpellcheck(view View, subviewID ...string) bool {
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string, string) {
|
||||||
return getEventWithOldListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
return getTwoArgEventListeners[EditView, string](view, subviewID, EditTextChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEditViewType returns a value of the Type property of EditView.
|
// GetEditViewType returns a value of the Type property of EditView.
|
||||||
|
|
@ -648,10 +472,7 @@ func GetEditViewType(view View, subviewID ...string) int {
|
||||||
// GetEditViewPattern returns a value of the Pattern property of EditView.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetEditViewPattern(view View, subviewID ...string) string {
|
func GetEditViewPattern(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if pattern, ok := stringProperty(view, EditViewPattern, view.Session()); ok {
|
if pattern, ok := stringProperty(view, EditViewPattern, view.Session()); ok {
|
||||||
return pattern
|
return pattern
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,517 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
var eventJsFunc = map[PropertyName]struct{ jsEvent, jsFunc string }{
|
||||||
|
FocusEvent: {jsEvent: "onfocus", jsFunc: "focusEvent"},
|
||||||
|
LostFocusEvent: {jsEvent: "onblur", jsFunc: "blurEvent"},
|
||||||
|
KeyDownEvent: {jsEvent: "onkeydown", jsFunc: "keyDownEvent"},
|
||||||
|
KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"},
|
||||||
|
ClickEvent: {jsEvent: "onclick", jsFunc: "clickEvent"},
|
||||||
|
DoubleClickEvent: {jsEvent: "ondblclick", jsFunc: "doubleClickEvent"},
|
||||||
|
MouseDown: {jsEvent: "onmousedown", jsFunc: "mouseDownEvent"},
|
||||||
|
MouseUp: {jsEvent: "onmouseup", jsFunc: "mouseUpEvent"},
|
||||||
|
MouseMove: {jsEvent: "onmousemove", jsFunc: "mouseMoveEvent"},
|
||||||
|
MouseOut: {jsEvent: "onmouseout", jsFunc: "mouseOutEvent"},
|
||||||
|
MouseOver: {jsEvent: "onmouseover", jsFunc: "mouseOverEvent"},
|
||||||
|
ContextMenuEvent: {jsEvent: "oncontextmenu", jsFunc: "contextMenuEvent"},
|
||||||
|
PointerDown: {jsEvent: "onpointerdown", jsFunc: "pointerDownEvent"},
|
||||||
|
PointerUp: {jsEvent: "onpointerup", jsFunc: "pointerUpEvent"},
|
||||||
|
PointerMove: {jsEvent: "onpointermove", jsFunc: "pointerMoveEvent"},
|
||||||
|
PointerCancel: {jsEvent: "onpointercancel", jsFunc: "pointerCancelEvent"},
|
||||||
|
PointerOut: {jsEvent: "onpointerout", jsFunc: "pointerOutEvent"},
|
||||||
|
PointerOver: {jsEvent: "onpointerover", jsFunc: "pointerOverEvent"},
|
||||||
|
TouchStart: {jsEvent: "ontouchstart", jsFunc: "touchStartEvent"},
|
||||||
|
TouchEnd: {jsEvent: "ontouchend", jsFunc: "touchEndEvent"},
|
||||||
|
TouchMove: {jsEvent: "ontouchmove", jsFunc: "touchMoveEvent"},
|
||||||
|
TouchCancel: {jsEvent: "ontouchcancel", jsFunc: "touchCancelEvent"},
|
||||||
|
TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"},
|
||||||
|
TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"},
|
||||||
|
TransitionEndEvent: {jsEvent: "ontransitionend", jsFunc: "transitionEndEvent"},
|
||||||
|
TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"},
|
||||||
|
AnimationStartEvent: {jsEvent: "onanimationstart", jsFunc: "animationStartEvent"},
|
||||||
|
AnimationEndEvent: {jsEvent: "onanimationend", jsFunc: "animationEndEvent"},
|
||||||
|
AnimationIterationEvent: {jsEvent: "onanimationiteration", jsFunc: "animationIterationEvent"},
|
||||||
|
AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
buffer.WriteString(js.jsEvent)
|
||||||
|
buffer.WriteString(`="`)
|
||||||
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateEventListenerHtml(view View, tag PropertyName) {
|
||||||
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
|
value := view.getRaw(tag)
|
||||||
|
session := view.Session()
|
||||||
|
htmlID := view.htmlID()
|
||||||
|
if value == nil {
|
||||||
|
session.removeProperty(view.htmlID(), js.jsEvent)
|
||||||
|
} else {
|
||||||
|
session.updateProperty(htmlID, js.jsEvent, js.jsFunc+"(this, event)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
152
filePicker.go
152
filePicker.go
|
|
@ -11,47 +11,47 @@ import (
|
||||||
const (
|
const (
|
||||||
// FileSelectedEvent is the constant for "file-selected-event" property tag.
|
// FileSelectedEvent is the constant for "file-selected-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `FilePicker`.
|
// Used by FilePicker.
|
||||||
// Fired when user selects file(s).
|
// Fired when user selects file(s).
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(picker rui.FilePicker, files []rui.FileInfo)`.
|
// func(picker rui.FilePicker, files []rui.FileInfo).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// picker - Interface of a file picker which generated this event,
|
// picker - Interface of a file picker which generated this event,
|
||||||
// files - Array of description of selected files.
|
// files - Array of description of selected files.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(picker rui.FilePicker)`,
|
// func(picker rui.FilePicker)
|
||||||
// `func(files []rui.FileInfo)`,
|
// func(files []rui.FileInfo)
|
||||||
// `func()`.
|
// func()
|
||||||
FileSelectedEvent = "file-selected-event"
|
FileSelectedEvent PropertyName = "file-selected-event"
|
||||||
|
|
||||||
// Accept is the constant for "accept" property tag.
|
// Accept is the constant for "accept" property tag.
|
||||||
//
|
//
|
||||||
// Used by `FilePicker`.
|
// Used by FilePicker.
|
||||||
// Set the list of allowed file extensions or MIME types.
|
// Set the list of allowed file extensions or MIME types.
|
||||||
//
|
//
|
||||||
// Supported types: `string`, `[]string`.
|
// Supported types: string, []string.
|
||||||
//
|
//
|
||||||
// Internal type is `string`, other types converted to it during assignment.
|
// Internal type is string, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - may contain single value of multiple separated by comma(`,`).
|
// - string - may contain single value of multiple separated by comma(,).
|
||||||
// `[]string` - an array of acceptable file extensions or MIME types.
|
// - []string - an array of acceptable file extensions or MIME types.
|
||||||
Accept = "accept"
|
Accept PropertyName = "accept"
|
||||||
|
|
||||||
// Multiple is the constant for "multiple" property tag.
|
// Multiple is the constant for "multiple" property tag.
|
||||||
//
|
//
|
||||||
// Used by `FilePicker`.
|
// Used by FilePicker.
|
||||||
// Controls whether multiple files can be selected.
|
// Controls whether multiple files can be selected.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Several files can be selected.
|
// - true, 1, "true", "yes", "on", "1" - Several files can be selected.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Only one file can be selected.
|
// - false, 0, "false", "no", "off", "0" - Only one file can be selected.
|
||||||
Multiple = "multiple"
|
Multiple PropertyName = "multiple"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileInfo describes a file which selected in the FilePicker view
|
// FileInfo describes a file which selected in the FilePicker view
|
||||||
|
|
@ -82,9 +82,8 @@ type FilePicker interface {
|
||||||
|
|
||||||
type filePickerData struct {
|
type filePickerData struct {
|
||||||
viewData
|
viewData
|
||||||
files []FileInfo
|
files []FileInfo
|
||||||
fileSelectedListeners []func(FilePicker, []FileInfo)
|
loader map[int]func(FileInfo, []byte)
|
||||||
loader map[int]func(FileInfo, []byte)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (file *FileInfo) initBy(node DataValue) {
|
func (file *FileInfo) initBy(node DataValue) {
|
||||||
|
|
@ -115,7 +114,7 @@ func NewFilePicker(session Session, params Params) FilePicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFilePicker(session Session) View {
|
func newFilePicker(session Session) View {
|
||||||
return NewFilePicker(session, nil)
|
return new(filePickerData) // NewFilePicker(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) init(session Session) {
|
func (picker *filePickerData) init(session Session) {
|
||||||
|
|
@ -124,11 +123,9 @@ func (picker *filePickerData) init(session Session) {
|
||||||
picker.hasHtmlDisabled = true
|
picker.hasHtmlDisabled = true
|
||||||
picker.files = []FileInfo{}
|
picker.files = []FileInfo{}
|
||||||
picker.loader = map[int]func(FileInfo, []byte){}
|
picker.loader = map[int]func(FileInfo, []byte){}
|
||||||
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
picker.set = picker.setFunc
|
||||||
}
|
picker.changed = picker.propertyChanged
|
||||||
|
|
||||||
func (picker *filePickerData) String() string {
|
|
||||||
return getViewString(picker, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) Focusable() bool {
|
func (picker *filePickerData) Focusable() bool {
|
||||||
|
|
@ -153,62 +150,16 @@ func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) Remove(tag string) {
|
func (picker *filePickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
picker.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *filePickerData) remove(tag string) {
|
|
||||||
switch tag {
|
|
||||||
case FileSelectedEvent:
|
|
||||||
if len(picker.fileSelectedListeners) > 0 {
|
|
||||||
picker.fileSelectedListeners = []func(FilePicker, []FileInfo){}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Accept:
|
|
||||||
delete(picker.properties, tag)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), "accept")
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
|
|
||||||
default:
|
|
||||||
picker.viewData.remove(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *filePickerData) Set(tag string, value any) bool {
|
|
||||||
return picker.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *filePickerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
picker.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case FileSelectedEvent:
|
case FileSelectedEvent:
|
||||||
listeners, ok := valueToEventListeners[FilePicker, []FileInfo](value)
|
return setOneArgEventListener[FilePicker, []FileInfo](picker, tag, value)
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(FilePicker, []FileInfo){}
|
|
||||||
}
|
|
||||||
picker.fileSelectedListeners = listeners
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case Accept:
|
case Accept:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
value = strings.Trim(value, " \t\n")
|
return setStringPropertyValue(picker, Accept, strings.Trim(value, " \t\n"))
|
||||||
if value == "" {
|
|
||||||
picker.remove(Accept)
|
|
||||||
} else {
|
|
||||||
picker.properties[Accept] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
case []string:
|
case []string:
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
|
|
@ -222,29 +173,27 @@ func (picker *filePickerData) set(tag string, value any) bool {
|
||||||
buffer.WriteString(val)
|
buffer.WriteString(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buffer.Len() == 0 {
|
return setStringPropertyValue(picker, Accept, buffer.String())
|
||||||
picker.remove(Accept)
|
|
||||||
} else {
|
|
||||||
picker.properties[Accept] = buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if picker.created {
|
return picker.viewData.setFunc(tag, value)
|
||||||
if css := picker.acceptCSS(); css != "" {
|
}
|
||||||
picker.session.updateProperty(picker.htmlID(), "accept", css)
|
|
||||||
} else {
|
func (picker *filePickerData) propertyChanged(tag PropertyName) {
|
||||||
picker.session.removeProperty(picker.htmlID(), "accept")
|
switch tag {
|
||||||
}
|
case Accept:
|
||||||
|
session := picker.Session()
|
||||||
|
if css := acceptPropertyCSS(picker); css != "" {
|
||||||
|
session.updateProperty(picker.htmlID(), "accept", css)
|
||||||
|
} else {
|
||||||
|
session.removeProperty(picker.htmlID(), "accept")
|
||||||
}
|
}
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return picker.viewData.set(tag, value)
|
picker.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,10 +201,10 @@ func (picker *filePickerData) htmlTag() string {
|
||||||
return "input"
|
return "input"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) acceptCSS() string {
|
func acceptPropertyCSS(view View) string {
|
||||||
accept, ok := stringProperty(picker, Accept, picker.Session())
|
accept, ok := stringProperty(view, Accept, view.Session())
|
||||||
if !ok {
|
if !ok {
|
||||||
if value := valueFromStyle(picker, Accept); value != nil {
|
if value := valueFromStyle(view, Accept); value != nil {
|
||||||
accept, ok = value.(string)
|
accept, ok = value.(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +231,7 @@ func (picker *filePickerData) acceptCSS() string {
|
||||||
func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
picker.viewData.htmlProperties(self, buffer)
|
picker.viewData.htmlProperties(self, buffer)
|
||||||
|
|
||||||
if accept := picker.acceptCSS(); accept != "" {
|
if accept := acceptPropertyCSS(picker); accept != "" {
|
||||||
buffer.WriteString(` accept="`)
|
buffer.WriteString(` accept="`)
|
||||||
buffer.WriteString(accept)
|
buffer.WriteString(accept)
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
|
|
@ -299,7 +248,7 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *filePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "fileSelected":
|
case "fileSelected":
|
||||||
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
|
if node := data.PropertyByTag("files"); node != nil && node.Type() == ArrayNode {
|
||||||
|
|
@ -312,7 +261,7 @@ func (picker *filePickerData) handleCommand(self View, command string, data Data
|
||||||
}
|
}
|
||||||
picker.files = files
|
picker.files = files
|
||||||
|
|
||||||
for _, listener := range picker.fileSelectedListeners {
|
for _, listener := range GetFileSelectedListeners(picker) {
|
||||||
listener(picker, files)
|
listener(picker, files)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -396,10 +345,7 @@ func IsMultipleFilePicker(view View, subviewID ...string) bool {
|
||||||
// GetFilePickerAccept returns sets the list of allowed file extensions or MIME types.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetFilePickerAccept(view View, subviewID ...string) []string {
|
func GetFilePickerAccept(view View, subviewID ...string) []string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
accept, ok := stringProperty(view, Accept, view.Session())
|
accept, ok := stringProperty(view, Accept, view.Session())
|
||||||
if !ok {
|
if !ok {
|
||||||
if value := valueFromStyle(view, Accept); value != nil {
|
if value := valueFromStyle(view, Accept); value != nil {
|
||||||
|
|
@ -421,5 +367,5 @@ func GetFilePickerAccept(view View, subviewID ...string) []string {
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetFileSelectedListeners(view View, subviewID ...string) []func(FilePicker, []FileInfo) {
|
||||||
return getEventListeners[FilePicker, []FileInfo](view, subviewID, FileSelectedEvent)
|
return getOneArgEventListeners[FilePicker, []FileInfo](view, subviewID, FileSelectedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,357 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants for [FilterProperty] specific properties and events
|
||||||
|
const (
|
||||||
|
// Blur is the constant for "blur" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Applies a Gaussian blur. The value of radius defines the value of the standard deviation to the Gaussian function, or
|
||||||
|
// how many pixels on the screen blend into each other, so a larger value will create more blur. The lacuna value for
|
||||||
|
// interpolation is 0. The parameter is specified as a length in pixels.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Blur PropertyName = "blur"
|
||||||
|
|
||||||
|
// Brightness is the constant for "brightness" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Applies a linear multiplier to input image, making it appear more or less bright. A value of 0% will create an image
|
||||||
|
// that is completely black. A value of 100% leaves the input unchanged. Other values are linear multipliers on the
|
||||||
|
// effect. Values of an amount over 100% are allowed, providing brighter results.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Brightness PropertyName = "brightness"
|
||||||
|
|
||||||
|
// Contrast is the constant for "contrast" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Adjusts the contrast of the input. A value of 0% will create an image that is completely black. A value of 100% leaves
|
||||||
|
// the input unchanged. Values of amount over 100% are allowed, providing results with less contrast.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Contrast PropertyName = "contrast"
|
||||||
|
|
||||||
|
// DropShadow is the constant for "drop-shadow" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Applies a drop shadow effect to the input image. A drop shadow is effectively a blurred, offset version of the input
|
||||||
|
// image's alpha mask drawn in a particular color, composited below the image. Shadow parameters are set using the
|
||||||
|
// ShadowProperty interface.
|
||||||
|
//
|
||||||
|
// Supported types: []ShadowProperty, ShadowProperty, string.
|
||||||
|
//
|
||||||
|
// Internal type is []ShadowProperty, other types converted to it during assignment.
|
||||||
|
// See ShadowProperty description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - []ShadowProperty - stored as is, no conversion performed.
|
||||||
|
// - ShadowProperty - converted to []ShadowProperty.
|
||||||
|
// - string - string representation of ShadowProperty. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}".
|
||||||
|
DropShadow PropertyName = "drop-shadow"
|
||||||
|
|
||||||
|
// Grayscale is the constant for "grayscale" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Converts the input image to grayscale. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
||||||
|
// is completely grayscale. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
||||||
|
// the effect.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Grayscale PropertyName = "grayscale"
|
||||||
|
|
||||||
|
// HueRotate is the constant for "hue-rotate" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Applies a hue rotation on the input image. The value of ‘angle’ defines the number of degrees around the color circle
|
||||||
|
// the input samples will be adjusted. A value of 0deg leaves the input unchanged. If the ‘angle’ parameter is missing, a
|
||||||
|
// value of 0deg is used. Though there is no maximum value, the effect of values above 360deg wraps around.
|
||||||
|
//
|
||||||
|
// Supported types: AngleUnit, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is AngleUnit, other types will be converted to it during assignment.
|
||||||
|
// See AngleUnit description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - AngleUnit - stored as is, no conversion performed.
|
||||||
|
// - string - must contain string representation of AngleUnit. If numeric value will be provided without any suffix then AngleUnit with value and Radian value type will be created.
|
||||||
|
// - float - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
// - int - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
HueRotate PropertyName = "hue-rotate"
|
||||||
|
|
||||||
|
// Invert is the constant for "invert" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Inverts the samples in the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
||||||
|
// is completely inverted. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
||||||
|
// the effect.
|
||||||
|
//
|
||||||
|
// Supported types: float64, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Invert PropertyName = "invert"
|
||||||
|
|
||||||
|
// Saturate is the constant for "saturate" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Saturates the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 0% is completely
|
||||||
|
// un-saturated. A value of 100% leaves the input unchanged. Other values are linear multipliers on the effect. Values of
|
||||||
|
// amount over 100% are allowed, providing super-saturated results.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Saturate PropertyName = "saturate"
|
||||||
|
|
||||||
|
// Sepia is the constant for "sepia" property tag.
|
||||||
|
//
|
||||||
|
// Used by FilterProperty.
|
||||||
|
// Converts the input image to sepia. The value of ‘amount’ defines the proportion of the conversion. A value of 100% is
|
||||||
|
// completely sepia. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on the
|
||||||
|
// effect.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
Sepia PropertyName = "sepia"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FilterProperty defines an applied to a View a graphical effects like blur or color shift.
|
||||||
|
// Allowable properties are Blur, Brightness, Contrast, DropShadow, Grayscale, HueRotate, Invert, Opacity, Saturate, and Sepia
|
||||||
|
type FilterProperty interface {
|
||||||
|
Properties
|
||||||
|
fmt.Stringer
|
||||||
|
stringWriter
|
||||||
|
cssStyle(session Session) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilterProperty creates the new FilterProperty
|
||||||
|
func NewFilterProperty(params Params) FilterProperty {
|
||||||
|
if len(params) > 0 {
|
||||||
|
filter := new(filterData)
|
||||||
|
filter.init()
|
||||||
|
for tag, value := range params {
|
||||||
|
if !filter.Set(tag, value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFilterProperty(obj DataObject) FilterProperty {
|
||||||
|
filter := new(filterData)
|
||||||
|
filter.init()
|
||||||
|
for i := 0; i < obj.PropertyCount(); i++ {
|
||||||
|
if node := obj.Property(i); node != nil {
|
||||||
|
tag := node.Tag()
|
||||||
|
switch node.Type() {
|
||||||
|
case TextNode:
|
||||||
|
filter.Set(PropertyName(tag), node.Text())
|
||||||
|
|
||||||
|
case ObjectNode:
|
||||||
|
if tag == string(HueRotate) {
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
ErrorLog(`Invalid value of "` + tag + `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
ErrorLog(`Invalid value of "` + tag + `"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filter.properties) > 0 {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
ErrorLog("Empty view filter")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterData) init() {
|
||||||
|
filter.dataProperty.init()
|
||||||
|
filter.set = filterDataSet
|
||||||
|
filter.supportedProperties = []PropertyName{Blur, Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia, HueRotate, DropShadow}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterDataSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case Blur, Brightness, Contrast, Saturate:
|
||||||
|
return setFloatProperty(properties, tag, value, 0, 10000)
|
||||||
|
|
||||||
|
case Grayscale, Invert, Opacity, Sepia:
|
||||||
|
return setFloatProperty(properties, tag, value, 0, 100)
|
||||||
|
|
||||||
|
case HueRotate:
|
||||||
|
return setAngleProperty(properties, tag, value)
|
||||||
|
|
||||||
|
case DropShadow:
|
||||||
|
if setShadowProperty(properties, tag, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"%s" property is not supported by the view filter`, tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterData) String() string {
|
||||||
|
return runStringWriter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
buffer.WriteString("filter { ")
|
||||||
|
comma := false
|
||||||
|
tags := filter.AllTags()
|
||||||
|
for _, tag := range tags {
|
||||||
|
if value, ok := filter.properties[tag]; ok {
|
||||||
|
if comma {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
|
comma = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString(" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filter *filterData) cssStyle(session Session) string {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
if value, ok := floatTextProperty(filter, Blur, session, 0); ok {
|
||||||
|
buffer.WriteString(string(Blur))
|
||||||
|
buffer.WriteRune('(')
|
||||||
|
buffer.WriteString(value)
|
||||||
|
buffer.WriteString("px)")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range []PropertyName{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} {
|
||||||
|
if value, ok := floatTextProperty(filter, tag, session, 0); ok {
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteRune('(')
|
||||||
|
buffer.WriteString(value)
|
||||||
|
buffer.WriteString("%)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if value, ok := angleProperty(filter, HueRotate, session); ok {
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(HueRotate))
|
||||||
|
buffer.WriteRune('(')
|
||||||
|
buffer.WriteString(value.cssString())
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
}
|
||||||
|
|
||||||
|
var lead string
|
||||||
|
if buffer.Len() > 0 {
|
||||||
|
lead = " drop-shadow("
|
||||||
|
} else {
|
||||||
|
lead = "drop-shadow("
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, shadow := range getShadows(filter, DropShadow) {
|
||||||
|
if shadow.cssTextStyle(buffer, session, lead) {
|
||||||
|
buffer.WriteRune(')')
|
||||||
|
lead = " drop-shadow("
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFilterProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case FilterProperty:
|
||||||
|
properties.setRaw(tag, value)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if obj := NewDataObject(value); obj == nil {
|
||||||
|
if filter := newFilterProperty(obj); filter != nil {
|
||||||
|
properties.setRaw(tag, filter)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataObject:
|
||||||
|
if filter := newFilterProperty(value); filter != nil {
|
||||||
|
properties.setRaw(tag, filter)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
case DataValue:
|
||||||
|
if value.IsObject() {
|
||||||
|
if filter := newFilterProperty(value.Object()); filter != nil {
|
||||||
|
properties.setRaw(tag, filter)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilter returns a View graphical effects like blur or color shift.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||||||
|
func GetFilter(view View, subviewID ...string) FilterProperty {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
if value := view.getRaw(Filter); value != nil {
|
||||||
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value := valueFromStyle(view, Filter); value != nil {
|
||||||
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackdropFilter returns the area behind a View graphical effects like blur or color shift.
|
||||||
|
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||||||
|
func GetBackdropFilter(view View, subviewID ...string) FilterProperty {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
if value := view.getRaw(BackdropFilter); value != nil {
|
||||||
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value := valueFromStyle(view, BackdropFilter); value != nil {
|
||||||
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
|
return filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
152
focusEvents.go
152
focusEvents.go
|
|
@ -6,162 +6,44 @@ import "strings"
|
||||||
const (
|
const (
|
||||||
// FocusEvent is the constant for "focus-event" property tag.
|
// FocusEvent is the constant for "focus-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Occur when the view takes input focus.
|
// Occur when the view takes input focus.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(View)`.
|
// func(rui.View).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event.
|
// view - Interface of a view which generated this event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func()`.
|
// func().
|
||||||
FocusEvent = "focus-event"
|
FocusEvent PropertyName = "focus-event"
|
||||||
|
|
||||||
// LostFocusEvent is the constant for "lost-focus-event" property tag.
|
// LostFocusEvent is the constant for "lost-focus-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Occur when the View lost input focus.
|
// Occur when the View lost input focus.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View)`.
|
// func(view rui.View).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event.
|
// view - Interface of a view which generated this event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func()`.
|
// func()
|
||||||
LostFocusEvent = "lost-focus-event"
|
LostFocusEvent PropertyName = "lost-focus-event"
|
||||||
)
|
)
|
||||||
|
|
||||||
func valueToNoParamListeners[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
|
|
||||||
}
|
|
||||||
|
|
||||||
var focusEvents = map[string]struct{ jsEvent, jsFunc string }{
|
|
||||||
FocusEvent: {jsEvent: "onfocus", jsFunc: "focusEvent"},
|
|
||||||
LostFocusEvent: {jsEvent: "onblur", jsFunc: "blurEvent"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) setFocusListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToNoParamListeners[View](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removeFocusListener(tag)
|
|
||||||
} else if js, ok := focusEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removeFocusListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
|
||||||
if view.created {
|
|
||||||
if js, ok := focusEvents[tag]; ok {
|
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFocusListeners(view View, subviewID []string, tag string) []func(View) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(tag); value != nil {
|
|
||||||
if result, ok := value.([]func(View)); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []func(View){}
|
|
||||||
}
|
|
||||||
|
|
||||||
func focusEventsHtml(view View, buffer *strings.Builder) {
|
func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||||
if view.Focusable() {
|
if view.Focusable() {
|
||||||
for _, js := range focusEvents {
|
for _, tag := range []PropertyName{FocusEvent, LostFocusEvent} {
|
||||||
buffer.WriteString(js.jsEvent)
|
if js, ok := eventJsFunc[tag]; ok {
|
||||||
buffer.WriteString(`="`)
|
buffer.WriteString(js.jsEvent)
|
||||||
buffer.WriteString(js.jsFunc)
|
buffer.WriteString(`="`)
|
||||||
buffer.WriteString(`(this, event)" `)
|
buffer.WriteString(js.jsFunc)
|
||||||
|
buffer.WriteString(`(this, event)" `)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,11 +51,11 @@ func focusEventsHtml(view View, buffer *strings.Builder) {
|
||||||
// GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned
|
// GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) 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) {
|
func GetFocusListeners(view View, subviewID ...string) []func(View) {
|
||||||
return getFocusListeners(view, subviewID, FocusEvent)
|
return getNoArgEventListeners[View](view, subviewID, FocusEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned
|
// GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) 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) {
|
func GetLostFocusListeners(view View, subviewID ...string) []func(View) {
|
||||||
return getFocusListeners(view, subviewID, LostFocusEvent)
|
return getNoArgEventListeners[View](view, subviewID, LostFocusEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
387
gridLayout.go
387
gridLayout.go
|
|
@ -9,71 +9,71 @@ import (
|
||||||
const (
|
const (
|
||||||
// CellVerticalAlign is the constant for "cell-vertical-align" property tag.
|
// CellVerticalAlign is the constant for "cell-vertical-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `GridLayout`, `SvgImageView`.
|
// Used by GridLayout, SvgImageView.
|
||||||
//
|
//
|
||||||
// Usage in `GridLayout`:
|
// Usage in GridLayout:
|
||||||
// Sets the default vertical alignment of `GridLayout` children within the cell they are occupying.
|
// Sets the default vertical alignment of GridLayout children within the cell they are occupying.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`TopAlign`) or "top" - Top alignment.
|
// - 0 (TopAlign) or "top" - Top alignment.
|
||||||
// `1`(`BottomAlign`) or "bottom" - Bottom alignment.
|
// - 1 (BottomAlign) or "bottom" - Bottom alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
// `3`(`StretchAlign`) or "stretch" - Full height stretch.
|
// - 3 (StretchAlign) or "stretch" - Full height stretch.
|
||||||
//
|
//
|
||||||
// Usage in `SvgImageView`:
|
// Usage in SvgImageView:
|
||||||
// Same as "vertical-align".
|
// Same as "vertical-align".
|
||||||
CellVerticalAlign = "cell-vertical-align"
|
CellVerticalAlign PropertyName = "cell-vertical-align"
|
||||||
|
|
||||||
// CellHorizontalAlign is the constant for "cell-horizontal-align" property tag.
|
// CellHorizontalAlign is the constant for "cell-horizontal-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `GridLayout`, `SvgImageView`.
|
// Used by GridLayout, SvgImageView.
|
||||||
//
|
//
|
||||||
// Usage in `GridLayout`:
|
// Usage in GridLayout:
|
||||||
// Sets the default horizontal alignment of `GridLayout` children within the occupied cell.
|
// Sets the default horizontal alignment of GridLayout children within the occupied cell.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`LeftAlign`) or "left" - Left alignment.
|
// - 0 (LeftAlign) or "left" - Left alignment.
|
||||||
// `1`(`RightAlign`) or "right" - Right alignment.
|
// - 1 (RightAlign) or "right" - Right alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
// `3`(`StretchAlign`) or "stretch" - Full width stretch.
|
// - 3 (StretchAlign) or "stretch" - Full width stretch.
|
||||||
//
|
//
|
||||||
// Usage in `SvgImageView`:
|
// Usage in SvgImageView:
|
||||||
// Same as "horizontal-align".
|
// Same as "horizontal-align".
|
||||||
CellHorizontalAlign = "cell-horizontal-align"
|
CellHorizontalAlign PropertyName = "cell-horizontal-align"
|
||||||
|
|
||||||
// CellVerticalSelfAlign is the constant for "cell-vertical-self-align" property tag.
|
// CellVerticalSelfAlign is the constant for "cell-vertical-self-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `GridLayout`.
|
// Used by GridLayout.
|
||||||
// Sets the vertical alignment of `GridLayout` children within the cell they are occupying. The property is set for the
|
// Sets the vertical alignment of GridLayout children within the cell they are occupying. The property is set for the
|
||||||
// child view of `GridLayout`.
|
// child view of GridLayout.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`TopAlign`) or "top" - Top alignment.
|
// - 0 (TopAlign) or "top" - Top alignment.
|
||||||
// `1`(`BottomAlign`) or "bottom" - Bottom alignment.
|
// - 1 (BottomAlign) or "bottom" - Bottom alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
// `3`(`StretchAlign`) or "stretch" - Full height stretch.
|
// - 3 (StretchAlign) or "stretch" - Full height stretch.
|
||||||
CellVerticalSelfAlign = "cell-vertical-self-align"
|
CellVerticalSelfAlign PropertyName = "cell-vertical-self-align"
|
||||||
|
|
||||||
// CellHorizontalSelfAlign is the constant for "cell-horizontal-self-align" property tag.
|
// CellHorizontalSelfAlign is the constant for "cell-horizontal-self-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `GridLayout`.
|
// Used by GridLayout.
|
||||||
// Sets the horizontal alignment of `GridLayout` children within the occupied cell. The property is set for the child view
|
// Sets the horizontal alignment of GridLayout children within the occupied cell. The property is set for the child view
|
||||||
// of `GridLayout`.
|
// of GridLayout.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`LeftAlign`) or "left" - Left alignment.
|
// - 0 (LeftAlign) or "left" - Left alignment.
|
||||||
// `1`(`RightAlign`) or "right" - Right alignment.
|
// - 1 (RightAlign) or "right" - Right alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
// `3`(`StretchAlign`) or "stretch" - Full width stretch.
|
// - 3 (StretchAlign) or "stretch" - Full width stretch.
|
||||||
CellHorizontalSelfAlign = "cell-horizontal-self-align"
|
CellHorizontalSelfAlign PropertyName = "cell-horizontal-self-align"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GridAdapter is an interface to define [GridLayout] content. [GridLayout] will query interface functions to populate
|
// GridAdapter is an interface to define [GridLayout] content. [GridLayout] will query interface functions to populate
|
||||||
|
|
@ -126,7 +126,8 @@ func NewGridLayout(session Session, params Params) GridLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGridLayout(session Session) View {
|
func newGridLayout(session Session) View {
|
||||||
return NewGridLayout(session, nil)
|
//return NewGridLayout(session, nil)
|
||||||
|
return new(gridLayoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of GridLayout by default values
|
// Init initialize fields of GridLayout by default values
|
||||||
|
|
@ -135,13 +136,14 @@ func (gridLayout *gridLayoutData) init(session Session) {
|
||||||
gridLayout.tag = "GridLayout"
|
gridLayout.tag = "GridLayout"
|
||||||
gridLayout.systemClass = "ruiGridLayout"
|
gridLayout.systemClass = "ruiGridLayout"
|
||||||
gridLayout.adapter = nil
|
gridLayout.adapter = nil
|
||||||
|
gridLayout.normalize = normalizeGridLayoutTag
|
||||||
|
gridLayout.get = gridLayout.getFunc
|
||||||
|
gridLayout.set = gridLayout.setFunc
|
||||||
|
gridLayout.remove = gridLayout.removeFunc
|
||||||
|
gridLayout.changed = gridLayout.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) String() string {
|
func setGridCellSize(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
return getViewString(gridLayout, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setGridCellSize(tag string, value any) bool {
|
|
||||||
setValues := func(values []string) bool {
|
setValues := func(values []string) bool {
|
||||||
count := len(values)
|
count := len(values)
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
|
|
@ -159,11 +161,11 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style.properties[tag] = sizes
|
properties.setRaw(tag, sizes)
|
||||||
} else if isConstantName(values[0]) {
|
} else if isConstantName(values[0]) {
|
||||||
style.properties[tag] = values[0]
|
properties.setRaw(tag, values[0])
|
||||||
} else if size, err := stringToSizeUnit(values[0]); err == nil {
|
} else if size, err := stringToSizeUnit(values[0]); err == nil {
|
||||||
style.properties[tag] = size
|
properties.setRaw(tag, size)
|
||||||
} else {
|
} else {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return false
|
||||||
|
|
@ -175,41 +177,41 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
|
||||||
case CellWidth, CellHeight:
|
case CellWidth, CellHeight:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case SizeUnit, []SizeUnit:
|
case SizeUnit, []SizeUnit:
|
||||||
style.properties[tag] = value
|
properties.setRaw(tag, value)
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if !setValues(strings.Split(value, ",")) {
|
if !setValues(strings.Split(value, ",")) {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case []string:
|
case []string:
|
||||||
if !setValues(value) {
|
if !setValues(value) {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case []DataValue:
|
case []DataValue:
|
||||||
count := len(value)
|
count := len(value)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
values := make([]string, count)
|
values := make([]string, count)
|
||||||
for i, val := range value {
|
for i, val := range value {
|
||||||
if val.IsObject() {
|
if val.IsObject() {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
values[i] = val.Value()
|
values[i] = val.Value()
|
||||||
}
|
}
|
||||||
if !setValues(values) {
|
if !setValues(values) {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case []any:
|
case []any:
|
||||||
count := len(value)
|
count := len(value)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
sizes := make([]any, count)
|
sizes := make([]any, count)
|
||||||
for i, val := range value {
|
for i, val := range value {
|
||||||
|
|
@ -224,29 +226,29 @@ func (style *viewStyle) setGridCellSize(tag string, value any) bool {
|
||||||
sizes[i] = size
|
sizes[i] = size
|
||||||
} else {
|
} else {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style.properties[tag] = sizes
|
properties.setRaw(tag, sizes)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string {
|
func gridCellSizesCSS(properties Properties, tag PropertyName, session Session) string {
|
||||||
switch cellSize := gridCellSizes(style, tag, session); len(cellSize) {
|
switch cellSize := gridCellSizes(properties, tag, session); len(cellSize) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -283,8 +285,8 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) normalizeTag(tag string) string {
|
func normalizeGridLayoutTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case VerticalAlign:
|
case VerticalAlign:
|
||||||
return CellVerticalAlign
|
return CellVerticalAlign
|
||||||
|
|
@ -301,162 +303,169 @@ func (gridLayout *gridLayoutData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) Get(tag string) any {
|
func (gridLayout *gridLayoutData) getFunc(tag PropertyName) any {
|
||||||
return gridLayout.get(gridLayout.normalizeTag(tag))
|
switch tag {
|
||||||
}
|
case Gap:
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) get(tag string) any {
|
|
||||||
if tag == Gap {
|
|
||||||
rowGap := GetGridRowGap(gridLayout)
|
rowGap := GetGridRowGap(gridLayout)
|
||||||
columnGap := GetGridColumnGap(gridLayout)
|
columnGap := GetGridColumnGap(gridLayout)
|
||||||
if rowGap.Equal(columnGap) {
|
if rowGap.Equal(columnGap) {
|
||||||
return rowGap
|
return rowGap
|
||||||
}
|
}
|
||||||
return AutoSize()
|
return AutoSize()
|
||||||
}
|
|
||||||
|
|
||||||
return gridLayout.viewsContainerData.get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) Remove(tag string) {
|
|
||||||
gridLayout.remove(gridLayout.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) remove(tag string) {
|
|
||||||
switch tag {
|
|
||||||
case Gap:
|
|
||||||
gridLayout.remove(GridRowGap)
|
|
||||||
gridLayout.remove(GridColumnGap)
|
|
||||||
gridLayout.propertyChangedEvent(Gap)
|
|
||||||
return
|
|
||||||
|
|
||||||
case Content:
|
case Content:
|
||||||
gridLayout.adapter = nil
|
if gridLayout.adapter != nil {
|
||||||
}
|
return gridLayout.adapter
|
||||||
|
|
||||||
gridLayout.viewsContainerData.remove(tag)
|
|
||||||
|
|
||||||
if gridLayout.created {
|
|
||||||
switch tag {
|
|
||||||
case CellWidth:
|
|
||||||
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-columns`,
|
|
||||||
gridLayout.gridCellSizesCSS(CellWidth, gridLayout.session))
|
|
||||||
|
|
||||||
case CellHeight:
|
|
||||||
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-rows`,
|
|
||||||
gridLayout.gridCellSizesCSS(CellHeight, gridLayout.session))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return gridLayout.viewsContainerData.getFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) Set(tag string, value any) bool {
|
func (gridLayout *gridLayoutData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
return gridLayout.set(gridLayout.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
gridLayout.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Gap:
|
case Gap:
|
||||||
return gridLayout.set(GridRowGap, value) && gridLayout.set(GridColumnGap, value)
|
result := []PropertyName{}
|
||||||
|
for _, tag := range []PropertyName{GridRowGap, GridColumnGap} {
|
||||||
|
if gridLayout.getRaw(tag) != nil {
|
||||||
|
gridLayout.setRaw(tag, nil)
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
case Content:
|
||||||
|
if len(gridLayout.views) > 0 || gridLayout.adapter != nil {
|
||||||
|
gridLayout.views = []View{}
|
||||||
|
gridLayout.adapter = nil
|
||||||
|
return []PropertyName{Content}
|
||||||
|
}
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gridLayout.viewsContainerData.removeFunc(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gridLayout *gridLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case Gap:
|
||||||
|
result := gridLayout.setFunc(GridRowGap, value)
|
||||||
|
if result != nil {
|
||||||
|
if gap := gridLayout.getRaw(GridRowGap); gap != nil {
|
||||||
|
gridLayout.setRaw(GridColumnGap, gap)
|
||||||
|
result = append(result, GridColumnGap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
case Content:
|
case Content:
|
||||||
if adapter, ok := value.(GridAdapter); ok {
|
if adapter, ok := value.(GridAdapter); ok {
|
||||||
gridLayout.adapter = adapter
|
gridLayout.adapter = adapter
|
||||||
gridLayout.UpdateGridContent()
|
gridLayout.createGridContent()
|
||||||
return true
|
} else if gridLayout.setContent(value) {
|
||||||
|
gridLayout.adapter = nil
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
gridLayout.adapter = nil
|
return []PropertyName{Content}
|
||||||
}
|
}
|
||||||
|
|
||||||
if gridLayout.viewsContainerData.set(tag, value) {
|
return gridLayout.viewsContainerData.setFunc(tag, value)
|
||||||
if gridLayout.created {
|
}
|
||||||
switch tag {
|
|
||||||
case CellWidth:
|
|
||||||
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-columns`,
|
|
||||||
gridLayout.gridCellSizesCSS(CellWidth, gridLayout.session))
|
|
||||||
|
|
||||||
case CellHeight:
|
func (gridLayout *gridLayoutData) propertyChanged(tag PropertyName) {
|
||||||
gridLayout.session.updateCSSProperty(gridLayout.htmlID(), `grid-template-rows`,
|
switch tag {
|
||||||
gridLayout.gridCellSizesCSS(CellHeight, gridLayout.session))
|
case CellWidth:
|
||||||
|
session := gridLayout.Session()
|
||||||
|
session.updateCSSProperty(gridLayout.htmlID(), `grid-template-columns`,
|
||||||
|
gridCellSizesCSS(gridLayout, CellWidth, session))
|
||||||
|
|
||||||
|
case CellHeight:
|
||||||
|
session := gridLayout.Session()
|
||||||
|
session.updateCSSProperty(gridLayout.htmlID(), `grid-template-rows`,
|
||||||
|
gridCellSizesCSS(gridLayout, CellHeight, session))
|
||||||
|
|
||||||
|
default:
|
||||||
|
gridLayout.viewsContainerData.propertyChanged(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gridLayout *gridLayoutData) createGridContent() bool {
|
||||||
|
if gridLayout.adapter == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := gridLayout.adapter
|
||||||
|
gridLayout.views = []View{}
|
||||||
|
|
||||||
|
session := gridLayout.session
|
||||||
|
htmlID := gridLayout.htmlID()
|
||||||
|
isDisabled := IsDisabled(gridLayout)
|
||||||
|
|
||||||
|
var columnSpan GridCellColumnSpanAdapter = nil
|
||||||
|
if span, ok := adapter.(GridCellColumnSpanAdapter); ok {
|
||||||
|
columnSpan = span
|
||||||
|
}
|
||||||
|
|
||||||
|
var rowSpan GridCellRowSpanAdapter = nil
|
||||||
|
if span, ok := adapter.(GridCellRowSpanAdapter); ok {
|
||||||
|
rowSpan = span
|
||||||
|
}
|
||||||
|
|
||||||
|
width := adapter.GridColumnCount()
|
||||||
|
height := adapter.GridRowCount()
|
||||||
|
for column := 0; column < width; column++ {
|
||||||
|
for row := 0; row < height; row++ {
|
||||||
|
if view := adapter.GridCellContent(row, column, session); view != nil {
|
||||||
|
view.setParentID(htmlID)
|
||||||
|
|
||||||
|
columnCount := 1
|
||||||
|
if columnSpan != nil {
|
||||||
|
columnCount = columnSpan.GridCellColumnSpan(row, column)
|
||||||
|
}
|
||||||
|
|
||||||
|
if columnCount > 1 {
|
||||||
|
view.Set(Column, Range{First: column, Last: column + columnCount - 1})
|
||||||
|
} else {
|
||||||
|
view.Set(Column, column)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowCount := 1
|
||||||
|
if rowSpan != nil {
|
||||||
|
rowCount = rowSpan.GridCellRowSpan(row, column)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rowCount > 1 {
|
||||||
|
view.Set(Row, Range{First: row, Last: row + rowCount - 1})
|
||||||
|
} else {
|
||||||
|
view.Set(Row, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDisabled {
|
||||||
|
view.Set(Disabled, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
gridLayout.views = append(gridLayout.views, view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gridLayout *gridLayoutData) UpdateGridContent() {
|
func (gridLayout *gridLayoutData) UpdateGridContent() {
|
||||||
if adapter := gridLayout.adapter; adapter != nil {
|
if gridLayout.createGridContent() {
|
||||||
gridLayout.views = []View{}
|
|
||||||
|
|
||||||
session := gridLayout.session
|
|
||||||
htmlID := gridLayout.htmlID()
|
|
||||||
isDisabled := IsDisabled(gridLayout)
|
|
||||||
|
|
||||||
var columnSpan GridCellColumnSpanAdapter = nil
|
|
||||||
if span, ok := adapter.(GridCellColumnSpanAdapter); ok {
|
|
||||||
columnSpan = span
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowSpan GridCellRowSpanAdapter = nil
|
|
||||||
if span, ok := adapter.(GridCellRowSpanAdapter); ok {
|
|
||||||
rowSpan = span
|
|
||||||
}
|
|
||||||
|
|
||||||
width := adapter.GridColumnCount()
|
|
||||||
height := adapter.GridRowCount()
|
|
||||||
for column := 0; column < width; column++ {
|
|
||||||
for row := 0; row < height; row++ {
|
|
||||||
if view := adapter.GridCellContent(row, column, session); view != nil {
|
|
||||||
view.setParentID(htmlID)
|
|
||||||
|
|
||||||
columnCount := 1
|
|
||||||
if columnSpan != nil {
|
|
||||||
columnCount = columnSpan.GridCellColumnSpan(row, column)
|
|
||||||
}
|
|
||||||
|
|
||||||
if columnCount > 1 {
|
|
||||||
view.Set(Column, Range{First: column, Last: column + columnCount - 1})
|
|
||||||
} else {
|
|
||||||
view.Set(Column, column)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowCount := 1
|
|
||||||
if rowSpan != nil {
|
|
||||||
rowCount = rowSpan.GridCellRowSpan(row, column)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rowCount > 1 {
|
|
||||||
view.Set(Row, Range{First: row, Last: row + rowCount - 1})
|
|
||||||
} else {
|
|
||||||
view.Set(Row, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDisabled {
|
|
||||||
view.Set(Disabled, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
gridLayout.views = append(gridLayout.views, view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if gridLayout.created {
|
if gridLayout.created {
|
||||||
updateInnerHTML(htmlID, session)
|
updateInnerHTML(gridLayout.htmlID(), gridLayout.session)
|
||||||
}
|
}
|
||||||
|
|
||||||
gridLayout.propertyChangedEvent(Content)
|
if listener, ok := gridLayout.changeListener[Content]; ok {
|
||||||
|
listener(gridLayout, Content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gridCellSizes(properties Properties, tag string, session Session) []SizeUnit {
|
func gridCellSizes(properties Properties, tag PropertyName, session Session) []SizeUnit {
|
||||||
if value := properties.Get(tag); value != nil {
|
if value := properties.Get(tag); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case []SizeUnit:
|
case []SizeUnit:
|
||||||
|
|
@ -524,10 +533,7 @@ func GetGridAutoFlow(view View, subviewID ...string) int {
|
||||||
// If the result is a single value array, then the width of all cell is equal.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetCellWidth(view View, subviewID ...string) []SizeUnit {
|
func GetCellWidth(view View, subviewID ...string) []SizeUnit {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return gridCellSizes(view, CellWidth, view.Session())
|
return gridCellSizes(view, CellWidth, view.Session())
|
||||||
}
|
}
|
||||||
return []SizeUnit{}
|
return []SizeUnit{}
|
||||||
|
|
@ -537,10 +543,7 @@ func GetCellWidth(view View, subviewID ...string) []SizeUnit {
|
||||||
// If the result is a single value array, then the height of all cell is equal.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetCellHeight(view View, subviewID ...string) []SizeUnit {
|
func GetCellHeight(view View, subviewID ...string) []SizeUnit {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return gridCellSizes(view, CellHeight, view.Session())
|
return gridCellSizes(view, CellHeight, view.Session())
|
||||||
}
|
}
|
||||||
return []SizeUnit{}
|
return []SizeUnit{}
|
||||||
|
|
|
||||||
190
imageView.go
190
imageView.go
|
|
@ -9,33 +9,33 @@ import (
|
||||||
const (
|
const (
|
||||||
// LoadedEvent is the constant for "loaded-event" property tag.
|
// LoadedEvent is the constant for "loaded-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ImageView`.
|
// Used by ImageView.
|
||||||
// Occur when the image has been loaded.
|
// Occur when the image has been loaded.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(image rui.ImageView)`.
|
// func(image rui.ImageView)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// image - Interface of an image view which generated this event.
|
// image - Interface of an image view which generated this event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func()`.
|
// func()
|
||||||
LoadedEvent = "loaded-event"
|
LoadedEvent PropertyName = "loaded-event"
|
||||||
|
|
||||||
// ErrorEvent is the constant for "error-event" property tag.
|
// ErrorEvent is the constant for "error-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ImageView`.
|
// Used by ImageView.
|
||||||
// Occur when the image loading has been failed.
|
// Occur when the image loading has been failed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(image rui.ImageView)`.
|
// func(image rui.ImageView)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// image - Interface of an image view which generated this event.
|
// image - Interface of an image view which generated this event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func()`.
|
// func()
|
||||||
ErrorEvent = "error-event"
|
ErrorEvent PropertyName = "error-event"
|
||||||
|
|
||||||
// NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized
|
// NoneFit - value of the "object-fit" property of an ImageView. The replaced content is not resized
|
||||||
NoneFit = 0
|
NoneFit = 0
|
||||||
|
|
@ -88,7 +88,7 @@ func NewImageView(session Session, params Params) ImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImageView(session Session) View {
|
func newImageView(session Session) View {
|
||||||
return NewImageView(session, nil)
|
return new(imageViewData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of imageView by default values
|
// Init initialize fields of imageView by default values
|
||||||
|
|
@ -96,14 +96,13 @@ func (imageView *imageViewData) init(session Session) {
|
||||||
imageView.viewData.init(session)
|
imageView.viewData.init(session)
|
||||||
imageView.tag = "ImageView"
|
imageView.tag = "ImageView"
|
||||||
imageView.systemClass = "ruiImageView"
|
imageView.systemClass = "ruiImageView"
|
||||||
|
imageView.normalize = normalizeImageViewTag
|
||||||
|
imageView.set = imageView.setFunc
|
||||||
|
imageView.changed = imageView.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) String() string {
|
func normalizeImageViewTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(imageView, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *imageViewData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case "source":
|
case "source":
|
||||||
tag = Source
|
tag = Source
|
||||||
|
|
@ -123,127 +122,58 @@ func (imageView *imageViewData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) Remove(tag string) {
|
func (imageView *imageViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
imageView.remove(imageView.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *imageViewData) remove(tag string) {
|
switch tag {
|
||||||
imageView.viewData.remove(tag)
|
case Source, SrcSet, AltText:
|
||||||
if imageView.created {
|
if text, ok := value.(string); ok {
|
||||||
switch tag {
|
return setStringPropertyValue(imageView, tag, text)
|
||||||
case Source:
|
|
||||||
imageView.session.updateProperty(imageView.htmlID(), "src", "")
|
|
||||||
imageView.session.removeProperty(imageView.htmlID(), "srcset")
|
|
||||||
|
|
||||||
case AltText:
|
|
||||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
|
||||||
|
|
||||||
case ImageVerticalAlign, ImageHorizontalAlign:
|
|
||||||
updateCSSStyle(imageView.htmlID(), imageView.session)
|
|
||||||
}
|
}
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case LoadedEvent, ErrorEvent:
|
||||||
|
return setNoArgEventListener[ImageView](imageView, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return imageView.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) Set(tag string, value any) bool {
|
func (imageView *imageViewData) propertyChanged(tag PropertyName) {
|
||||||
return imageView.set(imageView.normalizeTag(tag), value)
|
session := imageView.Session()
|
||||||
}
|
htmlID := imageView.htmlID()
|
||||||
|
|
||||||
func (imageView *imageViewData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
imageView.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Source:
|
case Source:
|
||||||
if text, ok := value.(string); ok {
|
src, srcset := imageViewSrc(imageView, GetImageViewSource(imageView))
|
||||||
imageView.properties[tag] = text
|
session.updateProperty(htmlID, "src", src)
|
||||||
if imageView.created {
|
if srcset != "" {
|
||||||
src, srcset := imageView.src(text)
|
session.updateProperty(htmlID, "srcset", srcset)
|
||||||
imageView.session.updateProperty(imageView.htmlID(), "src", src)
|
} else {
|
||||||
|
session.removeProperty(htmlID, "srcset")
|
||||||
if srcset != "" {
|
|
||||||
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
|
|
||||||
} else {
|
|
||||||
imageView.session.removeProperty(imageView.htmlID(), "srcset")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageView.propertyChangedEvent(Source)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
notCompatibleType(Source, value)
|
|
||||||
|
|
||||||
case SrcSet:
|
case SrcSet:
|
||||||
if text, ok := value.(string); ok {
|
_, srcset := imageViewSrc(imageView, GetImageViewSource(imageView))
|
||||||
if text == "" {
|
if srcset != "" {
|
||||||
delete(imageView.properties, tag)
|
session.updateProperty(htmlID, "srcset", srcset)
|
||||||
} else {
|
} else {
|
||||||
imageView.properties[tag] = text
|
session.removeProperty(htmlID, "srcset")
|
||||||
}
|
|
||||||
if imageView.created {
|
|
||||||
_, srcset := imageView.src(text)
|
|
||||||
if srcset != "" {
|
|
||||||
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
|
|
||||||
} else {
|
|
||||||
imageView.session.removeProperty(imageView.htmlID(), "srcset")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageView.propertyChangedEvent(Source)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
notCompatibleType(Source, value)
|
|
||||||
|
|
||||||
case AltText:
|
case AltText:
|
||||||
if text, ok := value.(string); ok {
|
updateInnerHTML(htmlID, session)
|
||||||
imageView.properties[AltText] = text
|
|
||||||
if imageView.created {
|
|
||||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
|
||||||
}
|
|
||||||
imageView.propertyChangedEvent(Source)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
|
|
||||||
case LoadedEvent, ErrorEvent:
|
case ImageVerticalAlign, ImageHorizontalAlign:
|
||||||
if listeners, ok := valueToNoParamListeners[ImageView](value); ok {
|
updateCSSStyle(htmlID, session)
|
||||||
if listeners == nil {
|
|
||||||
delete(imageView.properties, tag)
|
|
||||||
} else {
|
|
||||||
imageView.properties[tag] = listeners
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if imageView.viewData.set(tag, value) {
|
imageView.viewData.propertyChanged(tag)
|
||||||
if imageView.created {
|
|
||||||
switch tag {
|
|
||||||
case ImageVerticalAlign, ImageHorizontalAlign:
|
|
||||||
updateCSSStyle(imageView.htmlID(), imageView.session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) Get(tag string) any {
|
func imageViewSrcSet(view View, path string) string {
|
||||||
return imageView.viewData.get(imageView.normalizeTag(tag))
|
if value := view.getRaw(SrcSet); value != nil {
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *imageViewData) imageListeners(tag string) []func(ImageView) {
|
|
||||||
if value := imageView.getRaw(tag); value != nil {
|
|
||||||
if listeners, ok := value.([]func(ImageView)); ok {
|
|
||||||
return listeners
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []func(ImageView){}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *imageViewData) srcSet(path string) string {
|
|
||||||
if value := imageView.getRaw(SrcSet); value != nil {
|
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
srcset := strings.Split(text, ",")
|
srcset := strings.Split(text, ",")
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
|
|
@ -286,9 +216,9 @@ func (imageView *imageViewData) htmlTag() string {
|
||||||
return "img"
|
return "img"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) src(src string) (string, string) {
|
func imageViewSrc(view View, src string) (string, string) {
|
||||||
if src != "" && src[0] == '@' {
|
if src != "" && src[0] == '@' {
|
||||||
if image, ok := imageView.Session().ImageConstant(src[1:]); ok {
|
if image, ok := view.Session().ImageConstant(src[1:]); ok {
|
||||||
src = image
|
src = image
|
||||||
} else {
|
} else {
|
||||||
src = ""
|
src = ""
|
||||||
|
|
@ -296,7 +226,7 @@ func (imageView *imageViewData) src(src string) (string, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if src != "" {
|
if src != "" {
|
||||||
return src, imageView.srcSet(src)
|
return src, imageViewSrcSet(view, src)
|
||||||
}
|
}
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +236,7 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde
|
||||||
imageView.viewData.htmlProperties(self, buffer)
|
imageView.viewData.htmlProperties(self, buffer)
|
||||||
|
|
||||||
if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" {
|
if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" {
|
||||||
if src, srcset := imageView.src(imageResource); src != "" {
|
if src, srcset := imageViewSrc(imageView, imageResource); src != "" {
|
||||||
buffer.WriteString(` src="`)
|
buffer.WriteString(` src="`)
|
||||||
buffer.WriteString(src)
|
buffer.WriteString(src)
|
||||||
buffer.WriteString(`"`)
|
buffer.WriteString(`"`)
|
||||||
|
|
@ -326,7 +256,7 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde
|
||||||
|
|
||||||
buffer.WriteString(` onload="imageLoaded(this, event)"`)
|
buffer.WriteString(` onload="imageLoaded(this, event)"`)
|
||||||
|
|
||||||
if len(imageView.imageListeners(ErrorEvent)) > 0 {
|
if len(getNoArgEventListeners[ImageView](imageView, nil, ErrorEvent)) > 0 {
|
||||||
buffer.WriteString(` onerror="imageError(this, event)"`)
|
buffer.WriteString(` onerror="imageError(this, event)"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -366,10 +296,10 @@ func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *imageViewData) handleCommand(self View, command string, data DataObject) bool {
|
func (imageView *imageViewData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "imageViewError":
|
case "imageViewError":
|
||||||
for _, listener := range imageView.imageListeners(ErrorEvent) {
|
for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, ErrorEvent) {
|
||||||
listener(imageView)
|
listener(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -378,7 +308,7 @@ func (imageView *imageViewData) handleCommand(self View, command string, data Da
|
||||||
imageView.naturalHeight = dataFloatProperty(data, "natural-height")
|
imageView.naturalHeight = dataFloatProperty(data, "natural-height")
|
||||||
imageView.currentSrc, _ = data.PropertyValue("current-src")
|
imageView.currentSrc, _ = data.PropertyValue("current-src")
|
||||||
|
|
||||||
for _, listener := range imageView.imageListeners(LoadedEvent) {
|
for _, listener := range getNoArgEventListeners[ImageView](imageView, nil, LoadedEvent) {
|
||||||
listener(imageView)
|
listener(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,11 +329,7 @@ func (imageView *imageViewData) CurrentSource() string {
|
||||||
// GetImageViewSource returns the image URL of an ImageView subview.
|
// GetImageViewSource returns the image URL of an ImageView subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
||||||
func GetImageViewSource(view View, subviewID ...string) string {
|
func GetImageViewSource(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if image, ok := imageProperty(view, Source, view.Session()); ok {
|
if image, ok := imageProperty(view, Source, view.Session()); ok {
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
@ -415,11 +341,7 @@ func GetImageViewSource(view View, subviewID ...string) string {
|
||||||
// GetImageViewAltText returns an alternative text description of an ImageView subview.
|
// GetImageViewAltText returns an alternative text description of an ImageView subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned
|
||||||
func GetImageViewAltText(view View, subviewID ...string) string {
|
func GetImageViewAltText(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.getRaw(AltText); value != nil {
|
if value := view.getRaw(AltText); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
text, _ = view.Session().GetString(text)
|
text, _ = view.Session().GetString(text)
|
||||||
|
|
|
||||||
430
keyEvents.go
430
keyEvents.go
|
|
@ -6,39 +6,43 @@ import "strings"
|
||||||
const (
|
const (
|
||||||
// KeyDownEvent is the constant for "key-down-event" property tag.
|
// KeyDownEvent is the constant for "key-down-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when a key is pressed.
|
// Is fired when a key is pressed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.KeyEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.KeyEvent).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Key event.
|
// - event - Key event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.KeyEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.KeyEvent)
|
||||||
KeyDownEvent = "key-down-event"
|
// func()
|
||||||
|
KeyDownEvent PropertyName = "key-down-event"
|
||||||
|
|
||||||
// KeyUpEvent is the constant for "key-up-event" property tag.
|
// KeyUpEvent is the constant for "key-up-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when a key is released.
|
// Is fired when a key is released.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.KeyEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.KeyEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Key event.
|
// - event - Key event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.KeyEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.KeyEvent)
|
||||||
KeyUpEvent = "key-up-event"
|
// func()
|
||||||
|
KeyUpEvent PropertyName = "key-up-event"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ControlKeyMask represent ORed state of keyboard's control keys like [AltKey], [CtrlKey], [ShiftKey] and [MetaKey]
|
// ControlKeyMask represent ORed state of keyboard's control keys like [AltKey], [CtrlKey], [ShiftKey] and [MetaKey]
|
||||||
|
|
@ -429,402 +433,24 @@ func (event *KeyEvent) init(data DataObject) {
|
||||||
event.MetaKey = getBool("metaKey")
|
event.MetaKey = getBool("metaKey")
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueToEventListeners[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 valueToEventWithOldListeners[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 (view *viewData) setKeyListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[View, KeyEvent](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removeKeyListener(tag)
|
|
||||||
} else {
|
|
||||||
switch tag {
|
|
||||||
case KeyDownEvent:
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), "onkeydown", "keyDownEvent(this, event)")
|
|
||||||
}
|
|
||||||
|
|
||||||
case KeyUpEvent:
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), "onkeyup", "keyUpEvent(this, event)")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removeKeyListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
|
||||||
if view.created {
|
|
||||||
switch tag {
|
|
||||||
case KeyDownEvent:
|
|
||||||
if !view.Focusable() {
|
|
||||||
view.session.removeProperty(view.htmlID(), "onkeydown")
|
|
||||||
}
|
|
||||||
|
|
||||||
case KeyUpEvent:
|
|
||||||
view.session.removeProperty(view.htmlID(), "onkeyup")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEventWithOldListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E, E) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if 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 getEventListeners[V View, E any](view View, subviewID []string, tag string) []func(V, E) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(tag); value != nil {
|
|
||||||
if result, ok := value.([]func(V, E)); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []func(V, E){}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyEventsHtml(view View, buffer *strings.Builder) {
|
func keyEventsHtml(view View, buffer *strings.Builder) {
|
||||||
if len(getEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
|
if len(getOneArgEventListeners[View, KeyEvent](view, nil, KeyDownEvent)) > 0 {
|
||||||
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
||||||
} else if view.Focusable() {
|
} else if view.Focusable() {
|
||||||
if len(getEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
|
if len(getOneArgEventListeners[View, MouseEvent](view, nil, ClickEvent)) > 0 {
|
||||||
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
buffer.WriteString(`onkeydown="keyDownEvent(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if listeners := getEventListeners[View, KeyEvent](view, nil, KeyUpEvent); len(listeners) > 0 {
|
if listeners := getOneArgEventListeners[View, KeyEvent](view, nil, KeyUpEvent); len(listeners) > 0 {
|
||||||
buffer.WriteString(`onkeyup="keyUpEvent(this, event)" `)
|
buffer.WriteString(`onkeyup="keyUpEvent(this, event)" `)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleKeyEvents(view View, tag string, data DataObject) {
|
func handleKeyEvents(view View, tag PropertyName, data DataObject) {
|
||||||
var event KeyEvent
|
var event KeyEvent
|
||||||
event.init(data)
|
event.init(data)
|
||||||
listeners := getEventListeners[View, KeyEvent](view, nil, tag)
|
listeners := getOneArgEventListeners[View, KeyEvent](view, nil, tag)
|
||||||
|
|
||||||
if len(listeners) > 0 {
|
if len(listeners) > 0 {
|
||||||
for _, listener := range listeners {
|
for _, listener := range listeners {
|
||||||
|
|
@ -834,7 +460,7 @@ func handleKeyEvents(view View, tag string, data DataObject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if tag == KeyDownEvent && view.Focusable() && (event.Key == " " || event.Key == "Enter") && !IsDisabled(view) {
|
if tag == KeyDownEvent && view.Focusable() && (event.Key == " " || event.Key == "Enter") && !IsDisabled(view) {
|
||||||
if listeners := getEventListeners[View, MouseEvent](view, nil, ClickEvent); len(listeners) > 0 {
|
if listeners := getOneArgEventListeners[View, MouseEvent](view, nil, ClickEvent); len(listeners) > 0 {
|
||||||
clickEvent := MouseEvent{
|
clickEvent := MouseEvent{
|
||||||
TimeStamp: event.TimeStamp,
|
TimeStamp: event.TimeStamp,
|
||||||
Button: PrimaryMouseButton,
|
Button: PrimaryMouseButton,
|
||||||
|
|
@ -860,11 +486,11 @@ func handleKeyEvents(view View, tag string, data DataObject) {
|
||||||
// GetKeyDownListeners returns the "key-down-event" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetKeyDownListeners(view View, subviewID ...string) []func(View, KeyEvent) {
|
||||||
return getEventListeners[View, KeyEvent](view, subviewID, KeyDownEvent)
|
return getOneArgEventListeners[View, KeyEvent](view, subviewID, KeyDownEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeyUpListeners returns the "key-up-event" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetKeyUpListeners(view View, subviewID ...string) []func(View, KeyEvent) {
|
||||||
return getEventListeners[View, KeyEvent](view, subviewID, KeyUpEvent)
|
return getOneArgEventListeners[View, KeyEvent](view, subviewID, KeyUpEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
154
listLayout.go
154
listLayout.go
|
|
@ -53,7 +53,8 @@ func NewListLayout(session Session, params Params) ListLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListLayout(session Session) View {
|
func newListLayout(session Session) View {
|
||||||
return NewListLayout(session, nil)
|
//return NewListLayout(session, nil)
|
||||||
|
return new(listLayoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of ViewsAlignContainer by default values
|
// Init initialize fields of ViewsAlignContainer by default values
|
||||||
|
|
@ -61,14 +62,16 @@ func (listLayout *listLayoutData) init(session Session) {
|
||||||
listLayout.viewsContainerData.init(session)
|
listLayout.viewsContainerData.init(session)
|
||||||
listLayout.tag = "ListLayout"
|
listLayout.tag = "ListLayout"
|
||||||
listLayout.systemClass = "ruiListLayout"
|
listLayout.systemClass = "ruiListLayout"
|
||||||
|
listLayout.normalize = normalizeListLayoutTag
|
||||||
|
listLayout.get = listLayout.getFunc
|
||||||
|
listLayout.set = listLayout.setFunc
|
||||||
|
listLayout.remove = listLayout.removeFunc
|
||||||
|
listLayout.changed = listLayout.propertyChanged
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listLayout *listLayoutData) String() string {
|
func normalizeListLayoutTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(listLayout, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (listLayout *listLayoutData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case "wrap":
|
case "wrap":
|
||||||
tag = ListWrap
|
tag = ListWrap
|
||||||
|
|
@ -82,91 +85,90 @@ func (listLayout *listLayoutData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listLayout *listLayoutData) Get(tag string) any {
|
func (listLayout *listLayoutData) getFunc(tag PropertyName) any {
|
||||||
return listLayout.get(listLayout.normalizeTag(tag))
|
switch tag {
|
||||||
}
|
case Gap:
|
||||||
|
|
||||||
func (listLayout *listLayoutData) get(tag string) any {
|
|
||||||
if tag == Gap {
|
|
||||||
if rowGap := GetListRowGap(listLayout); rowGap.Equal(GetListColumnGap(listLayout)) {
|
if rowGap := GetListRowGap(listLayout); rowGap.Equal(GetListColumnGap(listLayout)) {
|
||||||
return rowGap
|
return rowGap
|
||||||
}
|
}
|
||||||
return AutoSize()
|
return AutoSize()
|
||||||
}
|
|
||||||
|
|
||||||
return listLayout.viewsContainerData.get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (listLayout *listLayoutData) Remove(tag string) {
|
|
||||||
listLayout.remove(listLayout.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (listLayout *listLayoutData) remove(tag string) {
|
|
||||||
switch tag {
|
|
||||||
case Gap:
|
|
||||||
listLayout.remove(ListRowGap)
|
|
||||||
listLayout.remove(ListColumnGap)
|
|
||||||
return
|
|
||||||
|
|
||||||
case Content:
|
case Content:
|
||||||
listLayout.adapter = nil
|
if listLayout.adapter != nil {
|
||||||
}
|
return listLayout.adapter
|
||||||
|
|
||||||
listLayout.viewsContainerData.remove(tag)
|
|
||||||
if listLayout.created {
|
|
||||||
switch tag {
|
|
||||||
case Orientation, ListWrap, HorizontalAlign, VerticalAlign:
|
|
||||||
updateCSSStyle(listLayout.htmlID(), listLayout.session)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return listLayout.viewsContainerData.getFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listLayout *listLayoutData) Set(tag string, value any) bool {
|
func (listLayout *listLayoutData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
return listLayout.set(listLayout.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (listLayout *listLayoutData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
listLayout.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Gap:
|
case Gap:
|
||||||
return listLayout.set(ListRowGap, value) && listLayout.set(ListColumnGap, value)
|
result := []PropertyName{}
|
||||||
|
for _, tag := range []PropertyName{ListRowGap, ListColumnGap} {
|
||||||
|
if listLayout.getRaw(tag) != nil {
|
||||||
|
listLayout.setRaw(tag, nil)
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
case Content:
|
||||||
|
listLayout.viewsContainerData.removeFunc(Content)
|
||||||
|
listLayout.adapter = nil
|
||||||
|
return []PropertyName{Content}
|
||||||
|
}
|
||||||
|
|
||||||
|
return listLayout.viewsContainerData.removeFunc(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (listLayout *listLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
case Gap:
|
||||||
|
result := listLayout.setFunc(ListRowGap, value)
|
||||||
|
if result != nil {
|
||||||
|
if gap := listLayout.getRaw(ListRowGap); gap != nil {
|
||||||
|
listLayout.setRaw(ListColumnGap, gap)
|
||||||
|
result = append(result, ListColumnGap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
case Content:
|
case Content:
|
||||||
if adapter, ok := value.(ListAdapter); ok {
|
if adapter, ok := value.(ListAdapter); ok {
|
||||||
listLayout.adapter = adapter
|
listLayout.adapter = adapter
|
||||||
listLayout.UpdateContent()
|
listLayout.createContent()
|
||||||
// TODO
|
} else if listLayout.setContent(value) {
|
||||||
return true
|
listLayout.adapter = nil
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
listLayout.adapter = nil
|
return []PropertyName{Content}
|
||||||
}
|
}
|
||||||
|
return listLayout.viewsContainerData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
if listLayout.viewsContainerData.set(tag, value) {
|
func (listLayout *listLayoutData) propertyChanged(tag PropertyName) {
|
||||||
if listLayout.created {
|
switch tag {
|
||||||
switch tag {
|
case Orientation, ListWrap, HorizontalAlign, VerticalAlign:
|
||||||
case Orientation, ListWrap, HorizontalAlign, VerticalAlign:
|
updateCSSStyle(listLayout.htmlID(), listLayout.Session())
|
||||||
updateCSSStyle(listLayout.htmlID(), listLayout.session)
|
|
||||||
}
|
default:
|
||||||
}
|
listLayout.viewsContainerData.propertyChanged(tag)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
if listLayout.views != nil {
|
if listLayout.views != nil {
|
||||||
for _, view := range listLayout.views {
|
for _, view := range listLayout.views {
|
||||||
view.addToCSSStyle(map[string]string{`flex`: `0 0 auto`})
|
view.addToCSSStyle(map[string]string{`flex`: `0 0 auto`})
|
||||||
viewHTML(view, buffer)
|
viewHTML(view, buffer, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (listLayout *listLayoutData) UpdateContent() {
|
func (listLayout *listLayoutData) createContent() bool {
|
||||||
if adapter := listLayout.adapter; adapter != nil {
|
if adapter := listLayout.adapter; adapter != nil {
|
||||||
listLayout.views = []View{}
|
listLayout.views = []View{}
|
||||||
|
|
||||||
|
|
@ -185,11 +187,20 @@ func (listLayout *listLayoutData) UpdateContent() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (listLayout *listLayoutData) UpdateContent() {
|
||||||
|
if listLayout.createContent() {
|
||||||
if listLayout.created {
|
if listLayout.created {
|
||||||
updateInnerHTML(htmlID, session)
|
updateInnerHTML(listLayout.htmlID(), listLayout.session)
|
||||||
}
|
}
|
||||||
|
|
||||||
listLayout.propertyChangedEvent(Content)
|
if listener, ok := listLayout.changeListener[Content]; ok {
|
||||||
|
listener(listLayout, Content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,11 +222,7 @@ func GetListHorizontalAlign(view View, subviewID ...string) int {
|
||||||
// TopDownOrientation (0), StartToEndOrientation (1), BottomUpOrientation (2), or EndToStartOrientation (3)
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetListOrientation(view View, subviewID ...string) int {
|
func GetListOrientation(view View, subviewID ...string) int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if orientation, ok := valueToOrientation(view.Get(Orientation), view.Session()); ok {
|
if orientation, ok := valueToOrientation(view.Get(Orientation), view.Session()); ok {
|
||||||
return orientation
|
return orientation
|
||||||
}
|
}
|
||||||
|
|
@ -253,11 +260,7 @@ func GetListColumnGap(view View, subviewID ...string) SizeUnit {
|
||||||
// otherwise does nothing.
|
// otherwise does nothing.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) updates.
|
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) updates.
|
||||||
func UpdateContent(view View, subviewID ...string) {
|
func UpdateContent(view View, subviewID ...string) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
switch view := view.(type) {
|
switch view := view.(type) {
|
||||||
case GridLayout:
|
case GridLayout:
|
||||||
view.UpdateGridContent()
|
view.UpdateGridContent()
|
||||||
|
|
@ -267,6 +270,9 @@ func UpdateContent(view View, subviewID ...string) {
|
||||||
|
|
||||||
case ListView:
|
case ListView:
|
||||||
view.ReloadListViewData()
|
view.ReloadListViewData()
|
||||||
|
|
||||||
|
case TableView:
|
||||||
|
view.ReloadTableData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
833
listView.go
833
listView.go
File diff suppressed because it is too large
Load Diff
967
mediaPlayer.go
967
mediaPlayer.go
File diff suppressed because it is too large
Load Diff
222
mouseEvents.go
222
mouseEvents.go
|
|
@ -9,151 +9,167 @@ import (
|
||||||
const (
|
const (
|
||||||
// ClickEvent is the constant for "click-event" property tag.
|
// ClickEvent is the constant for "click-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Occur when the user clicks on the view.
|
// Occur when the user clicks on the view.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
ClickEvent = "click-event"
|
// func()
|
||||||
|
ClickEvent PropertyName = "click-event"
|
||||||
|
|
||||||
// DoubleClickEvent is the constant for "double-click-event" property tag.
|
// DoubleClickEvent is the constant for "double-click-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Occur when the user double clicks on the view.
|
// Occur when the user double clicks on the view.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
DoubleClickEvent = "double-click-event"
|
// func()
|
||||||
|
DoubleClickEvent PropertyName = "double-click-event"
|
||||||
|
|
||||||
// MouseDown is the constant for "mouse-down" property tag.
|
// MouseDown is the constant for "mouse-down" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired at a View when a pointing device button is pressed while the pointer is inside the view.
|
// Is fired at a View when a pointing device button is pressed while the pointer is inside the view.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
MouseDown = "mouse-down"
|
// func()
|
||||||
|
MouseDown PropertyName = "mouse-down"
|
||||||
|
|
||||||
// MouseUp is the constant for "mouse-up" property tag.
|
// MouseUp is the constant for "mouse-up" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired at a View when a button on a pointing device (such as a mouse or trackpad) is released while the pointer is
|
// Is fired at a View when a button on a pointing device (such as a mouse or trackpad) is released while the pointer is
|
||||||
// located inside it. "mouse-up" events are the counterpoint to "mouse-down" events.
|
// located inside it. "mouse-up" events are the counterpoint to "mouse-down" events.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
MouseUp = "mouse-up"
|
// func()
|
||||||
|
MouseUp PropertyName = "mouse-up"
|
||||||
|
|
||||||
// MouseMove is the constant for "mouse-move" property tag.
|
// MouseMove is the constant for "mouse-move" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired at a view when a pointing device(usually a mouse) is moved while the cursor's hotspot is inside it.
|
// Is fired at a view when a pointing device(usually a mouse) is moved while the cursor's hotspot is inside it.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
MouseMove = "mouse-move"
|
// func()
|
||||||
|
MouseMove PropertyName = "mouse-move"
|
||||||
|
|
||||||
// MouseOut is the constant for "mouse-out" property tag.
|
// MouseOut is the constant for "mouse-out" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired at a View when a pointing device (usually a mouse) is used to move the cursor so that it is no longer
|
// Is fired at a View when a pointing device (usually a mouse) is used to move the cursor so that it is no longer
|
||||||
// contained within the view or one of its children. "mouse-out" is also delivered to a view if the cursor enters a child
|
// contained within the view or one of its children. "mouse-out" is also delivered to a view if the cursor enters a child
|
||||||
// view, because the child view obscures the visible area of the view.
|
// view, because the child view obscures the visible area of the view.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
MouseOut = "mouse-out"
|
// func()
|
||||||
|
MouseOut PropertyName = "mouse-out"
|
||||||
|
|
||||||
// MouseOver is the constant for "mouse-over" property tag.
|
// MouseOver is the constant for "mouse-over" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired at a View when a pointing device (such as a mouse or trackpad) is used to move the cursor onto the view or one
|
// Is fired at a View when a pointing device (such as a mouse or trackpad) is used to move the cursor onto the view or one
|
||||||
// of its child views.
|
// of its child views.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
MouseOver = "mouse-over"
|
// func()
|
||||||
|
MouseOver PropertyName = "mouse-over"
|
||||||
|
|
||||||
// ContextMenuEvent is the constant for "context-menu-event" property tag.
|
// ContextMenuEvent is the constant for "context-menu-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Occur when the user calls the context menu by the right mouse clicking.
|
// Occur when the user calls the context menu by the right mouse clicking.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.MouseEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.MouseEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Mouse event.
|
// - event - Mouse event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(view rui.View)`,
|
//
|
||||||
// `func(event rui.MouseEvent)`,
|
// func(view rui.View)
|
||||||
// `func()`.
|
// func(event rui.MouseEvent)
|
||||||
ContextMenuEvent = "context-menu-event"
|
// func()
|
||||||
|
ContextMenuEvent PropertyName = "context-menu-event"
|
||||||
|
|
||||||
// PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state
|
// PrimaryMouseButton is a number of the main pressed button, usually the left button or the un-initialized state
|
||||||
PrimaryMouseButton = 0
|
PrimaryMouseButton = 0
|
||||||
|
|
@ -228,64 +244,6 @@ type MouseEvent struct {
|
||||||
MetaKey bool
|
MetaKey bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var mouseEvents = map[string]struct{ jsEvent, jsFunc string }{
|
|
||||||
ClickEvent: {jsEvent: "onclick", jsFunc: "clickEvent"},
|
|
||||||
DoubleClickEvent: {jsEvent: "ondblclick", jsFunc: "doubleClickEvent"},
|
|
||||||
MouseDown: {jsEvent: "onmousedown", jsFunc: "mouseDownEvent"},
|
|
||||||
MouseUp: {jsEvent: "onmouseup", jsFunc: "mouseUpEvent"},
|
|
||||||
MouseMove: {jsEvent: "onmousemove", jsFunc: "mouseMoveEvent"},
|
|
||||||
MouseOut: {jsEvent: "onmouseout", jsFunc: "mouseOutEvent"},
|
|
||||||
MouseOver: {jsEvent: "onmouseover", jsFunc: "mouseOverEvent"},
|
|
||||||
ContextMenuEvent: {jsEvent: "oncontextmenu", jsFunc: "contextMenuEvent"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) setMouseListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[View, MouseEvent](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removeMouseListener(tag)
|
|
||||||
} else if js, ok := mouseEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removeMouseListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
|
||||||
if view.created {
|
|
||||||
if js, ok := mouseEvents[tag]; ok {
|
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mouseEventsHtml(view View, buffer *strings.Builder, hasTooltip bool) {
|
|
||||||
for tag, js := range mouseEvents {
|
|
||||||
if value := view.getRaw(tag); value != nil {
|
|
||||||
if listeners, ok := value.([]func(View, MouseEvent)); ok && len(listeners) > 0 {
|
|
||||||
buffer.WriteString(js.jsEvent)
|
|
||||||
buffer.WriteString(`="`)
|
|
||||||
buffer.WriteString(js.jsFunc)
|
|
||||||
buffer.WriteString(`(this, event)" `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasTooltip {
|
|
||||||
buffer.WriteString(`onmouseenter="mouseEnterEvent(this, event)" `)
|
|
||||||
buffer.WriteString(`onmouseleave="mouseLeaveEvent(this, event)" `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTimeStamp(data DataObject) uint64 {
|
func getTimeStamp(data DataObject) uint64 {
|
||||||
if value, ok := data.PropertyValue("timeStamp"); ok {
|
if value, ok := data.PropertyValue("timeStamp"); ok {
|
||||||
if index := strings.Index(value, "."); index > 0 {
|
if index := strings.Index(value, "."); index > 0 {
|
||||||
|
|
@ -315,8 +273,8 @@ func (event *MouseEvent) init(data DataObject) {
|
||||||
event.MetaKey = dataBoolProperty(data, "metaKey")
|
event.MetaKey = dataBoolProperty(data, "metaKey")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMouseEvents(view View, tag string, data DataObject) {
|
func handleMouseEvents(view View, tag PropertyName, data DataObject) {
|
||||||
listeners := getEventListeners[View, MouseEvent](view, nil, tag)
|
listeners := getOneArgEventListeners[View, MouseEvent](view, nil, tag)
|
||||||
if len(listeners) > 0 {
|
if len(listeners) > 0 {
|
||||||
var event MouseEvent
|
var event MouseEvent
|
||||||
event.init(data)
|
event.init(data)
|
||||||
|
|
@ -330,48 +288,48 @@ func handleMouseEvents(view View, tag string, data DataObject) {
|
||||||
// GetClickListeners returns the "click-event" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetClickListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, ClickEvent)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, ClickEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDoubleClickListeners returns the "double-click-event" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetDoubleClickListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, DoubleClickEvent)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, DoubleClickEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContextMenuListeners returns the "context-menu" listener list.
|
// GetContextMenuListeners returns the "context-menu" listener list.
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetContextMenuListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, ContextMenuEvent)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, ContextMenuEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMouseDownListeners returns the "mouse-down" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetMouseDownListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, MouseDown)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseDown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMouseUpListeners returns the "mouse-up" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetMouseUpListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, MouseUp)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMouseMoveListeners returns the "mouse-move" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetMouseMoveListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, MouseMove)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseMove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMouseOverListeners returns the "mouse-over" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetMouseOverListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, MouseOver)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseOver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMouseOutListeners returns the "mouse-out" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetMouseOutListeners(view View, subviewID ...string) []func(View, MouseEvent) {
|
||||||
return getEventListeners[View, MouseEvent](view, subviewID, MouseOut)
|
return getOneArgEventListeners[View, MouseEvent](view, subviewID, MouseOut)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
327
numberPicker.go
327
numberPicker.go
|
|
@ -1,6 +1,7 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -10,75 +11,87 @@ import (
|
||||||
const (
|
const (
|
||||||
// NumberChangedEvent is the constant for "number-changed" property tag.
|
// NumberChangedEvent is the constant for "number-changed" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Set listener(s) that track the change in the entered value.
|
// Set listener(s) that track the change in the entered value.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(picker rui.NumberPicker, newValue, oldValue float64)`.
|
//
|
||||||
|
// func(picker rui.NumberPicker, newValue float64, oldValue float64)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// picker - Interface of a number picker which generated this event,
|
// - picker - Interface of a number picker which generated this event,
|
||||||
// newValue - New value,
|
// - newValue - New value,
|
||||||
// oldValue - Old Value.
|
// - oldValue - Old Value.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(picker rui.NumberPicker, newValue float64)`,
|
//
|
||||||
// `func(newValue, oldValue float64)`,
|
// func(picker rui.NumberPicker, newValue float64)
|
||||||
// `func(newValue float64)`,
|
// func(newValue float64, oldValue float64)
|
||||||
// `func()`.
|
// func(newValue float64)
|
||||||
NumberChangedEvent = "number-changed"
|
// func()
|
||||||
|
NumberChangedEvent PropertyName = "number-changed"
|
||||||
|
|
||||||
// NumberPickerType is the constant for "number-picker-type" property tag.
|
// NumberPickerType is the constant for "number-picker-type" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Sets the visual representation.
|
// Sets the visual representation.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NumberEditor`) or "editor" - Displayed as an editor.
|
// - 0 (NumberEditor) or "editor" - Displayed as an editor.
|
||||||
// `1`(`NumberSlider`) or "slider" - Displayed as a slider.
|
// - 1 (NumberSlider) or "slider" - Displayed as a slider.
|
||||||
NumberPickerType = "number-picker-type"
|
NumberPickerType PropertyName = "number-picker-type"
|
||||||
|
|
||||||
// NumberPickerMin is the constant for "number-picker-min" property tag.
|
// NumberPickerMin is the constant for "number-picker-min" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Set the minimum value. The default value is 0.
|
// Set the minimum value. The default value is 0.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
NumberPickerMin = "number-picker-min"
|
NumberPickerMin PropertyName = "number-picker-min"
|
||||||
|
|
||||||
// NumberPickerMax is the constant for "number-picker-max" property tag.
|
// NumberPickerMax is the constant for "number-picker-max" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Set the maximum value. The default value is 1.
|
// Set the maximum value. The default value is 1.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
NumberPickerMax = "number-picker-max"
|
NumberPickerMax PropertyName = "number-picker-max"
|
||||||
|
|
||||||
// NumberPickerStep is the constant for "number-picker-step" property tag.
|
// NumberPickerStep is the constant for "number-picker-step" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Set the value change step.
|
// Set the value change step.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
NumberPickerStep = "number-picker-step"
|
NumberPickerStep PropertyName = "number-picker-step"
|
||||||
|
|
||||||
// NumberPickerValue is the constant for "number-picker-value" property tag.
|
// NumberPickerValue is the constant for "number-picker-value" property tag.
|
||||||
//
|
//
|
||||||
// Used by `NumberPicker`.
|
// Used by NumberPicker.
|
||||||
// Current value. The default value is 0.
|
// Current value. The default value is 0.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
NumberPickerValue = "number-picker-value"
|
NumberPickerValue PropertyName = "number-picker-value"
|
||||||
|
|
||||||
|
// NumberPickerValue is the constant for "number-picker-value" property tag.
|
||||||
|
//
|
||||||
|
// Used by NumberPicker.
|
||||||
|
// Precision of displaying fractional part in editor. The default value is 0 (not used).
|
||||||
|
//
|
||||||
|
// Supported types: int, int8...int64, uint, uint8...uint64, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
NumberPickerPrecision PropertyName = "number-picker-precision"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants which describe values of the "number-picker-type" property of a [NumberPicker]
|
// Constants which describe values of the "number-picker-type" property of a [NumberPicker]
|
||||||
|
|
@ -97,8 +110,6 @@ type NumberPicker interface {
|
||||||
|
|
||||||
type numberPickerData struct {
|
type numberPickerData struct {
|
||||||
viewData
|
viewData
|
||||||
dataList
|
|
||||||
numberChangedListeners []func(NumberPicker, float64, float64)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNumberPicker create new NumberPicker object and return it
|
// NewNumberPicker create new NumberPicker object and return it
|
||||||
|
|
@ -110,165 +121,102 @@ func NewNumberPicker(session Session, params Params) NumberPicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNumberPicker(session Session) View {
|
func newNumberPicker(session Session) View {
|
||||||
return NewNumberPicker(session, nil)
|
return new(numberPickerData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) init(session Session) {
|
func (picker *numberPickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "NumberPicker"
|
picker.tag = "NumberPicker"
|
||||||
picker.hasHtmlDisabled = true
|
picker.hasHtmlDisabled = true
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
picker.normalize = normalizeNumberPickerTag
|
||||||
picker.dataListInit()
|
picker.set = picker.setFunc
|
||||||
}
|
picker.changed = picker.propertyChanged
|
||||||
|
|
||||||
func (picker *numberPickerData) String() string {
|
|
||||||
return getViewString(picker, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) Focusable() bool {
|
func (picker *numberPickerData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) normalizeTag(tag string) string {
|
func normalizeNumberPickerTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case Type, Min, Max, Step, Value:
|
case Type, Min, Max, Step, Value, "precision":
|
||||||
return "number-picker-" + tag
|
return "number-picker-" + tag
|
||||||
}
|
}
|
||||||
|
|
||||||
return picker.normalizeDataListTag(tag)
|
return normalizeDataListTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) Remove(tag string) {
|
func (picker *numberPickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
picker.remove(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *numberPickerData) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case NumberChangedEvent:
|
case NumberChangedEvent:
|
||||||
if len(picker.numberChangedListeners) > 0 {
|
return setTwoArgEventListener[NumberPicker, float64](picker, tag, value)
|
||||||
picker.numberChangedListeners = []func(NumberPicker, float64, float64){}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPickerValue:
|
case NumberPickerValue:
|
||||||
oldValue := GetNumberPickerValue(picker)
|
picker.setRaw("old-number", GetNumberPickerValue(picker))
|
||||||
picker.viewData.remove(tag)
|
|
||||||
if oldValue != 0 {
|
|
||||||
if picker.created {
|
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), 0)
|
|
||||||
}
|
|
||||||
for _, listener := range picker.numberChangedListeners {
|
|
||||||
listener(picker, 0, oldValue)
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
if len(picker.dataList.dataList) > 0 {
|
|
||||||
picker.setDataList(picker, []string{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
picker.viewData.remove(tag)
|
|
||||||
picker.propertyChanged(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *numberPickerData) Set(tag string, value any) bool {
|
|
||||||
return picker.set(picker.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *numberPickerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
picker.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case NumberChangedEvent:
|
|
||||||
listeners, ok := valueToEventWithOldListeners[NumberPicker, float64](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(NumberPicker, float64, float64){}
|
|
||||||
}
|
|
||||||
picker.numberChangedListeners = listeners
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case NumberPickerValue:
|
|
||||||
oldValue := GetNumberPickerValue(picker)
|
|
||||||
min, max := GetNumberPickerMinMax(picker)
|
min, max := GetNumberPickerMinMax(picker)
|
||||||
if picker.setFloatProperty(NumberPickerValue, value, min, max) {
|
|
||||||
if f, ok := floatProperty(picker, NumberPickerValue, picker.Session(), min); ok && f != oldValue {
|
return setFloatProperty(picker, NumberPickerValue, value, min, max)
|
||||||
newValue, _ := floatTextProperty(picker, NumberPickerValue, picker.Session(), min)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), newValue)
|
|
||||||
}
|
|
||||||
for _, listener := range picker.numberChangedListeners {
|
|
||||||
listener(picker, f, oldValue)
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataList:
|
case DataList:
|
||||||
return picker.setDataList(picker, value, picker.created)
|
return setDataList(picker, value, "")
|
||||||
|
|
||||||
default:
|
|
||||||
if picker.viewData.set(tag, value) {
|
|
||||||
picker.propertyChanged(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
return picker.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) propertyChanged(tag string) {
|
func (picker *numberPickerData) numberFormat() string {
|
||||||
if picker.created {
|
if precission := GetNumberPickerPrecision(picker); precission > 0 {
|
||||||
switch tag {
|
return fmt.Sprintf("%%.%df", precission)
|
||||||
case NumberPickerType:
|
|
||||||
if GetNumberPickerType(picker) == NumberSlider {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), "type", "range")
|
|
||||||
} else {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), "type", "number")
|
|
||||||
}
|
|
||||||
|
|
||||||
case NumberPickerMin:
|
|
||||||
min, _ := GetNumberPickerMinMax(picker)
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Min, strconv.FormatFloat(min, 'f', -1, 32))
|
|
||||||
|
|
||||||
case NumberPickerMax:
|
|
||||||
_, max := GetNumberPickerMinMax(picker)
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Max, strconv.FormatFloat(max, 'f', -1, 32))
|
|
||||||
|
|
||||||
case NumberPickerStep:
|
|
||||||
if step := GetNumberPickerStep(picker); step > 0 {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Step, strconv.FormatFloat(step, 'f', -1, 32))
|
|
||||||
} else {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Step, "any")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return "%g"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) Get(tag string) any {
|
func (picker *numberPickerData) propertyChanged(tag PropertyName) {
|
||||||
return picker.get(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *numberPickerData) get(tag string) any {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case NumberChangedEvent:
|
case NumberPickerType:
|
||||||
return picker.numberChangedListeners
|
if GetNumberPickerType(picker) == NumberSlider {
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "type", "range")
|
||||||
|
} else {
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "type", "number")
|
||||||
|
}
|
||||||
|
|
||||||
case DataList:
|
case NumberPickerMin:
|
||||||
return picker.dataList.dataList
|
min, _ := GetNumberPickerMinMax(picker)
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "min", fmt.Sprintf(picker.numberFormat(), min))
|
||||||
|
|
||||||
|
case NumberPickerMax:
|
||||||
|
_, max := GetNumberPickerMinMax(picker)
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "max", fmt.Sprintf(picker.numberFormat(), max))
|
||||||
|
|
||||||
|
case NumberPickerStep:
|
||||||
|
if step := GetNumberPickerStep(picker); step > 0 {
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "step", fmt.Sprintf(picker.numberFormat(), step))
|
||||||
|
} else {
|
||||||
|
picker.Session().updateProperty(picker.htmlID(), "step", "any")
|
||||||
|
}
|
||||||
|
|
||||||
|
case NumberPickerValue:
|
||||||
|
value := GetNumberPickerValue(picker)
|
||||||
|
format := picker.numberFormat()
|
||||||
|
picker.Session().callFunc("setInputValue", picker.htmlID(), fmt.Sprintf(format, value))
|
||||||
|
|
||||||
|
if listeners := GetNumberChangedListeners(picker); len(listeners) > 0 {
|
||||||
|
old := 0.0
|
||||||
|
if val := picker.getRaw("old-number"); val != nil {
|
||||||
|
if n, ok := val.(float64); ok {
|
||||||
|
old = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if old != value {
|
||||||
|
for _, listener := range listeners {
|
||||||
|
listener(picker, value, old)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return picker.viewData.get(tag)
|
picker.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,7 +225,10 @@ func (picker *numberPickerData) htmlTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (picker *numberPickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
picker.dataListHtmlSubviews(self, buffer)
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
||||||
|
text, _ = session.resolveConstants(text)
|
||||||
|
return text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
|
|
@ -289,38 +240,39 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde
|
||||||
buffer.WriteString(` type="number"`)
|
buffer.WriteString(` type="number"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format := picker.numberFormat()
|
||||||
min, max := GetNumberPickerMinMax(picker)
|
min, max := GetNumberPickerMinMax(picker)
|
||||||
if min != math.Inf(-1) {
|
if min != math.Inf(-1) {
|
||||||
buffer.WriteString(` min="`)
|
buffer.WriteString(` min="`)
|
||||||
buffer.WriteString(strconv.FormatFloat(min, 'f', -1, 64))
|
buffer.WriteString(fmt.Sprintf(format, min))
|
||||||
buffer.WriteByte('"')
|
buffer.WriteByte('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
if max != math.Inf(1) {
|
if max != math.Inf(1) {
|
||||||
buffer.WriteString(` max="`)
|
buffer.WriteString(` max="`)
|
||||||
buffer.WriteString(strconv.FormatFloat(max, 'f', -1, 64))
|
buffer.WriteString(fmt.Sprintf(format, max))
|
||||||
buffer.WriteByte('"')
|
buffer.WriteByte('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
step := GetNumberPickerStep(picker)
|
step := GetNumberPickerStep(picker)
|
||||||
if step != 0 {
|
if step != 0 {
|
||||||
buffer.WriteString(` step="`)
|
buffer.WriteString(` step="`)
|
||||||
buffer.WriteString(strconv.FormatFloat(step, 'f', -1, 64))
|
buffer.WriteString(fmt.Sprintf(format, step))
|
||||||
buffer.WriteByte('"')
|
buffer.WriteByte('"')
|
||||||
} else {
|
} else {
|
||||||
buffer.WriteString(` step="any"`)
|
buffer.WriteString(` step="any"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.WriteString(` value="`)
|
buffer.WriteString(` value="`)
|
||||||
buffer.WriteString(strconv.FormatFloat(GetNumberPickerValue(picker), 'f', -1, 64))
|
buffer.WriteString(fmt.Sprintf(format, GetNumberPickerValue(picker)))
|
||||||
buffer.WriteByte('"')
|
buffer.WriteByte('"')
|
||||||
|
|
||||||
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
||||||
|
|
||||||
picker.dataListHtmlProperties(picker, buffer)
|
dataListHtmlProperties(picker, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *numberPickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
|
|
@ -328,9 +280,12 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da
|
||||||
oldValue := GetNumberPickerValue(picker)
|
oldValue := GetNumberPickerValue(picker)
|
||||||
picker.properties[NumberPickerValue] = text
|
picker.properties[NumberPickerValue] = text
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.numberChangedListeners {
|
for _, listener := range GetNumberChangedListeners(picker) {
|
||||||
listener(picker, value, oldValue)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
|
if listener, ok := picker.changeListener[NumberPickerValue]; ok {
|
||||||
|
listener(picker, NumberPickerValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,12 +306,8 @@ func GetNumberPickerType(view View, subviewID ...string) int {
|
||||||
// GetNumberPickerMinMax returns the min and max value of NumberPicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
|
func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
|
||||||
var pickerType int
|
view = getSubview(view, subviewID)
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
pickerType := GetNumberPickerType(view)
|
||||||
pickerType = GetNumberPickerType(view, subviewID[0])
|
|
||||||
} else {
|
|
||||||
pickerType = GetNumberPickerType(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
var defMin, defMax float64
|
var defMin, defMax float64
|
||||||
if pickerType == NumberSlider {
|
if pickerType == NumberSlider {
|
||||||
|
|
@ -367,8 +318,8 @@ func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
|
||||||
defMax = math.Inf(1)
|
defMax = math.Inf(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
min := floatStyledProperty(view, subviewID, NumberPickerMin, defMin)
|
min := floatStyledProperty(view, nil, NumberPickerMin, defMin)
|
||||||
max := floatStyledProperty(view, subviewID, NumberPickerMax, defMax)
|
max := floatStyledProperty(view, nil, NumberPickerMax, defMax)
|
||||||
|
|
||||||
if min > max {
|
if min > max {
|
||||||
return max, min
|
return max, min
|
||||||
|
|
@ -379,14 +330,10 @@ func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
|
||||||
// GetNumberPickerStep returns the value changing step of NumberPicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetNumberPickerStep(view View, subviewID ...string) float64 {
|
func GetNumberPickerStep(view View, subviewID ...string) float64 {
|
||||||
var max float64
|
view = getSubview(view, subviewID)
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
_, max := GetNumberPickerMinMax(view)
|
||||||
_, max = GetNumberPickerMinMax(view, subviewID[0])
|
|
||||||
} else {
|
|
||||||
_, max = GetNumberPickerMinMax(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := floatStyledProperty(view, subviewID, NumberPickerStep, 0)
|
result := floatStyledProperty(view, nil, NumberPickerStep, 0)
|
||||||
if result > max {
|
if result > max {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
|
|
@ -396,20 +343,20 @@ func GetNumberPickerStep(view View, subviewID ...string) float64 {
|
||||||
// GetNumberPickerValue returns the value of NumberPicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetNumberPickerValue(view View, subviewID ...string) float64 {
|
func GetNumberPickerValue(view View, subviewID ...string) float64 {
|
||||||
var min float64
|
view = getSubview(view, subviewID)
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
min, _ := GetNumberPickerMinMax(view)
|
||||||
min, _ = GetNumberPickerMinMax(view, subviewID[0])
|
return floatStyledProperty(view, nil, NumberPickerValue, min)
|
||||||
} else {
|
|
||||||
min, _ = GetNumberPickerMinMax(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := floatStyledProperty(view, subviewID, NumberPickerValue, min)
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
|
||||||
return getEventWithOldListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
return getTwoArgEventListeners[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.
|
||||||
|
func GetNumberPickerPrecision(view View, subviewID ...string) int {
|
||||||
|
return intStyledProperty(view, subviewID, NumberPickerPrecision, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
outline.go
73
outline.go
|
|
@ -16,33 +16,39 @@ type OutlineProperty interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type outlinePropertyData struct {
|
type outlinePropertyData struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutlineProperty creates the new OutlineProperty.
|
// NewOutlineProperty creates the new OutlineProperty.
|
||||||
|
//
|
||||||
// The following properties can be used:
|
// The following properties can be used:
|
||||||
//
|
// - "color" (ColorTag) - Determines the line color (Color);
|
||||||
// "color" (ColorTag). Determines the line color (Color);
|
// - "width" (Width) - Determines the line thickness (SizeUnit).
|
||||||
//
|
|
||||||
// "width" (Width). Determines the line thickness (SizeUnit).
|
|
||||||
func NewOutlineProperty(params Params) OutlineProperty {
|
func NewOutlineProperty(params Params) OutlineProperty {
|
||||||
outline := new(outlinePropertyData)
|
outline := new(outlinePropertyData)
|
||||||
outline.properties = map[string]any{}
|
outline.init()
|
||||||
for tag, value := range params {
|
for tag, value := range params {
|
||||||
outline.Set(tag, value)
|
outline.Set(tag, value)
|
||||||
}
|
}
|
||||||
return outline
|
return outline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (outline *outlinePropertyData) init() {
|
||||||
|
outline.propertyList.init()
|
||||||
|
outline.normalize = normalizeOutlineTag
|
||||||
|
outline.set = outlineSet
|
||||||
|
outline.supportedProperties = []PropertyName{Style, Width, ColorTag}
|
||||||
|
}
|
||||||
|
|
||||||
func (outline *outlinePropertyData) writeString(buffer *strings.Builder, indent string) {
|
func (outline *outlinePropertyData) writeString(buffer *strings.Builder, indent string) {
|
||||||
buffer.WriteString("_{ ")
|
buffer.WriteString("_{ ")
|
||||||
comma := false
|
comma := false
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if value, ok := outline.properties[tag]; ok {
|
if value, ok := outline.properties[tag]; ok {
|
||||||
if comma {
|
if comma {
|
||||||
buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, BorderStyle, value, indent)
|
writePropertyValue(buffer, BorderStyle, value, indent)
|
||||||
comma = true
|
comma = true
|
||||||
|
|
@ -56,46 +62,33 @@ func (outline *outlinePropertyData) String() string {
|
||||||
return runStringWriter(outline)
|
return runStringWriter(outline)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (outline *outlinePropertyData) normalizeTag(tag string) string {
|
func normalizeOutlineTag(tag PropertyName) PropertyName {
|
||||||
return strings.TrimPrefix(strings.ToLower(tag), "outline-")
|
tag = defaultNormalize(tag)
|
||||||
|
return PropertyName(strings.TrimPrefix(string(tag), "outline-"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (outline *outlinePropertyData) Remove(tag string) {
|
func outlineSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
delete(outline.properties, outline.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (outline *outlinePropertyData) Set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
outline.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = outline.normalizeTag(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Style:
|
case Style:
|
||||||
return outline.setEnumProperty(Style, value, enumProperties[BorderStyle].values)
|
return setEnumProperty(properties, Style, value, enumProperties[BorderStyle].values)
|
||||||
|
|
||||||
case Width:
|
case Width:
|
||||||
if width, ok := value.(SizeUnit); ok {
|
if width, ok := value.(SizeUnit); ok {
|
||||||
switch width.Type {
|
switch width.Type {
|
||||||
case SizeInFraction, SizeInPercent:
|
case SizeInFraction, SizeInPercent:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return outline.setSizeProperty(Width, value)
|
return setSizeProperty(properties, Width, value)
|
||||||
|
|
||||||
case ColorTag:
|
case ColorTag:
|
||||||
return outline.setColorProperty(ColorTag, value)
|
return setColorProperty(properties, ColorTag, value)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ErrorLogF(`"%s" property is not compatible with the OutlineProperty`, tag)
|
ErrorLogF(`"%s" property is not compatible with the OutlineProperty`, tag)
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func (outline *outlinePropertyData) Get(tag string) any {
|
|
||||||
return outline.propertyList.Get(outline.normalizeTag(tag))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (outline *outlinePropertyData) ViewOutline(session Session) ViewOutline {
|
func (outline *outlinePropertyData) ViewOutline(session Session) ViewOutline {
|
||||||
|
|
@ -132,7 +125,7 @@ func (outline ViewOutline) cssString(session Session) string {
|
||||||
return builder.finish()
|
return builder.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutline(properties Properties) OutlineProperty {
|
func getOutlineProperty(properties Properties) OutlineProperty {
|
||||||
if value := properties.Get(Outline); value != nil {
|
if value := properties.Get(Outline); value != nil {
|
||||||
if outline, ok := value.(OutlineProperty); ok {
|
if outline, ok := value.(OutlineProperty); ok {
|
||||||
return outline
|
return outline
|
||||||
|
|
@ -142,30 +135,30 @@ func getOutline(properties Properties) OutlineProperty {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) setOutline(value any) bool {
|
func setOutlineProperty(properties Properties, value any) []PropertyName {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case OutlineProperty:
|
case OutlineProperty:
|
||||||
style.properties[Outline] = value
|
properties.setRaw(Outline, value)
|
||||||
|
|
||||||
case ViewOutline:
|
case ViewOutline:
|
||||||
style.properties[Outline] = NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color})
|
properties.setRaw(Outline, NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}))
|
||||||
|
|
||||||
case ViewBorder:
|
case ViewBorder:
|
||||||
style.properties[Outline] = NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color})
|
properties.setRaw(Outline, NewOutlineProperty(Params{Style: value.Style, Width: value.Width, ColorTag: value.Color}))
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
outline := NewOutlineProperty(nil)
|
outline := NewOutlineProperty(nil)
|
||||||
for _, tag := range []string{Style, Width, ColorTag} {
|
for _, tag := range []PropertyName{Style, Width, ColorTag} {
|
||||||
if text, ok := value.PropertyValue(tag); ok && text != "" {
|
if text, ok := value.PropertyValue(string(tag)); ok && text != "" {
|
||||||
outline.Set(tag, text)
|
outline.Set(tag, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
style.properties[Outline] = outline
|
properties.setRaw(Outline, outline)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(Outline, value)
|
notCompatibleType(Outline, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{Outline}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
params.go
24
params.go
|
|
@ -3,15 +3,15 @@ package rui
|
||||||
import "sort"
|
import "sort"
|
||||||
|
|
||||||
// Params defines a type of a parameters list
|
// Params defines a type of a parameters list
|
||||||
type Params map[string]any
|
type Params map[PropertyName]any
|
||||||
|
|
||||||
// Get returns a value of the property with name defined by the argument. The type of return value depends
|
// Get returns a value of the property with name defined by the argument. The type of return value depends
|
||||||
// on the property. If the property is not set then nil is returned.
|
// on the property. If the property is not set then nil is returned.
|
||||||
func (params Params) Get(tag string) any {
|
func (params Params) Get(tag PropertyName) any {
|
||||||
return params.getRaw(tag)
|
return params.getRaw(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (params Params) getRaw(tag string) any {
|
func (params Params) getRaw(tag PropertyName) any {
|
||||||
if value, ok := params[tag]; ok {
|
if value, ok := params[tag]; ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -20,12 +20,12 @@ func (params Params) getRaw(tag string) any {
|
||||||
|
|
||||||
// Set sets the value (second argument) of the property with name defined by the first argument.
|
// Set sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" is returned and a description of an error is written to the log
|
// Return "true" if the value has been set, in the opposite case "false" is returned and a description of an error is written to the log
|
||||||
func (params Params) Set(tag string, value any) bool {
|
func (params Params) Set(tag PropertyName, value any) bool {
|
||||||
params.setRaw(tag, value)
|
params.setRaw(tag, value)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (params Params) setRaw(tag string, value any) {
|
func (params Params) setRaw(tag PropertyName, value any) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
params[tag] = value
|
params[tag] = value
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -34,7 +34,7 @@ func (params Params) setRaw(tag string, value any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the property with name defined by the argument from a map.
|
// Remove removes the property with name defined by the argument from a map.
|
||||||
func (params Params) Remove(tag string) {
|
func (params Params) Remove(tag PropertyName) {
|
||||||
delete(params, tag)
|
delete(params, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,11 +46,17 @@ func (params Params) Clear() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllTags returns a sorted slice of all properties.
|
// AllTags returns a sorted slice of all properties.
|
||||||
func (params Params) AllTags() []string {
|
func (params Params) AllTags() []PropertyName {
|
||||||
tags := make([]string, 0, len(params))
|
tags := make([]PropertyName, 0, len(params))
|
||||||
for t := range params {
|
for t := range params {
|
||||||
tags = append(tags, t)
|
tags = append(tags, t)
|
||||||
}
|
}
|
||||||
sort.Strings(tags)
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
|
return tags[i] < tags[j]
|
||||||
|
})
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (params Params) empty() bool {
|
||||||
|
return len(params) == 0
|
||||||
|
}
|
||||||
|
|
|
||||||
41
path.go
41
path.go
|
|
@ -11,51 +11,50 @@ type Path interface {
|
||||||
|
|
||||||
// ArcTo adds a circular arc to the current sub-path, using the given control points and radius.
|
// ArcTo adds a circular arc to the current sub-path, using the given control points and radius.
|
||||||
// The arc is automatically connected to the path's latest point with a straight line, if necessary.
|
// The arc is automatically connected to the path's latest point with a straight line, if necessary.
|
||||||
// x0, y0 - coordinates of the first control point;
|
// - x0, y0 - coordinates of the first control point;
|
||||||
// x1, y1 - coordinates of the second control point;
|
// - x1, y1 - coordinates of the second control point;
|
||||||
// radius - the arc's radius. Must be non-negative.
|
// - radius - the arc's radius. Must be non-negative.
|
||||||
ArcTo(x0, y0, x1, y1, radius float64)
|
ArcTo(x0, y0, x1, y1, radius float64)
|
||||||
|
|
||||||
// Arc adds a circular arc to the current sub-path.
|
// Arc adds a circular arc to the current sub-path.
|
||||||
// x, y - coordinates of the arc's center;
|
// - x, y - coordinates of the arc's center;
|
||||||
// radius - the arc's radius. Must be non-negative;
|
// - radius - the arc's radius. Must be non-negative;
|
||||||
// startAngle - the angle at which the arc starts, measured clockwise from the positive
|
// - startAngle - the angle at which the arc starts, measured clockwise from the positive
|
||||||
// x-axis and expressed in radians.
|
// x-axis and expressed in radians.
|
||||||
// endAngle - the angle at which the arc ends, measured clockwise from the positive
|
// - endAngle - the angle at which the arc ends, measured clockwise from the positive
|
||||||
// x-axis and expressed in radians.
|
// x-axis and expressed in radians.
|
||||||
// clockwise - if true, causes the arc to be drawn clockwise between the start and end angles,
|
// - clockwise - if true, causes the arc to be drawn clockwise between the start and end angles,
|
||||||
// otherwise - counter-clockwise
|
// otherwise - counter-clockwise
|
||||||
Arc(x, y, radius, startAngle, endAngle float64, clockwise bool)
|
Arc(x, y, radius, startAngle, endAngle float64, clockwise bool)
|
||||||
|
|
||||||
// BezierCurveTo adds a cubic Bézier curve to the current sub-path. The starting point is
|
// BezierCurveTo adds a cubic Bézier curve to the current sub-path. The starting point is
|
||||||
// the latest point in the current path.
|
// the latest point in the current path.
|
||||||
// cp0x, cp0y - coordinates of the first control point;
|
// - cp0x, cp0y - coordinates of the first control point;
|
||||||
// cp1x, cp1y - coordinates of the second control point;
|
// - cp1x, cp1y - coordinates of the second control point;
|
||||||
// x, y - coordinates of the end point.
|
// - x, y - coordinates of the end point.
|
||||||
BezierCurveTo(cp0x, cp0y, cp1x, cp1y, x, y float64)
|
BezierCurveTo(cp0x, cp0y, cp1x, cp1y, x, y float64)
|
||||||
|
|
||||||
// QuadraticCurveTo adds a quadratic Bézier curve to the current sub-path.
|
// QuadraticCurveTo adds a quadratic Bézier curve to the current sub-path.
|
||||||
// cpx, cpy - coordinates of the control point;
|
// - cpx, cpy - coordinates of the control point;
|
||||||
// x, y - coordinates of the end point.
|
// - x, y - coordinates of the end point.
|
||||||
QuadraticCurveTo(cpx, cpy, x, y float64)
|
QuadraticCurveTo(cpx, cpy, x, y float64)
|
||||||
|
|
||||||
// Ellipse adds an elliptical arc to the current sub-path
|
// Ellipse adds an elliptical arc to the current sub-path
|
||||||
// x, y - coordinates of the ellipse's center;
|
// - x, y - coordinates of the ellipse's center;
|
||||||
// radiusX - the ellipse's major-axis radius. Must be non-negative;
|
// - radiusX - the ellipse's major-axis radius. Must be non-negative;
|
||||||
// radiusY - the ellipse's minor-axis radius. Must be non-negative;
|
// - radiusY - the ellipse's minor-axis radius. Must be non-negative;
|
||||||
// rotation - the rotation of the ellipse, expressed in radians;
|
// - rotation - the rotation of the ellipse, expressed in radians;
|
||||||
// startAngle - the angle at which the ellipse starts, measured clockwise
|
// - startAngle - the angle at which the ellipse starts, measured clockwise
|
||||||
// from the positive x-axis and expressed in radians;
|
// from the positive x-axis and expressed in radians;
|
||||||
// endAngle - the angle at which the ellipse ends, measured clockwise
|
// - endAngle - the angle at which the ellipse ends, measured clockwise
|
||||||
// from the positive x-axis and expressed in radians.
|
// from the positive x-axis and expressed in radians.
|
||||||
// clockwise - if true, draws the ellipse clockwise, otherwise draws counter-clockwise
|
// - clockwise - if true, draws the ellipse clockwise, otherwise draws counter-clockwise
|
||||||
Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, clockwise bool)
|
Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, clockwise bool)
|
||||||
|
|
||||||
// Close adds a straight line from the current point to the start of the current sub-path.
|
// Close adds a straight line from the current point to the start of the current sub-path.
|
||||||
// If the shape has already been closed or has only one point, this function does nothing.
|
// If the shape has already been closed or has only one point, this function does nothing.
|
||||||
Close()
|
Close()
|
||||||
|
|
||||||
//create(session Session)
|
|
||||||
obj() any
|
obj() any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
179
pointerEvents.go
179
pointerEvents.go
|
|
@ -1,122 +1,130 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for [View] specific pointer events properties
|
// Constants for [View] specific pointer events properties
|
||||||
const (
|
const (
|
||||||
// PointerDown is the constant for "pointer-down" property tag.
|
// PointerDown is the constant for "pointer-down" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Fired when a pointer becomes active. For mouse, it is fired when the device transitions from no buttons depressed to at
|
// Fired when a pointer becomes active. For mouse, it is fired when the device transitions from no buttons depressed to at
|
||||||
// least one button depressed. For touch, it is fired when physical contact is made with the digitizer. For pen, it is
|
// least one button depressed. For touch, it is fired when physical contact is made with the digitizer. For pen, it is
|
||||||
// fired when the stylus makes physical contact with the digitizer.
|
// fired when the stylus makes physical contact with the digitizer.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerDown = "pointer-down"
|
// func()
|
||||||
|
PointerDown PropertyName = "pointer-down"
|
||||||
|
|
||||||
// PointerUp is the constant for "pointer-up" property tag.
|
// PointerUp is the constant for "pointer-up" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when a pointer is no longer active.
|
// Is fired when a pointer is no longer active.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerUp = "pointer-up"
|
// func()
|
||||||
|
PointerUp PropertyName = "pointer-up"
|
||||||
|
|
||||||
// PointerMove is the constant for "pointer-move" property tag.
|
// PointerMove is the constant for "pointer-move" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when a pointer changes coordinates.
|
// Is fired when a pointer changes coordinates.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerMove = "pointer-move"
|
// func()
|
||||||
|
PointerMove PropertyName = "pointer-move"
|
||||||
|
|
||||||
// PointerCancel is the constant for "pointer-cancel" property tag.
|
// PointerCancel is the constant for "pointer-cancel" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired if the pointer will no longer be able to generate events (for example the related device is deactivated).
|
// Is fired if the pointer will no longer be able to generate events (for example the related device is deactivated).
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerCancel = "pointer-cancel"
|
// func()
|
||||||
|
PointerCancel PropertyName = "pointer-cancel"
|
||||||
|
|
||||||
// PointerOut is the constant for "pointer-out" property tag.
|
// PointerOut is the constant for "pointer-out" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired for several reasons including: pointing device is moved out of the hit test boundaries of an element; firing
|
// Is fired for several reasons including: pointing device is moved out of the hit test boundaries of an element; firing
|
||||||
// the "pointer-up" event for a device that does not support hover (see "pointer-up"); after firing the "pointer-cancel"
|
// the "pointer-up" event for a device that does not support hover (see "pointer-up"); after firing the "pointer-cancel"
|
||||||
// event (see "pointer-cancel"); when a pen stylus leaves the hover range detectable by the digitizer.
|
// event (see "pointer-cancel"); when a pen stylus leaves the hover range detectable by the digitizer.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerOut = "pointer-out"
|
// func()
|
||||||
|
PointerOut PropertyName = "pointer-out"
|
||||||
|
|
||||||
// PointerOver is the constant for "pointer-over" property tag.
|
// PointerOver is the constant for "pointer-over" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when a pointing device is moved into an view's hit test boundaries.
|
// Is fired when a pointing device is moved into an view's hit test boundaries.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.PointerEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.PointerEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Pointer event.
|
// - event - Pointer event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.PointerEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.PointerEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
PointerOver = "pointer-over"
|
// func()
|
||||||
|
PointerOver PropertyName = "pointer-over"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PointerEvent represent a stylus events. Also inherit [MouseEvent] attributes
|
// PointerEvent represent a stylus events. Also inherit [MouseEvent] attributes
|
||||||
|
|
@ -158,57 +166,6 @@ type PointerEvent struct {
|
||||||
IsPrimary bool
|
IsPrimary bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var pointerEvents = map[string]struct{ jsEvent, jsFunc string }{
|
|
||||||
PointerDown: {jsEvent: "onpointerdown", jsFunc: "pointerDownEvent"},
|
|
||||||
PointerUp: {jsEvent: "onpointerup", jsFunc: "pointerUpEvent"},
|
|
||||||
PointerMove: {jsEvent: "onpointermove", jsFunc: "pointerMoveEvent"},
|
|
||||||
PointerCancel: {jsEvent: "onpointercancel", jsFunc: "pointerCancelEvent"},
|
|
||||||
PointerOut: {jsEvent: "onpointerout", jsFunc: "pointerOutEvent"},
|
|
||||||
PointerOver: {jsEvent: "onpointerover", jsFunc: "pointerOverEvent"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) setPointerListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[View, PointerEvent](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removePointerListener(tag)
|
|
||||||
} else if js, ok := pointerEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removePointerListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
|
||||||
if view.created {
|
|
||||||
if js, ok := pointerEvents[tag]; ok {
|
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointerEventsHtml(view View, buffer *strings.Builder) {
|
|
||||||
for tag, js := range pointerEvents {
|
|
||||||
if value := view.getRaw(tag); value != nil {
|
|
||||||
if listeners, ok := value.([]func(View, PointerEvent)); ok && len(listeners) > 0 {
|
|
||||||
buffer.WriteString(js.jsEvent)
|
|
||||||
buffer.WriteString(`="`)
|
|
||||||
buffer.WriteString(js.jsFunc)
|
|
||||||
buffer.WriteString(`(this, event)" `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (event *PointerEvent) init(data DataObject) {
|
func (event *PointerEvent) init(data DataObject) {
|
||||||
event.MouseEvent.init(data)
|
event.MouseEvent.init(data)
|
||||||
|
|
||||||
|
|
@ -225,8 +182,8 @@ func (event *PointerEvent) init(data DataObject) {
|
||||||
event.IsPrimary = dataBoolProperty(data, "isPrimary")
|
event.IsPrimary = dataBoolProperty(data, "isPrimary")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePointerEvents(view View, tag string, data DataObject) {
|
func handlePointerEvents(view View, tag PropertyName, data DataObject) {
|
||||||
listeners := getEventListeners[View, PointerEvent](view, nil, tag)
|
listeners := getOneArgEventListeners[View, PointerEvent](view, nil, tag)
|
||||||
if len(listeners) == 0 {
|
if len(listeners) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -242,35 +199,35 @@ func handlePointerEvents(view View, tag string, data DataObject) {
|
||||||
// GetPointerDownListeners returns the "pointer-down" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerDownListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerDown)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerDown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPointerUpListeners returns the "pointer-up" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerUpListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerUp)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPointerMoveListeners returns the "pointer-move" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerMoveListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerMove)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerMove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPointerCancelListeners returns the "pointer-cancel" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerCancelListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerCancel)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerCancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPointerOverListeners returns the "pointer-over" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerOverListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerOver)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerOver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPointerOutListeners returns the "pointer-out" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetPointerOutListeners(view View, subviewID ...string) []func(View, PointerEvent) {
|
||||||
return getEventListeners[View, PointerEvent](view, subviewID, PointerOut)
|
return getOneArgEventListeners[View, PointerEvent](view, subviewID, PointerOut)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
463
popup.go
463
popup.go
|
|
@ -1,6 +1,7 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -8,151 +9,208 @@ import (
|
||||||
const (
|
const (
|
||||||
// Title is the constant for "title" property tag.
|
// Title is the constant for "title" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`, `TabsLayout`.
|
// Used by Popup, TabsLayout.
|
||||||
//
|
//
|
||||||
// Usage in `Popup`:
|
// Usage in Popup:
|
||||||
// Define the title.
|
// Define the title.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
//
|
//
|
||||||
// Usage in `TabsLayout`:
|
// Usage in TabsLayout:
|
||||||
// Set the title of the tab. The property is set for the child view of `TabsLayout`.
|
// Set the title of the tab. The property is set for the child view of TabsLayout.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
Title = "title"
|
Title = "title"
|
||||||
|
|
||||||
// TitleStyle is the constant for "title-style" property tag.
|
// TitleStyle is the constant for "title-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set popup title style. Default title style is "ruiPopupTitle".
|
// Set popup title style. Default title style is "ruiPopupTitle".
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
TitleStyle = "title-style"
|
TitleStyle PropertyName = "title-style"
|
||||||
|
|
||||||
// CloseButton is the constant for "close-button" property tag.
|
// CloseButton is the constant for "close-button" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Controls whether a close button can be added to the popup. Default value is `false`.
|
// Controls whether a close button can be added to the popup. Default value is false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Close button will be added to a title bar of a window.
|
// - true, 1, "true", "yes", "on", "1" - Close button will be added to a title bar of a window.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Popup without a close button.
|
// - false, 0, "false", "no", "off", "0" - Popup without a close button.
|
||||||
CloseButton = "close-button"
|
CloseButton PropertyName = "close-button"
|
||||||
|
|
||||||
// OutsideClose is the constant for "outside-close" property tag.
|
// OutsideClose is the constant for "outside-close" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Controls whether popup can be closed by clicking outside of the window. Default value is `false`.
|
// Controls whether popup can be closed by clicking outside of the window. Default value is false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Clicking outside the popup window will automatically call the `Dismiss()` method.
|
// - true, 1, "true", "yes", "on", "1" - Clicking outside the popup window will automatically call the Dismiss() method.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Clicking outside the popup window has no effect.
|
// - false, 0, "false", "no", "off", "0" - Clicking outside the popup window has no effect.
|
||||||
OutsideClose = "outside-close"
|
OutsideClose PropertyName = "outside-close"
|
||||||
|
|
||||||
// Buttons is the constant for "buttons" property tag.
|
// Buttons is the constant for "buttons" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Buttons that will be placed at the bottom of the popup.
|
// Buttons that will be placed at the bottom of the popup.
|
||||||
//
|
//
|
||||||
// Supported types: `PopupButton`, `[]PopupButton`.
|
// Supported types: PopupButton, []PopupButton.
|
||||||
//
|
//
|
||||||
// Internal type is `[]PopupButton`, other types converted to it during assignment.
|
// Internal type is []PopupButton, other types converted to it during assignment.
|
||||||
// See `PopupButton` description for more details.
|
// See PopupButton description for more details.
|
||||||
Buttons = "buttons"
|
Buttons PropertyName = "buttons"
|
||||||
|
|
||||||
// ButtonsAlign is the constant for "buttons-align" property tag.
|
// ButtonsAlign is the constant for "buttons-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set the horizontal alignment of popup buttons.
|
// Set the horizontal alignment of popup buttons.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`LeftAlign`) or "left" - Left alignment.
|
// - 0 (LeftAlign) or "left" - Left alignment.
|
||||||
// `1`(`RightAlign`) or "right" - Right alignment.
|
// - 1 (RightAlign) or "right" - Right alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
// `3`(`StretchAlign`) or "stretch" - Width alignment.
|
// - 3 (StretchAlign) or "stretch" - Width alignment.
|
||||||
ButtonsAlign = "buttons-align"
|
ButtonsAlign PropertyName = "buttons-align"
|
||||||
|
|
||||||
// DismissEvent is the constant for "dismiss-event" property tag.
|
// DismissEvent is the constant for "dismiss-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Used to track the closing state of the `Popup`. It occurs after the `Popup` disappears from the screen.
|
// Used to track the closing state of the Popup. It occurs after the Popup disappears from the screen.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(popup rui.Popup)`.
|
//
|
||||||
|
// func(popup rui.Popup)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// popup - Interface of a popup which generated this event.
|
// popup - Interface of a popup which generated this event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func()`.
|
//
|
||||||
DismissEvent = "dismiss-event"
|
// func()
|
||||||
|
DismissEvent PropertyName = "dismiss-event"
|
||||||
|
|
||||||
// Arrow is the constant for "arrow" property tag.
|
// Arrow is the constant for "arrow" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Add an arrow to popup. Default value is "none".
|
// Add an arrow to popup. Default value is "none".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`NoneArrow`) or "none" - No arrow.
|
// - 0 (NoneArrow) or "none" - No arrow.
|
||||||
// `1`(`TopArrow`) or "top" - Arrow at the top side of the pop-up window.
|
// - 1 (TopArrow) or "top" - Arrow at the top side of the pop-up window.
|
||||||
// `2`(`RightArrow`) or "right" - Arrow on the right side of the pop-up window.
|
// - 2 (RightArrow) or "right" - Arrow on the right side of the pop-up window.
|
||||||
// `3`(`BottomArrow`) or "bottom" - Arrow at the bottom of the pop-up window.
|
// - 3 (BottomArrow) or "bottom" - Arrow at the bottom of the pop-up window.
|
||||||
// `4`(`LeftArrow`) or "left" - Arrow on the left side of the pop-up window.
|
// - 4 (LeftArrow) or "left" - Arrow on the left side of the pop-up window.
|
||||||
Arrow = "arrow"
|
Arrow PropertyName = "arrow"
|
||||||
|
|
||||||
// ArrowAlign is the constant for "arrow-align" property tag.
|
// ArrowAlign is the constant for "arrow-align" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set the horizontal alignment of the popup arrow. Default value is "center".
|
// Set the horizontal alignment of the popup arrow. Default value is "center".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`TopAlign`/`LeftAlign`) or "top" - Top/left alignment.
|
// - 0 (TopAlign/LeftAlign) or "top" - Top/left alignment.
|
||||||
// `1`(`BottomAlign`/`RightAlign`) or "bottom" - Bottom/right alignment.
|
// - 1 (BottomAlign/RightAlign) or "bottom" - Bottom/right alignment.
|
||||||
// `2`(`CenterAlign`) or "center" - Center alignment.
|
// - 2 (CenterAlign) or "center" - Center alignment.
|
||||||
ArrowAlign = "arrow-align"
|
ArrowAlign PropertyName = "arrow-align"
|
||||||
|
|
||||||
// ArrowSize is the constant for "arrow-size" property tag.
|
// ArrowSize is the constant for "arrow-size" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set the size(length) of the popup arrow. Default value is 16px defined by @ruiArrowSize constant.
|
// Set the size(length) of the popup arrow. Default value is 16px defined by @ruiArrowSize constant.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
ArrowSize = "arrow-size"
|
ArrowSize PropertyName = "arrow-size"
|
||||||
|
|
||||||
// ArrowWidth is the constant for "arrow-width" property tag.
|
// ArrowWidth is the constant for "arrow-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set the width of the popup arrow. Default value is 16px defined by @ruiArrowWidth constant.
|
// Set the width of the popup arrow. Default value is 16px defined by @ruiArrowWidth constant.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
ArrowWidth = "arrow-width"
|
ArrowWidth PropertyName = "arrow-width"
|
||||||
|
|
||||||
|
// ShowTransform is the constant for "show-transform" property tag.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Supported types: TransformProperty, string.
|
||||||
|
//
|
||||||
|
// See TransformProperty description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - TransformProperty - stored as is, no conversion performed.
|
||||||
|
// - string - string representation of Transform interface. Example:
|
||||||
|
//
|
||||||
|
// "_{ translate-x = 10px, scale-y = 1.1}"
|
||||||
|
ShowTransform = "show-transform"
|
||||||
|
|
||||||
|
// ShowDuration is the constant for "show-duration" property tag.
|
||||||
|
//
|
||||||
|
// Used by Popup.
|
||||||
|
// Sets the length of time in seconds that a Popup show/hide animation takes to complete.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
ShowDuration = "show-duration"
|
||||||
|
|
||||||
|
// ShowTiming is the constant for "show-timing" property tag.
|
||||||
|
//
|
||||||
|
// Used by Popup.
|
||||||
|
// Set how a Popup show/hide animation progresses through the duration of each cycle.
|
||||||
|
//
|
||||||
|
// Supported types: string.
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// - "ease" (EaseTiming) - Speed increases towards the middle and slows down at the end.
|
||||||
|
// - "ease-in" (EaseInTiming) - Speed is slow at first, but increases in the end.
|
||||||
|
// - "ease-out" (EaseOutTiming) - Speed is fast at first, but decreases in the end.
|
||||||
|
// - "ease-in-out" (EaseInOutTiming) - Speed is slow at first, but quickly increases and at the end it decreases again.
|
||||||
|
// - "linear" (LinearTiming) - Constant speed.
|
||||||
|
// - "step(n)" (StepTiming(n int) function) - Timing function along stepCount stops along the transition, displaying each stop for equal lengths of time.
|
||||||
|
// - "cubic-bezier(x1, y1, x2, y2)" (CubicBezierTiming(x1, y1, x2, y2 float64) function) - Cubic-Bezier curve timing function. x1 and x2 must be in the range [0, 1].
|
||||||
|
ShowTiming = "show-timing"
|
||||||
|
|
||||||
|
// ShowOpacity is the constant for "show-opacity" property tag.
|
||||||
|
//
|
||||||
|
// Used by Popup.
|
||||||
|
// In [1..0] range sets the start opacity of Popup show animation (the finish animation opacity is 1).
|
||||||
|
// Opacity is the degree to which content behind the view is hidden, and is the opposite of transparency.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
ShowOpacity = "show-opacity"
|
||||||
|
|
||||||
// ArrowOffset is the constant for "arrow-offset" property tag.
|
// ArrowOffset is the constant for "arrow-offset" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Popup`.
|
// Used by Popup.
|
||||||
// Set the offset of the popup arrow.
|
// Set the offset of the popup arrow.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
ArrowOffset = "arrow-offset"
|
ArrowOffset PropertyName = "arrow-offset"
|
||||||
|
|
||||||
// NoneArrow is value of the popup "arrow" property: no arrow
|
// NoneArrow is value of the popup "arrow" property: no arrow
|
||||||
NoneArrow = 0
|
NoneArrow = 0
|
||||||
|
|
@ -219,14 +277,21 @@ type Popup interface {
|
||||||
html(buffer *strings.Builder)
|
html(buffer *strings.Builder)
|
||||||
viewByHTMLID(id string) View
|
viewByHTMLID(id string) View
|
||||||
keyEvent(event KeyEvent) bool
|
keyEvent(event KeyEvent) bool
|
||||||
|
showAnimation()
|
||||||
|
dissmissAnimation(listener func(PropertyName)) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type popupData struct {
|
type popupData struct {
|
||||||
layerView View
|
layerView GridLayout
|
||||||
view View
|
popupView GridLayout
|
||||||
|
contentView View
|
||||||
buttons []PopupButton
|
buttons []PopupButton
|
||||||
cancelable bool
|
cancelable bool
|
||||||
dismissListener []func(Popup)
|
dismissListener []func(Popup)
|
||||||
|
showTransform TransformProperty
|
||||||
|
showOpacity float64
|
||||||
|
showDuration float64
|
||||||
|
showTiming string
|
||||||
}
|
}
|
||||||
|
|
||||||
type popupManager struct {
|
type popupManager struct {
|
||||||
|
|
@ -304,7 +369,7 @@ func (arrow *popupArrow) createView(popupView View) View {
|
||||||
|
|
||||||
params := Params{BackgroundColor: GetBackgroundColor(popupView)}
|
params := Params{BackgroundColor: GetBackgroundColor(popupView)}
|
||||||
|
|
||||||
if shadow := GetViewShadows(popupView); shadow != nil {
|
if shadow := GetShadowPropertys(popupView); shadow != nil {
|
||||||
params[Shadow] = shadow
|
params[Shadow] = shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,28 +381,28 @@ func (arrow *popupArrow) createView(popupView View) View {
|
||||||
case TopArrow:
|
case TopArrow:
|
||||||
params[Row] = 0
|
params[Row] = 0
|
||||||
params[Column] = 1
|
params[Column] = 1
|
||||||
params[Clip] = PolygonClip([]any{"0%", "100%", "50%", "0%", "100%", "100%"})
|
params[Clip] = NewPolygonClip([]any{"0%", "100%", "50%", "0%", "100%", "100%"})
|
||||||
params[Width] = arrow.width
|
params[Width] = arrow.width
|
||||||
params[Height] = arrow.size
|
params[Height] = arrow.size
|
||||||
|
|
||||||
case RightArrow:
|
case RightArrow:
|
||||||
params[Row] = 1
|
params[Row] = 1
|
||||||
params[Column] = 0
|
params[Column] = 0
|
||||||
params[Clip] = PolygonClip([]any{"0%", "0%", "100%", "50%", "0%", "100%"})
|
params[Clip] = NewPolygonClip([]any{"0%", "0%", "100%", "50%", "0%", "100%"})
|
||||||
params[Width] = arrow.size
|
params[Width] = arrow.size
|
||||||
params[Height] = arrow.width
|
params[Height] = arrow.width
|
||||||
|
|
||||||
case BottomArrow:
|
case BottomArrow:
|
||||||
params[Row] = 0
|
params[Row] = 0
|
||||||
params[Column] = 1
|
params[Column] = 1
|
||||||
params[Clip] = PolygonClip([]any{"0%", "0%", "50%", "100%", "100%", "0%"})
|
params[Clip] = NewPolygonClip([]any{"0%", "0%", "50%", "100%", "100%", "0%"})
|
||||||
params[Width] = arrow.width
|
params[Width] = arrow.width
|
||||||
params[Height] = arrow.size
|
params[Height] = arrow.size
|
||||||
|
|
||||||
case LeftArrow:
|
case LeftArrow:
|
||||||
params[Row] = 1
|
params[Row] = 1
|
||||||
params[Column] = 0
|
params[Column] = 0
|
||||||
params[Clip] = PolygonClip([]any{"100%", "0%", "0%", "50%", "100%", "100%"})
|
params[Clip] = NewPolygonClip([]any{"100%", "0%", "0%", "50%", "100%", "100%"})
|
||||||
params[Width] = arrow.size
|
params[Width] = arrow.size
|
||||||
params[Height] = arrow.width
|
params[Height] = arrow.width
|
||||||
}
|
}
|
||||||
|
|
@ -397,39 +462,15 @@ func (arrow *popupArrow) createView(popupView View) View {
|
||||||
return NewGridLayout(session, params)
|
return NewGridLayout(session, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) init(view View, popupParams Params) {
|
func (popup *popupData) layerCellWidth(arrowLocation int, popupParams Params, session Session) []SizeUnit {
|
||||||
popup.view = view
|
|
||||||
popup.cancelable = false
|
|
||||||
session := view.Session()
|
|
||||||
|
|
||||||
columnCount := 3
|
var columnCount int
|
||||||
rowCount := 3
|
switch arrowLocation {
|
||||||
popupRow := 1
|
case LeftArrow, RightArrow:
|
||||||
popupColumn := 1
|
|
||||||
arrow := popupArrow{
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
align: CenterAlign,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch arrow.location, _ = enumProperty(popupParams, Arrow, session, NoneArrow); arrow.location {
|
|
||||||
case TopArrow:
|
|
||||||
rowCount = 4
|
|
||||||
popupRow = 2
|
|
||||||
|
|
||||||
case BottomArrow:
|
|
||||||
rowCount = 4
|
|
||||||
arrow.row = 2
|
|
||||||
|
|
||||||
case LeftArrow:
|
|
||||||
columnCount = 4
|
columnCount = 4
|
||||||
popupColumn = 2
|
|
||||||
|
|
||||||
case RightArrow:
|
|
||||||
columnCount = 4
|
|
||||||
arrow.column = 2
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
columnCount = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
cellWidth := make([]SizeUnit, columnCount)
|
cellWidth := make([]SizeUnit, columnCount)
|
||||||
|
|
@ -444,6 +485,19 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
cellWidth[0] = Fr(1)
|
cellWidth[0] = Fr(1)
|
||||||
cellWidth[columnCount-1] = Fr(1)
|
cellWidth[columnCount-1] = Fr(1)
|
||||||
}
|
}
|
||||||
|
return cellWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (popup *popupData) layerCellHeight(arrowLocation int, popupParams Params, session Session) []SizeUnit {
|
||||||
|
|
||||||
|
var rowCount int
|
||||||
|
switch arrowLocation {
|
||||||
|
case TopArrow, BottomArrow:
|
||||||
|
rowCount = 4
|
||||||
|
|
||||||
|
default:
|
||||||
|
rowCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
cellHeight := make([]SizeUnit, rowCount)
|
cellHeight := make([]SizeUnit, rowCount)
|
||||||
switch vAlign, _ := enumProperty(popupParams, VerticalAlign, session, CenterAlign); vAlign {
|
switch vAlign, _ := enumProperty(popupParams, VerticalAlign, session, CenterAlign); vAlign {
|
||||||
|
|
@ -458,16 +512,47 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
cellHeight[rowCount-1] = Fr(1)
|
cellHeight[rowCount-1] = Fr(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cellHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (popup *popupData) init(view View, popupParams Params) {
|
||||||
|
popup.contentView = view
|
||||||
|
popup.cancelable = false
|
||||||
|
session := view.Session()
|
||||||
|
|
||||||
|
popupRow := 1
|
||||||
|
popupColumn := 1
|
||||||
|
arrow := popupArrow{
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
align: CenterAlign,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch arrow.location, _ = enumProperty(popupParams, Arrow, session, NoneArrow); arrow.location {
|
||||||
|
case TopArrow:
|
||||||
|
popupRow = 2
|
||||||
|
|
||||||
|
case BottomArrow:
|
||||||
|
arrow.row = 2
|
||||||
|
|
||||||
|
case LeftArrow:
|
||||||
|
popupColumn = 2
|
||||||
|
|
||||||
|
case RightArrow:
|
||||||
|
arrow.column = 2
|
||||||
|
}
|
||||||
|
|
||||||
layerParams := Params{
|
layerParams := Params{
|
||||||
Style: "ruiPopupLayer",
|
Style: "ruiPopupLayer",
|
||||||
MaxWidth: Percent(100),
|
MaxWidth: Percent(100),
|
||||||
MaxHeight: Percent(100),
|
MaxHeight: Percent(100),
|
||||||
CellWidth: cellWidth,
|
CellWidth: popup.layerCellWidth(arrow.location, popupParams, session),
|
||||||
CellHeight: cellHeight,
|
CellHeight: popup.layerCellHeight(arrow.location, popupParams, session),
|
||||||
}
|
}
|
||||||
|
|
||||||
params := Params{
|
params := Params{
|
||||||
Style: "ruiPopup",
|
Style: "ruiPopup",
|
||||||
|
ID: "ruiPopup",
|
||||||
Row: popupRow,
|
Row: popupRow,
|
||||||
Column: popupColumn,
|
Column: popupColumn,
|
||||||
MaxWidth: Percent(100),
|
MaxWidth: Percent(100),
|
||||||
|
|
@ -475,7 +560,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
CellVerticalAlign: StretchAlign,
|
CellVerticalAlign: StretchAlign,
|
||||||
CellHorizontalAlign: StretchAlign,
|
CellHorizontalAlign: StretchAlign,
|
||||||
ClickEvent: func(View) {},
|
ClickEvent: func(View) {},
|
||||||
Shadow: NewShadowWithParams(Params{
|
Shadow: NewShadowProperty(Params{
|
||||||
SpreadRadius: Px(4),
|
SpreadRadius: Px(4),
|
||||||
Blur: Px(16),
|
Blur: Px(16),
|
||||||
ColorTag: "@ruiPopupShadow",
|
ColorTag: "@ruiPopupShadow",
|
||||||
|
|
@ -483,10 +568,14 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var closeButton View = nil
|
var closeButton View = nil
|
||||||
outsideClose := false
|
|
||||||
buttons := []PopupButton{}
|
|
||||||
titleStyle := "ruiPopupTitle"
|
|
||||||
var title View = nil
|
var title View = nil
|
||||||
|
outsideClose := false
|
||||||
|
popup.buttons = []PopupButton{}
|
||||||
|
titleStyle := "ruiPopupTitle"
|
||||||
|
|
||||||
|
popup.showOpacity = 1.0
|
||||||
|
popup.showDuration = 1.0
|
||||||
|
popup.showTiming = "easy"
|
||||||
|
|
||||||
for tag, value := range popupParams {
|
for tag, value := range popupParams {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
|
|
@ -532,10 +621,10 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
case Buttons:
|
case Buttons:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case PopupButton:
|
case PopupButton:
|
||||||
buttons = []PopupButton{value}
|
popup.buttons = []PopupButton{value}
|
||||||
|
|
||||||
case []PopupButton:
|
case []PopupButton:
|
||||||
buttons = value
|
popup.buttons = value
|
||||||
}
|
}
|
||||||
|
|
||||||
case Title:
|
case Title:
|
||||||
|
|
@ -560,7 +649,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case DismissEvent:
|
case DismissEvent:
|
||||||
if listeners, ok := valueToNoParamListeners[Popup](value); ok {
|
if listeners, ok := valueToNoArgEventListeners[Popup](value); ok {
|
||||||
if listeners != nil {
|
if listeners != nil {
|
||||||
popup.dismissListener = listeners
|
popup.dismissListener = listeners
|
||||||
}
|
}
|
||||||
|
|
@ -587,13 +676,36 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
case ArrowOffset:
|
case ArrowOffset:
|
||||||
arrow.off, _ = sizeProperty(popupParams, ArrowOffset, session)
|
arrow.off, _ = sizeProperty(popupParams, ArrowOffset, session)
|
||||||
|
|
||||||
|
case ShowOpacity:
|
||||||
|
if opacity, _ := floatProperty(popupParams, ShowOpacity, session, 1); opacity >= 0 && opacity < 1 {
|
||||||
|
popup.showOpacity = opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
case ShowTransform:
|
||||||
|
if transform := valueToTransformProperty(value); transform != nil && !transform.empty() {
|
||||||
|
popup.showTransform = transform
|
||||||
|
}
|
||||||
|
|
||||||
|
case ShowDuration:
|
||||||
|
if duration, _ := floatProperty(popupParams, ShowDuration, session, 1); duration > 0 {
|
||||||
|
popup.showDuration = duration
|
||||||
|
}
|
||||||
|
|
||||||
|
case ShowTiming:
|
||||||
|
if text, ok := value.(string); ok {
|
||||||
|
text, _ = session.resolveConstants(text)
|
||||||
|
if isTimingFunctionValid(text) {
|
||||||
|
popup.showTiming = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
params[tag] = value
|
params[tag] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
popupView := NewGridLayout(view.Session(), params)
|
popup.popupView = NewGridLayout(view.Session(), params)
|
||||||
|
|
||||||
var popupCellHeight []SizeUnit
|
var popupCellHeight []SizeUnit
|
||||||
viewRow := 0
|
viewRow := 0
|
||||||
|
|
@ -605,7 +717,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
if closeButton != nil {
|
if closeButton != nil {
|
||||||
titleContent = append(titleContent, closeButton)
|
titleContent = append(titleContent, closeButton)
|
||||||
}
|
}
|
||||||
popupView.Append(NewGridLayout(session, Params{
|
popup.popupView.Append(NewGridLayout(session, Params{
|
||||||
Row: 0,
|
Row: 0,
|
||||||
Style: titleStyle,
|
Style: titleStyle,
|
||||||
CellWidth: []any{Fr(1), AutoSize()},
|
CellWidth: []any{Fr(1), AutoSize()},
|
||||||
|
|
@ -621,10 +733,9 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
view.Set(Row, viewRow)
|
view.Set(Row, viewRow)
|
||||||
popupView.Append(view)
|
popup.popupView.Append(view)
|
||||||
|
|
||||||
popup.buttons = buttons
|
if buttonCount := len(popup.buttons); buttonCount > 0 {
|
||||||
if buttonCount := len(buttons); buttonCount > 0 {
|
|
||||||
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
|
||||||
popupCellHeight = append(popupCellHeight, AutoSize())
|
popupCellHeight = append(popupCellHeight, AutoSize())
|
||||||
gap, _ := sizeConstant(session, "ruiPopupButtonGap")
|
gap, _ := sizeConstant(session, "ruiPopupButtonGap")
|
||||||
|
|
@ -641,7 +752,7 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
buttonsPanel.Set(Margin, gap)
|
buttonsPanel.Set(Margin, gap)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, button := range buttons {
|
for i, button := range popup.buttons {
|
||||||
title := button.Title
|
title := button.Title
|
||||||
if title == "" && button.Type == CancelButton {
|
if title == "" && button.Type == CancelButton {
|
||||||
title = "Cancel"
|
title = "Cancel"
|
||||||
|
|
@ -668,33 +779,82 @@ func (popup *popupData) init(view View, popupParams Params) {
|
||||||
buttonsPanel.Append(buttonView)
|
buttonsPanel.Append(buttonView)
|
||||||
}
|
}
|
||||||
|
|
||||||
popupView.Append(NewGridLayout(session, Params{
|
popup.popupView.Append(NewGridLayout(session, Params{
|
||||||
Row: viewRow + 1,
|
Row: viewRow + 1,
|
||||||
CellHorizontalAlign: buttonsAlign,
|
CellHorizontalAlign: buttonsAlign,
|
||||||
Content: buttonsPanel,
|
Content: buttonsPanel,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
popupView.Set(CellHeight, popupCellHeight)
|
popup.popupView.Set(CellHeight, popupCellHeight)
|
||||||
|
|
||||||
if arrow.location != NoneArrow {
|
if arrow.location != NoneArrow {
|
||||||
layerParams[Content] = []View{popupView, arrow.createView(popupView)}
|
layerParams[Content] = []View{popup.popupView, arrow.createView(popup.popupView)}
|
||||||
} else {
|
} else {
|
||||||
layerParams[Content] = []View{popupView}
|
layerParams[Content] = []View{popup.popupView}
|
||||||
}
|
}
|
||||||
|
|
||||||
popup.layerView = NewGridLayout(session, layerParams)
|
popup.layerView = NewGridLayout(session, layerParams)
|
||||||
|
|
||||||
|
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||||
|
animation := NewAnimationProperty(Params{
|
||||||
|
Duration: popup.showDuration,
|
||||||
|
TimingFunction: popup.showTiming,
|
||||||
|
})
|
||||||
|
if popup.showOpacity != 1 {
|
||||||
|
popup.popupView.Set(Opacity, popup.showOpacity)
|
||||||
|
popup.popupView.SetTransition(Opacity, animation)
|
||||||
|
}
|
||||||
|
if popup.showTransform != nil {
|
||||||
|
popup.popupView.Set(Transform, popup.showTransform)
|
||||||
|
popup.popupView.SetTransition(Transform, animation)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
session.updateCSSProperty("ruiPopupLayer", "transition", "")
|
||||||
|
}
|
||||||
|
|
||||||
if outsideClose {
|
if outsideClose {
|
||||||
popup.layerView.Set(ClickEvent, popup.cancel)
|
popup.layerView.Set(ClickEvent, popup.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup popupData) View() View {
|
func (popup *popupData) showAnimation() {
|
||||||
return popup.view
|
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||||
|
htmlID := popup.popupView.htmlID()
|
||||||
|
session := popup.Session()
|
||||||
|
if popup.showOpacity != 1 {
|
||||||
|
session.updateCSSProperty(htmlID, string(Opacity), "1")
|
||||||
|
}
|
||||||
|
if popup.showTransform != nil {
|
||||||
|
session.updateCSSProperty(htmlID, string(Transform), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (popup *popupData) dissmissAnimation(listener func(PropertyName)) bool {
|
||||||
|
if popup.showOpacity != 1 || popup.showTransform != nil {
|
||||||
|
session := popup.Session()
|
||||||
|
popup.popupView.Set(TransitionEndEvent, listener)
|
||||||
|
popup.popupView.Set(TransitionCancelEvent, listener)
|
||||||
|
|
||||||
|
htmlID := popup.popupView.htmlID()
|
||||||
|
if popup.showOpacity != 1 {
|
||||||
|
session.updateCSSProperty(htmlID, string(Opacity), fmt.Sprintf("%.2f", popup.showOpacity))
|
||||||
|
}
|
||||||
|
if popup.showTransform != nil {
|
||||||
|
session.updateCSSProperty(htmlID, string(Transform), popup.showTransform.transformCSS(session))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (popup *popupData) View() View {
|
||||||
|
return popup.contentView
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) Session() Session {
|
func (popup *popupData) Session() Session {
|
||||||
return popup.view.Session()
|
return popup.contentView.Session()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) cancel() {
|
func (popup *popupData) cancel() {
|
||||||
|
|
@ -709,9 +869,6 @@ func (popup *popupData) cancel() {
|
||||||
|
|
||||||
func (popup *popupData) Dismiss() {
|
func (popup *popupData) Dismiss() {
|
||||||
popup.Session().popupManager().dismissPopup(popup)
|
popup.Session().popupManager().dismissPopup(popup)
|
||||||
for _, listener := range popup.dismissListener {
|
|
||||||
listener(popup)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) Show() {
|
func (popup *popupData) Show() {
|
||||||
|
|
@ -719,8 +876,7 @@ func (popup *popupData) Show() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) html(buffer *strings.Builder) {
|
func (popup *popupData) html(buffer *strings.Builder) {
|
||||||
|
viewHTML(popup.layerView, buffer, "")
|
||||||
viewHTML(popup.layerView, buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) viewByHTMLID(id string) View {
|
func (popup *popupData) viewByHTMLID(id string) View {
|
||||||
|
|
@ -728,6 +884,8 @@ func (popup *popupData) viewByHTMLID(id string) View {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (popup *popupData) onDismiss() {
|
func (popup *popupData) onDismiss() {
|
||||||
|
popup.Session().callFunc("removeView", popup.layerView.htmlID())
|
||||||
|
|
||||||
for _, listener := range popup.dismissListener {
|
for _, listener := range popup.dismissListener {
|
||||||
listener(popup)
|
listener(popup)
|
||||||
}
|
}
|
||||||
|
|
@ -808,6 +966,7 @@ func (manager *popupManager) showPopup(popup Popup) {
|
||||||
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
|
session.updateCSSProperty("ruiTooltipLayer", "opacity", "0")
|
||||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
session.updateCSSProperty("ruiPopupLayer", "visibility", "visible")
|
||||||
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
session.updateCSSProperty("ruiRoot", "pointer-events", "none")
|
||||||
|
popup.showAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (manager *popupManager) dismissPopup(popup Popup) {
|
func (manager *popupManager) dismissPopup(popup Popup) {
|
||||||
|
|
@ -821,31 +980,37 @@ func (manager *popupManager) dismissPopup(popup Popup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session := popup.Session()
|
index := -1
|
||||||
if manager.popups[count-1] == popup {
|
for n, p := range manager.popups {
|
||||||
if count == 1 {
|
if p == popup {
|
||||||
manager.popups = []Popup{}
|
index = n
|
||||||
session.updateCSSProperty("ruiRoot", "pointer-events", "auto")
|
break
|
||||||
session.updateCSSProperty("ruiPopupLayer", "visibility", "hidden")
|
|
||||||
session.updateInnerHTML("ruiPopupLayer", "")
|
|
||||||
} else {
|
|
||||||
manager.popups = manager.popups[:count-1]
|
|
||||||
manager.updatePopupLayerInnerHTML(session)
|
|
||||||
}
|
}
|
||||||
popup.onDismiss()
|
}
|
||||||
|
|
||||||
|
if index < 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, p := range manager.popups {
|
session := popup.Session()
|
||||||
if p == popup {
|
listener := func(PropertyName) {
|
||||||
if n == 0 {
|
if index == count-1 {
|
||||||
manager.popups = manager.popups[1:]
|
if count == 1 {
|
||||||
|
manager.popups = []Popup{}
|
||||||
|
session.updateCSSProperty("ruiRoot", "pointer-events", "auto")
|
||||||
|
session.updateCSSProperty("ruiPopupLayer", "visibility", "hidden")
|
||||||
} else {
|
} else {
|
||||||
manager.popups = append(manager.popups[:n], manager.popups[n+1:]...)
|
manager.popups = manager.popups[:count-1]
|
||||||
}
|
}
|
||||||
manager.updatePopupLayerInnerHTML(session)
|
} else if index == 0 {
|
||||||
popup.onDismiss()
|
manager.popups = manager.popups[1:]
|
||||||
return
|
} else {
|
||||||
|
manager.popups = append(manager.popups[:index], manager.popups[index+1:]...)
|
||||||
}
|
}
|
||||||
|
popup.onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !popup.dissmissAnimation(listener) {
|
||||||
|
listener("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ func ShowMessage(title, text string, session Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowQuestion displays a message with the given title and text and two buttons "Yes" and "No".
|
// ShowQuestion displays a message with the given title and text and two buttons "Yes" and "No".
|
||||||
|
//
|
||||||
// When the "Yes" button is clicked, the message is closed and the onYes function is called (if it is not nil).
|
// When the "Yes" button is clicked, the message is closed and the onYes function is called (if it is not nil).
|
||||||
|
//
|
||||||
// When the "No" button is pressed, the message is closed and the onNo function is called (if it is not nil).
|
// When the "No" button is pressed, the message is closed and the onNo function is called (if it is not nil).
|
||||||
func ShowQuestion(title, text string, session Session, onYes func(), onNo func()) {
|
func ShowQuestion(title, text string, session Session, onYes func(), onNo func()) {
|
||||||
textView := NewTextView(session, Params{
|
textView := NewTextView(session, Params{
|
||||||
|
|
@ -57,6 +59,7 @@ func ShowQuestion(title, text string, session Session, onYes func(), onNo func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowCancellableQuestion displays a message with the given title and text and three buttons "Yes", "No" and "Cancel".
|
// ShowCancellableQuestion displays a message with the given title and text and three buttons "Yes", "No" and "Cancel".
|
||||||
|
//
|
||||||
// When the "Yes", "No" or "Cancel" button is pressed, the message is closed and the onYes, onNo or onCancel function
|
// When the "Yes", "No" or "Cancel" button is pressed, the message is closed and the onYes, onNo or onCancel function
|
||||||
// (if it is not nil) is called, respectively.
|
// (if it is not nil) is called, respectively.
|
||||||
func ShowCancellableQuestion(title, text string, session Session, onYes func(), onNo func(), onCancel func()) {
|
func ShowCancellableQuestion(title, text string, session Session, onYes func(), onNo func(), onCancel func()) {
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,23 @@ import (
|
||||||
const (
|
const (
|
||||||
// ProgressBarMax is the constant for "progress-max" property tag.
|
// ProgressBarMax is the constant for "progress-max" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ProgressBar`.
|
// Used by ProgressBar.
|
||||||
// Maximum value, default is 1.
|
// Maximum value, default is 1.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
ProgressBarMax = "progress-max"
|
ProgressBarMax PropertyName = "progress-max"
|
||||||
|
|
||||||
// ProgressBarValue is the constant for "progress-value" property tag.
|
// ProgressBarValue is the constant for "progress-value" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ProgressBar`.
|
// Used by ProgressBar.
|
||||||
// Current value, default is 0.
|
// Current value, default is 0.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
ProgressBarValue = "progress-value"
|
ProgressBarValue PropertyName = "progress-value"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProgressBar represents a ProgressBar view
|
// ProgressBar represents a ProgressBar view
|
||||||
|
|
@ -46,20 +46,18 @@ func NewProgressBar(session Session, params Params) ProgressBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProgressBar(session Session) View {
|
func newProgressBar(session Session) View {
|
||||||
return NewProgressBar(session, nil)
|
return new(progressBarData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (progress *progressBarData) init(session Session) {
|
func (progress *progressBarData) init(session Session) {
|
||||||
progress.viewData.init(session)
|
progress.viewData.init(session)
|
||||||
progress.tag = "ProgressBar"
|
progress.tag = "ProgressBar"
|
||||||
|
progress.normalize = normalizeProgressBarTag
|
||||||
|
progress.changed = progress.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (progress *progressBarData) String() string {
|
func normalizeProgressBarTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(progress, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (progress *progressBarData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Max, "progress-bar-max", "progressbar-max":
|
case Max, "progress-bar-max", "progressbar-max":
|
||||||
return ProgressBarMax
|
return ProgressBarMax
|
||||||
|
|
@ -70,45 +68,22 @@ func (progress *progressBarData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (progress *progressBarData) Remove(tag string) {
|
func (progress *progressBarData) propertyChanged(tag PropertyName) {
|
||||||
progress.remove(progress.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (progress *progressBarData) remove(tag string) {
|
switch tag {
|
||||||
progress.viewData.remove(tag)
|
case ProgressBarMax:
|
||||||
progress.propertyChanged(tag)
|
progress.Session().updateProperty(progress.htmlID(), "max",
|
||||||
}
|
strconv.FormatFloat(GetProgressBarMax(progress), 'f', -1, 32))
|
||||||
|
|
||||||
func (progress *progressBarData) propertyChanged(tag string) {
|
case ProgressBarValue:
|
||||||
if progress.created {
|
progress.Session().updateProperty(progress.htmlID(), "value",
|
||||||
switch tag {
|
strconv.FormatFloat(GetProgressBarValue(progress), 'f', -1, 32))
|
||||||
case ProgressBarMax:
|
|
||||||
progress.session.updateProperty(progress.htmlID(), Max,
|
|
||||||
strconv.FormatFloat(GetProgressBarMax(progress), 'f', -1, 32))
|
|
||||||
|
|
||||||
case ProgressBarValue:
|
default:
|
||||||
progress.session.updateProperty(progress.htmlID(), Value,
|
progress.viewData.propertyChanged(tag)
|
||||||
strconv.FormatFloat(GetProgressBarValue(progress), 'f', -1, 32))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (progress *progressBarData) Set(tag string, value any) bool {
|
|
||||||
return progress.set(progress.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (progress *progressBarData) set(tag string, value any) bool {
|
|
||||||
if progress.viewData.set(tag, value) {
|
|
||||||
progress.propertyChanged(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (progress *progressBarData) Get(tag string) any {
|
|
||||||
return progress.get(progress.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (progress *progressBarData) htmlTag() string {
|
func (progress *progressBarData) htmlTag() string {
|
||||||
return "progress"
|
return "progress"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
126
properties.go
126
properties.go
|
|
@ -9,71 +9,96 @@ import (
|
||||||
type Properties interface {
|
type Properties interface {
|
||||||
// Get returns a value of the property with name defined by the argument.
|
// Get returns a value of the property with name defined by the argument.
|
||||||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||||||
Get(tag string) any
|
Get(tag PropertyName) any
|
||||||
getRaw(tag string) any
|
getRaw(tag PropertyName) any
|
||||||
|
|
||||||
// Set sets the value (second argument) of the property with name defined by the first argument.
|
// Set sets the value (second argument) of the property with name defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
Set(tag string, value any) bool
|
Set(tag PropertyName, value any) bool
|
||||||
setRaw(tag string, value any)
|
setRaw(tag PropertyName, value any)
|
||||||
|
|
||||||
// Remove removes the property with name defined by the argument
|
// Remove removes the property with name defined by the argument
|
||||||
Remove(tag string)
|
Remove(tag PropertyName)
|
||||||
|
|
||||||
// Clear removes all properties
|
// Clear removes all properties
|
||||||
Clear()
|
Clear()
|
||||||
|
|
||||||
// AllTags returns an array of the set properties
|
// AllTags returns an array of the set properties
|
||||||
AllTags() []string
|
AllTags() []PropertyName
|
||||||
|
|
||||||
|
empty() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type propertyList struct {
|
type propertyList struct {
|
||||||
properties map[string]any
|
properties map[PropertyName]any
|
||||||
|
normalize func(PropertyName) PropertyName
|
||||||
|
//getFunc func(PropertyName) any
|
||||||
|
//set func(Properties, PropertyName, any) []PropertyName
|
||||||
|
//remove func(Properties, PropertyName) []PropertyName
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataProperty struct {
|
||||||
|
propertyList
|
||||||
|
supportedProperties []PropertyName
|
||||||
|
get func(Properties, PropertyName) any
|
||||||
|
set func(Properties, PropertyName, any) []PropertyName
|
||||||
|
remove func(Properties, PropertyName) []PropertyName
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultNormalize(tag PropertyName) PropertyName {
|
||||||
|
return PropertyName(strings.ToLower(strings.Trim(string(tag), " \t")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) init() {
|
func (properties *propertyList) init() {
|
||||||
properties.properties = map[string]any{}
|
properties.properties = map[PropertyName]any{}
|
||||||
|
properties.normalize = defaultNormalize
|
||||||
|
//properties.getFunc = properties.getRaw
|
||||||
|
//properties.set = propertiesSet
|
||||||
|
//properties.remove = propertiesRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) Get(tag string) any {
|
func (properties *propertyList) empty() bool {
|
||||||
return properties.getRaw(strings.ToLower(tag))
|
return len(properties.properties) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) getRaw(tag string) any {
|
func (properties *propertyList) getRaw(tag PropertyName) any {
|
||||||
if value, ok := properties.properties[tag]; ok {
|
if value, ok := properties.properties[tag]; ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setRaw(tag string, value any) {
|
func (properties *propertyList) setRaw(tag PropertyName, value any) {
|
||||||
properties.properties[tag] = value
|
if value == nil {
|
||||||
}
|
delete(properties.properties, tag)
|
||||||
|
} else {
|
||||||
func (properties *propertyList) Remove(tag string) {
|
properties.properties[tag] = value
|
||||||
delete(properties.properties, strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (properties *propertyList) remove(tag string) {
|
|
||||||
delete(properties.properties, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (properties *propertyList) Clear() {
|
|
||||||
properties.properties = map[string]any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (properties *propertyList) AllTags() []string {
|
|
||||||
tags := make([]string, 0, len(properties.properties))
|
|
||||||
for t := range properties.properties {
|
|
||||||
tags = append(tags, t)
|
|
||||||
}
|
}
|
||||||
sort.Strings(tags)
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (properties *propertyList) Remove(tag PropertyName) {
|
||||||
|
properties.remove(properties, properties.normalize(tag))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func (properties *propertyList) Clear() {
|
||||||
|
properties.properties = map[PropertyName]any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (properties *propertyList) AllTags() []PropertyName {
|
||||||
|
tags := make([]PropertyName, 0, len(properties.properties))
|
||||||
|
for tag := range properties.properties {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
|
return tags[i] < tags[j]
|
||||||
|
})
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
|
func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
|
||||||
indent string, objectTag string, tags []string) {
|
indent string, objectTag string, tags []PropertyName) {
|
||||||
|
|
||||||
buffer.WriteString(objectTag)
|
buffer.WriteString(objectTag)
|
||||||
buffer.WriteString(" {\n")
|
buffer.WriteString(" {\n")
|
||||||
|
|
@ -83,7 +108,7 @@ func (properties *propertyList) writeToBuffer(buffer *strings.Builder,
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
if value, ok := properties.properties[tag]; ok {
|
if value, ok := properties.properties[tag]; ok {
|
||||||
buffer.WriteString(indent2)
|
buffer.WriteString(indent2)
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, indent2)
|
writePropertyValue(buffer, tag, value, indent2)
|
||||||
buffer.WriteString(",\n")
|
buffer.WriteString(",\n")
|
||||||
|
|
@ -100,14 +125,41 @@ func parseProperties(properties Properties, object DataObject) {
|
||||||
if node := object.Property(i); node != nil {
|
if node := object.Property(i); node != nil {
|
||||||
switch node.Type() {
|
switch node.Type() {
|
||||||
case TextNode:
|
case TextNode:
|
||||||
properties.Set(node.Tag(), node.Text())
|
properties.Set(PropertyName(node.Tag()), node.Text())
|
||||||
|
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
properties.Set(node.Tag(), node.Object())
|
properties.Set(PropertyName(node.Tag()), node.Object())
|
||||||
|
|
||||||
case ArrayNode:
|
case ArrayNode:
|
||||||
properties.Set(node.Tag(), node.ArrayElements())
|
properties.Set(PropertyName(node.Tag()), node.ArrayElements())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func propertiesGet(properties Properties, tag PropertyName) any {
|
||||||
|
return properties.getRaw(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func propertiesRemove(properties Properties, tag PropertyName) []PropertyName {
|
||||||
|
if properties.getRaw(tag) == nil {
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
properties.setRaw(tag, nil)
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *dataProperty) init() {
|
||||||
|
data.propertyList.init()
|
||||||
|
data.get = propertiesGet
|
||||||
|
data.set = propertiesSet
|
||||||
|
data.remove = propertiesRemove
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *dataProperty) Get(tag PropertyName) any {
|
||||||
|
return propertiesGet(data, data.normalize(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *dataProperty) Remove(tag PropertyName) {
|
||||||
|
data.remove(data, data.normalize(tag))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func stringProperty(properties Properties, tag string, session Session) (string, bool) {
|
func stringProperty(properties Properties, tag PropertyName, session Session) (string, bool) {
|
||||||
if value := properties.getRaw(tag); value != nil {
|
if value := properties.getRaw(tag); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
return session.resolveConstants(text)
|
return session.resolveConstants(text)
|
||||||
|
|
@ -15,7 +15,7 @@ func stringProperty(properties Properties, tag string, session Session) (string,
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageProperty(properties Properties, tag string, session Session) (string, bool) {
|
func imageProperty(properties Properties, tag PropertyName, session Session) (string, bool) {
|
||||||
if value := properties.getRaw(tag); value != nil {
|
if value := properties.getRaw(tag); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
if text != "" && text[0] == '@' {
|
if text != "" && text[0] == '@' {
|
||||||
|
|
@ -61,11 +61,11 @@ func valueToSizeUnit(value any, session Session) (SizeUnit, bool) {
|
||||||
return AutoSize(), false
|
return AutoSize(), false
|
||||||
}
|
}
|
||||||
|
|
||||||
func sizeProperty(properties Properties, tag string, session Session) (SizeUnit, bool) {
|
func sizeProperty(properties Properties, tag PropertyName, session Session) (SizeUnit, bool) {
|
||||||
return valueToSizeUnit(properties.getRaw(tag), session)
|
return valueToSizeUnit(properties.getRaw(tag), session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func angleProperty(properties Properties, tag string, session Session) (AngleUnit, bool) {
|
func angleProperty(properties Properties, tag PropertyName, session Session) (AngleUnit, bool) {
|
||||||
if value := properties.getRaw(tag); value != nil {
|
if value := properties.getRaw(tag); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case AngleUnit:
|
case AngleUnit:
|
||||||
|
|
@ -98,11 +98,11 @@ func valueToColor(value any, session Session) (Color, bool) {
|
||||||
return Color(0), false
|
return Color(0), false
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorProperty(properties Properties, tag string, session Session) (Color, bool) {
|
func colorProperty(properties Properties, tag PropertyName, session Session) (Color, bool) {
|
||||||
return valueToColor(properties.getRaw(tag), session)
|
return valueToColor(properties.getRaw(tag), session)
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueToEnum(value any, tag string, session Session, defaultValue int) (int, bool) {
|
func valueToEnum(value any, tag PropertyName, session Session, defaultValue int) (int, bool) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
values := enumProperties[tag].values
|
values := enumProperties[tag].values
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -165,7 +165,7 @@ func enumStringToInt(value string, enumValues []string, logError bool) (int, boo
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func enumProperty(properties Properties, tag string, session Session, defaultValue int) (int, bool) {
|
func enumProperty(properties Properties, tag PropertyName, session Session, defaultValue int) (int, bool) {
|
||||||
return valueToEnum(properties.getRaw(tag), tag, session, defaultValue)
|
return valueToEnum(properties.getRaw(tag), tag, session, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,7 +194,7 @@ func valueToBool(value any, session Session) (bool, bool) {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolProperty(properties Properties, tag string, session Session) (bool, bool) {
|
func boolProperty(properties Properties, tag PropertyName, session Session) (bool, bool) {
|
||||||
return valueToBool(properties.getRaw(tag), session)
|
return valueToBool(properties.getRaw(tag), session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +224,7 @@ func valueToInt(value any, session Session, defaultValue int) (int, bool) {
|
||||||
return defaultValue, false
|
return defaultValue, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func intProperty(properties Properties, tag string, session Session, defaultValue int) (int, bool) {
|
func intProperty(properties Properties, tag PropertyName, session Session, defaultValue int) (int, bool) {
|
||||||
return valueToInt(properties.getRaw(tag), session, defaultValue)
|
return valueToInt(properties.getRaw(tag), session, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +248,7 @@ func valueToFloat(value any, session Session, defaultValue float64) (float64, bo
|
||||||
return defaultValue, false
|
return defaultValue, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func floatProperty(properties Properties, tag string, session Session, defaultValue float64) (float64, bool) {
|
func floatProperty(properties Properties, tag PropertyName, session Session, defaultValue float64) (float64, bool) {
|
||||||
return valueToFloat(properties.getRaw(tag), session, defaultValue)
|
return valueToFloat(properties.getRaw(tag), session, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ func valueToFloatText(value any, session Session, defaultValue float64) (string,
|
||||||
return fmt.Sprintf("%g", defaultValue), false
|
return fmt.Sprintf("%g", defaultValue), false
|
||||||
}
|
}
|
||||||
|
|
||||||
func floatTextProperty(properties Properties, tag string, session Session, defaultValue float64) (string, bool) {
|
func floatTextProperty(properties Properties, tag PropertyName, session Session, defaultValue float64) (string, bool) {
|
||||||
return valueToFloatText(properties.getRaw(tag), session, defaultValue)
|
return valueToFloatText(properties.getRaw(tag), session, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,6 +297,6 @@ func valueToRange(value any, session Session) (Range, bool) {
|
||||||
return Range{}, false
|
return Range{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func rangeProperty(properties Properties, tag string, session Session) (Range, bool) {
|
func rangeProperty(properties Properties, tag PropertyName, session Session) (Range, bool) {
|
||||||
return valueToRange(properties.getRaw(tag), session)
|
return valueToRange(properties.getRaw(tag), session)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2875
propertyNames.go
2875
propertyNames.go
File diff suppressed because it is too large
Load Diff
409
propertySet.go
409
propertySet.go
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var colorProperties = []string{
|
var colorProperties = []PropertyName{
|
||||||
ColorTag,
|
ColorTag,
|
||||||
BackgroundColor,
|
BackgroundColor,
|
||||||
TextColor,
|
TextColor,
|
||||||
|
|
@ -19,9 +19,10 @@ var colorProperties = []string{
|
||||||
OutlineColor,
|
OutlineColor,
|
||||||
TextLineColor,
|
TextLineColor,
|
||||||
ColorPickerValue,
|
ColorPickerValue,
|
||||||
|
AccentColor,
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPropertyInList(tag string, list []string) bool {
|
func isPropertyInList(tag PropertyName, list []PropertyName) bool {
|
||||||
for _, prop := range list {
|
for _, prop := range list {
|
||||||
if prop == tag {
|
if prop == tag {
|
||||||
return true
|
return true
|
||||||
|
|
@ -30,11 +31,11 @@ func isPropertyInList(tag string, list []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var angleProperties = []string{
|
var angleProperties = []PropertyName{
|
||||||
From,
|
From,
|
||||||
}
|
}
|
||||||
|
|
||||||
var boolProperties = []string{
|
var boolProperties = []PropertyName{
|
||||||
Disabled,
|
Disabled,
|
||||||
Focusable,
|
Focusable,
|
||||||
Inset,
|
Inset,
|
||||||
|
|
@ -61,9 +62,11 @@ var boolProperties = []string{
|
||||||
Repeating,
|
Repeating,
|
||||||
UserSelect,
|
UserSelect,
|
||||||
ColumnSpanAll,
|
ColumnSpanAll,
|
||||||
|
MoveToFrontAnimation,
|
||||||
|
HideSummaryMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
var intProperties = []string{
|
var intProperties = []PropertyName{
|
||||||
ZIndex,
|
ZIndex,
|
||||||
TabSize,
|
TabSize,
|
||||||
HeadHeight,
|
HeadHeight,
|
||||||
|
|
@ -73,9 +76,11 @@ var intProperties = []string{
|
||||||
ColumnCount,
|
ColumnCount,
|
||||||
Order,
|
Order,
|
||||||
TabIndex,
|
TabIndex,
|
||||||
|
MaxLength,
|
||||||
|
NumberPickerPrecision,
|
||||||
}
|
}
|
||||||
|
|
||||||
var floatProperties = map[string]struct{ min, max float64 }{
|
var floatProperties = map[PropertyName]struct{ min, max float64 }{
|
||||||
Opacity: {min: 0, max: 1},
|
Opacity: {min: 0, max: 1},
|
||||||
NumberPickerMax: {min: -math.MaxFloat64, max: math.MaxFloat64},
|
NumberPickerMax: {min: -math.MaxFloat64, max: math.MaxFloat64},
|
||||||
NumberPickerMin: {min: -math.MaxFloat64, max: math.MaxFloat64},
|
NumberPickerMin: {min: -math.MaxFloat64, max: math.MaxFloat64},
|
||||||
|
|
@ -85,85 +90,88 @@ var floatProperties = map[string]struct{ min, max float64 }{
|
||||||
ProgressBarValue: {min: 0, max: math.MaxFloat64},
|
ProgressBarValue: {min: 0, max: math.MaxFloat64},
|
||||||
VideoWidth: {min: 0, max: 10000},
|
VideoWidth: {min: 0, max: 10000},
|
||||||
VideoHeight: {min: 0, max: 10000},
|
VideoHeight: {min: 0, max: 10000},
|
||||||
|
PushDuration: {min: 0, max: math.MaxFloat64},
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizeProperties = map[string]string{
|
var sizeProperties = map[PropertyName]string{
|
||||||
Width: Width,
|
Width: string(Width),
|
||||||
Height: Height,
|
Height: string(Height),
|
||||||
MinWidth: MinWidth,
|
MinWidth: string(MinWidth),
|
||||||
MinHeight: MinHeight,
|
MinHeight: string(MinHeight),
|
||||||
MaxWidth: MaxWidth,
|
MaxWidth: string(MaxWidth),
|
||||||
MaxHeight: MaxHeight,
|
MaxHeight: string(MaxHeight),
|
||||||
Left: Left,
|
Left: string(Left),
|
||||||
Right: Right,
|
Right: string(Right),
|
||||||
Top: Top,
|
Top: string(Top),
|
||||||
Bottom: Bottom,
|
Bottom: string(Bottom),
|
||||||
TextSize: "font-size",
|
TextSize: "font-size",
|
||||||
TextIndent: TextIndent,
|
TextIndent: string(TextIndent),
|
||||||
LetterSpacing: LetterSpacing,
|
LetterSpacing: string(LetterSpacing),
|
||||||
WordSpacing: WordSpacing,
|
WordSpacing: string(WordSpacing),
|
||||||
LineHeight: LineHeight,
|
LineHeight: string(LineHeight),
|
||||||
TextLineThickness: "text-decoration-thickness",
|
TextLineThickness: "text-decoration-thickness",
|
||||||
ListRowGap: "row-gap",
|
ListRowGap: "row-gap",
|
||||||
ListColumnGap: "column-gap",
|
ListColumnGap: "column-gap",
|
||||||
GridRowGap: GridRowGap,
|
GridRowGap: string(GridRowGap),
|
||||||
GridColumnGap: GridColumnGap,
|
GridColumnGap: string(GridColumnGap),
|
||||||
ColumnWidth: ColumnWidth,
|
ColumnWidth: string(ColumnWidth),
|
||||||
ColumnGap: ColumnGap,
|
ColumnGap: string(ColumnGap),
|
||||||
Gap: Gap,
|
Gap: string(Gap),
|
||||||
Margin: Margin,
|
Margin: string(Margin),
|
||||||
MarginLeft: MarginLeft,
|
MarginLeft: string(MarginLeft),
|
||||||
MarginRight: MarginRight,
|
MarginRight: string(MarginRight),
|
||||||
MarginTop: MarginTop,
|
MarginTop: string(MarginTop),
|
||||||
MarginBottom: MarginBottom,
|
MarginBottom: string(MarginBottom),
|
||||||
Padding: Padding,
|
Padding: string(Padding),
|
||||||
PaddingLeft: PaddingLeft,
|
PaddingLeft: string(PaddingLeft),
|
||||||
PaddingRight: PaddingRight,
|
PaddingRight: string(PaddingRight),
|
||||||
PaddingTop: PaddingTop,
|
PaddingTop: string(PaddingTop),
|
||||||
PaddingBottom: PaddingBottom,
|
PaddingBottom: string(PaddingBottom),
|
||||||
BorderWidth: BorderWidth,
|
BorderWidth: string(BorderWidth),
|
||||||
BorderLeftWidth: BorderLeftWidth,
|
BorderLeftWidth: string(BorderLeftWidth),
|
||||||
BorderRightWidth: BorderRightWidth,
|
BorderRightWidth: string(BorderRightWidth),
|
||||||
BorderTopWidth: BorderTopWidth,
|
BorderTopWidth: string(BorderTopWidth),
|
||||||
BorderBottomWidth: BorderBottomWidth,
|
BorderBottomWidth: string(BorderBottomWidth),
|
||||||
OutlineWidth: OutlineWidth,
|
OutlineWidth: string(OutlineWidth),
|
||||||
OutlineOffset: OutlineOffset,
|
OutlineOffset: string(OutlineOffset),
|
||||||
XOffset: XOffset,
|
XOffset: string(XOffset),
|
||||||
YOffset: YOffset,
|
YOffset: string(YOffset),
|
||||||
BlurRadius: BlurRadius,
|
BlurRadius: string(BlurRadius),
|
||||||
SpreadRadius: SpreadRadius,
|
SpreadRadius: string(SpreadRadius),
|
||||||
Perspective: Perspective,
|
Perspective: string(Perspective),
|
||||||
PerspectiveOriginX: PerspectiveOriginX,
|
PerspectiveOriginX: string(PerspectiveOriginX),
|
||||||
PerspectiveOriginY: PerspectiveOriginY,
|
PerspectiveOriginY: string(PerspectiveOriginY),
|
||||||
OriginX: OriginX,
|
TransformOriginX: string(TransformOriginX),
|
||||||
OriginY: OriginY,
|
TransformOriginY: string(TransformOriginY),
|
||||||
OriginZ: OriginZ,
|
TransformOriginZ: string(TransformOriginZ),
|
||||||
Radius: Radius,
|
Radius: string(Radius),
|
||||||
RadiusX: RadiusX,
|
RadiusX: string(RadiusX),
|
||||||
RadiusY: RadiusY,
|
RadiusY: string(RadiusY),
|
||||||
RadiusTopLeft: RadiusTopLeft,
|
RadiusTopLeft: string(RadiusTopLeft),
|
||||||
RadiusTopLeftX: RadiusTopLeftX,
|
RadiusTopLeftX: string(RadiusTopLeftX),
|
||||||
RadiusTopLeftY: RadiusTopLeftY,
|
RadiusTopLeftY: string(RadiusTopLeftY),
|
||||||
RadiusTopRight: RadiusTopRight,
|
RadiusTopRight: string(RadiusTopRight),
|
||||||
RadiusTopRightX: RadiusTopRightX,
|
RadiusTopRightX: string(RadiusTopRightX),
|
||||||
RadiusTopRightY: RadiusTopRightY,
|
RadiusTopRightY: string(RadiusTopRightY),
|
||||||
RadiusBottomLeft: RadiusBottomLeft,
|
RadiusBottomLeft: string(RadiusBottomLeft),
|
||||||
RadiusBottomLeftX: RadiusBottomLeftX,
|
RadiusBottomLeftX: string(RadiusBottomLeftX),
|
||||||
RadiusBottomLeftY: RadiusBottomLeftY,
|
RadiusBottomLeftY: string(RadiusBottomLeftY),
|
||||||
RadiusBottomRight: RadiusBottomRight,
|
RadiusBottomRight: string(RadiusBottomRight),
|
||||||
RadiusBottomRightX: RadiusBottomRightX,
|
RadiusBottomRightX: string(RadiusBottomRightX),
|
||||||
RadiusBottomRightY: RadiusBottomRightY,
|
RadiusBottomRightY: string(RadiusBottomRightY),
|
||||||
ItemWidth: ItemWidth,
|
ItemWidth: string(ItemWidth),
|
||||||
ItemHeight: ItemHeight,
|
ItemHeight: string(ItemHeight),
|
||||||
CenterX: CenterX,
|
CenterX: string(CenterX),
|
||||||
CenterY: CenterX,
|
CenterY: string(CenterX),
|
||||||
}
|
}
|
||||||
|
|
||||||
var enumProperties = map[string]struct {
|
type enumPropertyData struct {
|
||||||
values []string
|
values []string
|
||||||
cssTag string
|
cssTag string
|
||||||
cssValues []string
|
cssValues []string
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var enumProperties = map[PropertyName]enumPropertyData{
|
||||||
Semantics: {
|
Semantics: {
|
||||||
[]string{"default", "article", "section", "aside", "header", "main", "footer", "navigation", "figure", "figure-caption", "button", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "code"},
|
[]string{"default", "article", "section", "aside", "header", "main", "footer", "navigation", "figure", "figure-caption", "button", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "code"},
|
||||||
"",
|
"",
|
||||||
|
|
@ -176,17 +184,17 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
Overflow: {
|
Overflow: {
|
||||||
[]string{"hidden", "visible", "scroll", "auto"},
|
[]string{"hidden", "visible", "scroll", "auto"},
|
||||||
Overflow,
|
string(Overflow),
|
||||||
[]string{"hidden", "visible", "scroll", "auto"},
|
[]string{"hidden", "visible", "scroll", "auto"},
|
||||||
},
|
},
|
||||||
TextAlign: {
|
TextAlign: {
|
||||||
[]string{"left", "right", "center", "justify"},
|
[]string{"left", "right", "center", "justify"},
|
||||||
TextAlign,
|
string(TextAlign),
|
||||||
[]string{"left", "right", "center", "justify"},
|
[]string{"left", "right", "center", "justify"},
|
||||||
},
|
},
|
||||||
TextTransform: {
|
TextTransform: {
|
||||||
[]string{"none", "capitalize", "lowercase", "uppercase"},
|
[]string{"none", "capitalize", "lowercase", "uppercase"},
|
||||||
TextTransform,
|
string(TextTransform),
|
||||||
[]string{"none", "capitalize", "lowercase", "uppercase"},
|
[]string{"none", "capitalize", "lowercase", "uppercase"},
|
||||||
},
|
},
|
||||||
TextWeight: {
|
TextWeight: {
|
||||||
|
|
@ -196,27 +204,27 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
WhiteSpace: {
|
WhiteSpace: {
|
||||||
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
|
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
|
||||||
WhiteSpace,
|
string(WhiteSpace),
|
||||||
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
|
[]string{"normal", "nowrap", "pre", "pre-wrap", "pre-line", "break-spaces"},
|
||||||
},
|
},
|
||||||
WordBreak: {
|
WordBreak: {
|
||||||
[]string{"normal", "break-all", "keep-all", "break-word"},
|
[]string{"normal", "break-all", "keep-all", "break-word"},
|
||||||
WordBreak,
|
string(WordBreak),
|
||||||
[]string{"normal", "break-all", "keep-all", "break-word"},
|
[]string{"normal", "break-all", "keep-all", "break-word"},
|
||||||
},
|
},
|
||||||
TextOverflow: {
|
TextOverflow: {
|
||||||
[]string{"clip", "ellipsis"},
|
[]string{"clip", "ellipsis"},
|
||||||
TextOverflow,
|
string(TextOverflow),
|
||||||
[]string{"clip", "ellipsis"},
|
[]string{"clip", "ellipsis"},
|
||||||
},
|
},
|
||||||
TextWrap: {
|
TextWrap: {
|
||||||
[]string{"wrap", "nowrap", "balance"},
|
[]string{"wrap", "nowrap", "balance"},
|
||||||
TextWrap,
|
string(TextWrap),
|
||||||
[]string{"wrap", "nowrap", "balance"},
|
[]string{"wrap", "nowrap", "balance"},
|
||||||
},
|
},
|
||||||
WritingMode: {
|
WritingMode: {
|
||||||
[]string{"horizontal-top-to-bottom", "horizontal-bottom-to-top", "vertical-right-to-left", "vertical-left-to-right"},
|
[]string{"horizontal-top-to-bottom", "horizontal-bottom-to-top", "vertical-right-to-left", "vertical-left-to-right"},
|
||||||
WritingMode,
|
string(WritingMode),
|
||||||
[]string{"horizontal-tb", "horizontal-bt", "vertical-rl", "vertical-lr"},
|
[]string{"horizontal-tb", "horizontal-bt", "vertical-rl", "vertical-lr"},
|
||||||
},
|
},
|
||||||
TextDirection: {
|
TextDirection: {
|
||||||
|
|
@ -236,7 +244,7 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
BorderStyle: {
|
BorderStyle: {
|
||||||
[]string{"none", "solid", "dashed", "dotted", "double"},
|
[]string{"none", "solid", "dashed", "dotted", "double"},
|
||||||
BorderStyle,
|
string(BorderStyle),
|
||||||
[]string{"none", "solid", "dashed", "dotted", "double"},
|
[]string{"none", "solid", "dashed", "dotted", "double"},
|
||||||
},
|
},
|
||||||
TopStyle: {
|
TopStyle: {
|
||||||
|
|
@ -261,7 +269,7 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
OutlineStyle: {
|
OutlineStyle: {
|
||||||
[]string{"none", "solid", "dashed", "dotted", "double"},
|
[]string{"none", "solid", "dashed", "dotted", "double"},
|
||||||
OutlineStyle,
|
string(OutlineStyle),
|
||||||
[]string{"none", "solid", "dashed", "dotted", "double"},
|
[]string{"none", "solid", "dashed", "dotted", "double"},
|
||||||
},
|
},
|
||||||
Tabs: {
|
Tabs: {
|
||||||
|
|
@ -336,7 +344,7 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
GridAutoFlow: {
|
GridAutoFlow: {
|
||||||
[]string{"row", "column", "row-dense", "column-dense"},
|
[]string{"row", "column", "row-dense", "column-dense"},
|
||||||
GridAutoFlow,
|
string(GridAutoFlow),
|
||||||
[]string{"row", "column", "row dense", "column dense"},
|
[]string{"row", "column", "row dense", "column dense"},
|
||||||
},
|
},
|
||||||
ImageVerticalAlign: {
|
ImageVerticalAlign: {
|
||||||
|
|
@ -376,7 +384,7 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
Cursor: {
|
Cursor: {
|
||||||
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
|
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
|
||||||
Cursor,
|
string(Cursor),
|
||||||
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
|
[]string{"auto", "default", "none", "context-menu", "help", "pointer", "progress", "wait", "cell", "crosshair", "text", "vertical-text", "alias", "copy", "move", "no-drop", "not-allowed", "e-resize", "n-resize", "ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize", "w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize", "col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out", "grab", "grabbing"},
|
||||||
},
|
},
|
||||||
Fit: {
|
Fit: {
|
||||||
|
|
@ -404,6 +412,21 @@ var enumProperties = map[string]struct {
|
||||||
"background-clip",
|
"background-clip",
|
||||||
[]string{"border-box", "padding-box", "content-box"}, // "text"},
|
[]string{"border-box", "padding-box", "content-box"}, // "text"},
|
||||||
},
|
},
|
||||||
|
BackgroundOrigin: {
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
"background-origin",
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
},
|
||||||
|
MaskClip: {
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
"mask-clip",
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
},
|
||||||
|
MaskOrigin: {
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
"background-origin",
|
||||||
|
[]string{"border-box", "padding-box", "content-box"},
|
||||||
|
},
|
||||||
Direction: {
|
Direction: {
|
||||||
[]string{"to-top", "to-right-top", "to-right", "to-right-bottom", "to-bottom", "to-left-bottom", "to-left", "to-left-top"},
|
[]string{"to-top", "to-right-top", "to-right", "to-right-bottom", "to-bottom", "to-left-bottom", "to-left", "to-left-top"},
|
||||||
"",
|
"",
|
||||||
|
|
@ -456,27 +479,27 @@ var enumProperties = map[string]struct {
|
||||||
},
|
},
|
||||||
MixBlendMode: {
|
MixBlendMode: {
|
||||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||||
MixBlendMode,
|
string(MixBlendMode),
|
||||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||||
},
|
},
|
||||||
BackgroundBlendMode: {
|
BackgroundBlendMode: {
|
||||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||||
BackgroundBlendMode,
|
string(BackgroundBlendMode),
|
||||||
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
[]string{"normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"},
|
||||||
},
|
},
|
||||||
ColumnFill: {
|
ColumnFill: {
|
||||||
[]string{"balance", "auto"},
|
[]string{"balance", "auto"},
|
||||||
ColumnFill,
|
string(ColumnFill),
|
||||||
[]string{"balance", "auto"},
|
[]string{"balance", "auto"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func notCompatibleType(tag string, value any) {
|
func notCompatibleType(tag PropertyName, value any) {
|
||||||
ErrorLogF(`"%T" type not compatible with "%s" property`, value, tag)
|
ErrorLogF(`"%T" type not compatible with "%s" property`, value, string(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
func invalidPropertyValue(tag string, value any) {
|
func invalidPropertyValue(tag PropertyName, value any) {
|
||||||
ErrorLogF(`Invalid value "%v" of "%s" property`, value, tag)
|
ErrorLogF(`Invalid value "%v" of "%s" property`, value, string(tag))
|
||||||
}
|
}
|
||||||
|
|
||||||
func isConstantName(text string) bool {
|
func isConstantName(text string) bool {
|
||||||
|
|
@ -537,26 +560,48 @@ func isInt(value any) (int, bool) {
|
||||||
return n, true
|
return n, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setSimpleProperty(tag string, value any) bool {
|
func setSimpleProperty(properties Properties, tag PropertyName, value any) bool {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
delete(properties.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
return true
|
return true
|
||||||
} else if text, ok := value.(string); ok {
|
} else if text, ok := value.(string); ok {
|
||||||
text = strings.Trim(text, " \t\n\r")
|
text = strings.Trim(text, " \t\n\r")
|
||||||
if text == "" {
|
if text == "" {
|
||||||
delete(properties.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if isConstantName(text) {
|
if isConstantName(text) {
|
||||||
properties.properties[tag] = text
|
properties.setRaw(tag, text)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setSizeProperty(tag string, value any) bool {
|
func setStringPropertyValue(properties Properties, tag PropertyName, text any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if text != "" {
|
||||||
|
properties.setRaw(tag, text)
|
||||||
|
} else if properties.getRaw(tag) != nil {
|
||||||
|
properties.setRaw(tag, nil)
|
||||||
|
} else {
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArrayPropertyValue[T any](properties Properties, tag PropertyName, value []T) []PropertyName {
|
||||||
|
if len(value) > 0 {
|
||||||
|
properties.setRaw(tag, value)
|
||||||
|
} else if properties.getRaw(tag) != nil {
|
||||||
|
properties.setRaw(tag, nil)
|
||||||
|
} else {
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSizeProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
var size SizeUnit
|
var size SizeUnit
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
@ -566,7 +611,7 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
|
||||||
size.Function = fn
|
size.Function = fn
|
||||||
} else if size, ok = StringToSizeUnit(value); !ok {
|
} else if size, ok = StringToSizeUnit(value); !ok {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
case SizeUnit:
|
case SizeUnit:
|
||||||
size = value
|
size = value
|
||||||
|
|
@ -589,29 +634,29 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
|
||||||
size.Value = float64(n)
|
size.Value = float64(n)
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if size.Type == Auto {
|
if size.Type == Auto {
|
||||||
delete(properties.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
} else {
|
} else {
|
||||||
properties.properties[tag] = size
|
properties.setRaw(tag, size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setAngleProperty(tag string, value any) bool {
|
func setAngleProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
var angle AngleUnit
|
var angle AngleUnit
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
var ok bool
|
var ok bool
|
||||||
if angle, ok = StringToAngleUnit(value); !ok {
|
if angle, ok = StringToAngleUnit(value); !ok {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
case AngleUnit:
|
case AngleUnit:
|
||||||
angle = value
|
angle = value
|
||||||
|
|
@ -627,24 +672,24 @@ func (properties *propertyList) setAngleProperty(tag string, value any) bool {
|
||||||
angle = Rad(float64(n))
|
angle = Rad(float64(n))
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
properties.properties[tag] = angle
|
properties.setRaw(tag, angle)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setColorProperty(tag string, value any) bool {
|
func setColorProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
var result Color
|
var result Color
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
var err error
|
var err error
|
||||||
if result, err = stringToColor(value); err != nil {
|
if result, err = stringToColor(value); err != nil {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
case Color:
|
case Color:
|
||||||
result = value
|
result = value
|
||||||
|
|
@ -654,105 +699,101 @@ func (properties *propertyList) setColorProperty(tag string, value any) bool {
|
||||||
result = Color(color)
|
result = Color(color)
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if result == 0 {
|
properties.setRaw(tag, result)
|
||||||
delete(properties.properties, tag)
|
|
||||||
} else {
|
|
||||||
properties.properties[tag] = result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setEnumProperty(tag string, value any, values []string) bool {
|
func setEnumProperty(properties Properties, tag PropertyName, value any, values []string) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
var n int
|
var n int
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
if n, ok = enumStringToInt(text, values, false); !ok {
|
if n, ok = enumStringToInt(text, values, false); !ok {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
} else if i, ok := isInt(value); ok {
|
} else if i, ok := isInt(value); ok {
|
||||||
if i < 0 || i >= len(values) {
|
if i < 0 || i >= len(values) {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
n = i
|
n = i
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
properties.properties[tag] = n
|
properties.setRaw(tag, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setBoolProperty(tag string, value any) bool {
|
func setBoolProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
switch strings.ToLower(strings.Trim(text, " \t")) {
|
switch strings.ToLower(strings.Trim(text, " \t")) {
|
||||||
case "true", "yes", "on", "1":
|
case "true", "yes", "on", "1":
|
||||||
properties.properties[tag] = true
|
properties.setRaw(tag, true)
|
||||||
|
|
||||||
case "false", "no", "off", "0":
|
case "false", "no", "off", "0":
|
||||||
properties.properties[tag] = false
|
properties.setRaw(tag, false)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
} else if n, ok := isInt(value); ok {
|
} else if n, ok := isInt(value); ok {
|
||||||
switch n {
|
switch n {
|
||||||
case 1:
|
case 1:
|
||||||
properties.properties[tag] = true
|
properties.setRaw(tag, true)
|
||||||
|
|
||||||
case 0:
|
case 0:
|
||||||
properties.properties[tag] = false
|
properties.setRaw(tag, false)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
} else if b, ok := value.(bool); ok {
|
} else if b, ok := value.(bool); ok {
|
||||||
properties.properties[tag] = b
|
properties.setRaw(tag, b)
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setIntProperty(tag string, value any) bool {
|
func setIntProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
n, err := strconv.Atoi(strings.Trim(text, " \t"))
|
n, err := strconv.Atoi(strings.Trim(text, " \t"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
ErrorLog(err.Error())
|
ErrorLog(err.Error())
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
properties.properties[tag] = n
|
properties.setRaw(tag, n)
|
||||||
} else if n, ok := isInt(value); ok {
|
} else if n, ok := isInt(value); ok {
|
||||||
properties.properties[tag] = n
|
properties.setRaw(tag, n)
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setFloatProperty(tag string, value any, min, max float64) bool {
|
func setFloatProperty(properties Properties, tag PropertyName, value any, min, max float64) []PropertyName {
|
||||||
if !properties.setSimpleProperty(tag, value) {
|
if !setSimpleProperty(properties, tag, value) {
|
||||||
f := float64(0)
|
f := float64(0)
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
|
@ -760,14 +801,14 @@ func (properties *propertyList) setFloatProperty(tag string, value any, min, max
|
||||||
if f, err = strconv.ParseFloat(strings.Trim(value, " \t"), 64); err != nil {
|
if f, err = strconv.ParseFloat(strings.Trim(value, " \t"), 64); err != nil {
|
||||||
invalidPropertyValue(tag, value)
|
invalidPropertyValue(tag, value)
|
||||||
ErrorLog(err.Error())
|
ErrorLog(err.Error())
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
if f < min || f > max {
|
if f < min || f > max {
|
||||||
ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
|
ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
properties.properties[tag] = value
|
properties.setRaw(tag, value)
|
||||||
return true
|
return nil
|
||||||
|
|
||||||
case float32:
|
case float32:
|
||||||
f = float64(value)
|
f = float64(value)
|
||||||
|
|
@ -780,64 +821,84 @@ func (properties *propertyList) setFloatProperty(tag string, value any, min, max
|
||||||
f = float64(n)
|
f = float64(n)
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f >= min && f <= max {
|
if f >= min && f <= max {
|
||||||
properties.properties[tag] = f
|
properties.setRaw(tag, f)
|
||||||
} else {
|
} else {
|
||||||
ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
|
ErrorLogF(`"%T" out of range of "%s" property`, value, tag)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) Set(tag string, value any) bool {
|
func propertiesSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
return properties.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (properties *propertyList) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
delete(properties.properties, tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := sizeProperties[tag]; ok {
|
if _, ok := sizeProperties[tag]; ok {
|
||||||
return properties.setSizeProperty(tag, value)
|
return setSizeProperty(properties, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if valuesData, ok := enumProperties[tag]; ok {
|
if valuesData, ok := enumProperties[tag]; ok {
|
||||||
return properties.setEnumProperty(tag, value, valuesData.values)
|
return setEnumProperty(properties, tag, value, valuesData.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
if limits, ok := floatProperties[tag]; ok {
|
if limits, ok := floatProperties[tag]; ok {
|
||||||
return properties.setFloatProperty(tag, value, limits.min, limits.max)
|
return setFloatProperty(properties, tag, value, limits.min, limits.max)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPropertyInList(tag, colorProperties) {
|
if isPropertyInList(tag, colorProperties) {
|
||||||
return properties.setColorProperty(tag, value)
|
return setColorProperty(properties, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPropertyInList(tag, angleProperties) {
|
if isPropertyInList(tag, angleProperties) {
|
||||||
return properties.setAngleProperty(tag, value)
|
return setAngleProperty(properties, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPropertyInList(tag, boolProperties) {
|
if isPropertyInList(tag, boolProperties) {
|
||||||
return properties.setBoolProperty(tag, value)
|
return setBoolProperty(properties, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPropertyInList(tag, intProperties) {
|
if isPropertyInList(tag, intProperties) {
|
||||||
return properties.setIntProperty(tag, value)
|
return setIntProperty(properties, tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
properties.properties[tag] = text
|
properties.setRaw(tag, text)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (properties *propertyList) Set(tag PropertyName, value any) bool {
|
||||||
|
tag = properties.normalize(tag)
|
||||||
|
if value == nil {
|
||||||
|
properties.remove(properties, tag)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties.set(properties, tag, value) != nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (data *dataProperty) Set(tag PropertyName, value any) bool {
|
||||||
|
if value == nil {
|
||||||
|
data.Remove(tag)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = data.normalize(tag)
|
||||||
|
for _, supported := range data.supportedProperties {
|
||||||
|
if tag == supported {
|
||||||
|
return data.set(data, tag, value) != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"%s" property is not supported`, string(tag))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,9 @@ const (
|
||||||
WhiteSpacePreLine = 4
|
WhiteSpacePreLine = 4
|
||||||
|
|
||||||
// WhiteSpaceBreakSpaces - the behavior is identical to that of WhiteSpacePreWrap, except that:
|
// WhiteSpaceBreakSpaces - the behavior is identical to that of WhiteSpacePreWrap, except that:
|
||||||
// * Any sequence of preserved white space always takes up space, including at the end of the line.
|
// - Any sequence of preserved white space always takes up space, including at the end of the line.
|
||||||
// * A line breaking opportunity exists after every preserved white space character, including between white space characters.
|
// - A line breaking opportunity exists after every preserved white space character, including between white space characters.
|
||||||
// * Such preserved spaces take up space and do not hang, and thus affect the box’s intrinsic sizes (min-content size and max-content size).
|
// - Such preserved spaces take up space and do not hang, and thus affect the box’s intrinsic sizes (min-content size and max-content size).
|
||||||
WhiteSpaceBreakSpaces = 5
|
WhiteSpaceBreakSpaces = 5
|
||||||
|
|
||||||
// WordBreakNormal - use the default line break rule.
|
// WordBreakNormal - use the default line break rule.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Range defines range limits. The First and Last value are included in the range
|
||||||
|
type Range struct {
|
||||||
|
First, Last int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the Range struct
|
||||||
|
func (r Range) String() string {
|
||||||
|
if r.First == r.Last {
|
||||||
|
return fmt.Sprintf("%d", r.First)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d:%d", r.First, r.Last)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Range) setValue(value string) bool {
|
||||||
|
var err error
|
||||||
|
if strings.Contains(value, ":") {
|
||||||
|
values := strings.Split(value, ":")
|
||||||
|
if len(values) != 2 {
|
||||||
|
ErrorLog("Invalid range value: " + value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.First, err = strconv.Atoi(strings.Trim(values[0], " \t\n\r")); err != nil {
|
||||||
|
ErrorLog(`Invalid first range value "` + value + `" (` + err.Error() + ")")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.Last, err = strconv.Atoi(strings.Trim(values[1], " \t\n\r")); err != nil {
|
||||||
|
ErrorLog(`Invalid last range value "` + value + `" (` + err.Error() + ")")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.First, err = strconv.Atoi(value); err != nil {
|
||||||
|
ErrorLog(`Invalid range value "` + value + `" (` + err.Error() + ")")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r.Last = r.First
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRangeProperty(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if setSimpleProperty(properties, tag, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r Range
|
||||||
|
if !r.setValue(value) {
|
||||||
|
invalidPropertyValue(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
properties.setRaw(tag, r)
|
||||||
|
|
||||||
|
case Range:
|
||||||
|
properties.setRaw(tag, value)
|
||||||
|
|
||||||
|
default:
|
||||||
|
if n, ok := isInt(value); ok {
|
||||||
|
properties.setRaw(tag, Range{First: n, Last: n})
|
||||||
|
} else {
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
243
resizable.go
243
resizable.go
|
|
@ -10,29 +10,29 @@ import (
|
||||||
const (
|
const (
|
||||||
// Side is the constant for "side" property tag.
|
// Side is the constant for "side" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Resizable`.
|
// Used by Resizable.
|
||||||
// Determines which side of the container is used to resize. The value of property is an or-combination of values listed.
|
// Determines which side of the container is used to resize. The value of property is an or-combination of values listed.
|
||||||
// Default value is "all".
|
// Default value is "all".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `1`(`TopSide`) or "top" - Top frame side.
|
// - 1 (TopSide) or "top" - Top frame side.
|
||||||
// `2`(`RightSide`) or "right" - Right frame side.
|
// - 2 (RightSide) or "right" - Right frame side.
|
||||||
// `4`(`BottomSide`) or "bottom" - Bottom frame side.
|
// - 4 (BottomSide) or "bottom" - Bottom frame side.
|
||||||
// `8`(`LeftSide`) or "left" - Left frame side.
|
// - 8 (LeftSide) or "left" - Left frame side.
|
||||||
// `15`(`AllSides`) or "all" - All frame sides.
|
// - 15 (AllSides) or "all" - All frame sides.
|
||||||
Side = "side"
|
Side = "side"
|
||||||
|
|
||||||
// ResizeBorderWidth is the constant for "resize-border-width" property tag.
|
// ResizeBorderWidth is the constant for "resize-border-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `Resizable`.
|
// Used by Resizable.
|
||||||
// Specifies the width of the resizing border.
|
// Specifies the width of the resizing border.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
ResizeBorderWidth = "resize-border-width"
|
ResizeBorderWidth = "resize-border-width"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -62,7 +62,6 @@ type Resizable interface {
|
||||||
|
|
||||||
type resizableData struct {
|
type resizableData struct {
|
||||||
viewData
|
viewData
|
||||||
content []View
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResizable create new Resizable object and return it
|
// NewResizable create new Resizable object and return it
|
||||||
|
|
@ -74,97 +73,40 @@ func NewResizable(session Session, params Params) Resizable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResizable(session Session) View {
|
func newResizable(session Session) View {
|
||||||
return NewResizable(session, nil)
|
return new(resizableData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) init(session Session) {
|
func (resizable *resizableData) init(session Session) {
|
||||||
resizable.viewData.init(session)
|
resizable.viewData.init(session)
|
||||||
resizable.tag = "Resizable"
|
resizable.tag = "Resizable"
|
||||||
resizable.systemClass = "ruiGridLayout"
|
resizable.systemClass = "ruiGridLayout"
|
||||||
resizable.content = []View{}
|
resizable.set = resizable.setFunc
|
||||||
}
|
resizable.changed = resizable.propertyChanged
|
||||||
|
|
||||||
func (resizable *resizableData) String() string {
|
|
||||||
return getViewString(resizable, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) Views() []View {
|
func (resizable *resizableData) Views() []View {
|
||||||
return resizable.content
|
if view := resizable.content(); view != nil {
|
||||||
|
return []View{view}
|
||||||
|
}
|
||||||
|
return []View{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) Remove(tag string) {
|
func (resizable *resizableData) content() View {
|
||||||
resizable.remove(strings.ToLower(tag))
|
if value := resizable.getRaw(Content); value != nil {
|
||||||
|
if content, ok := value.(View); ok {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) remove(tag string) {
|
func (resizable *resizableData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case Side:
|
case Side:
|
||||||
oldSide := resizable.getSide()
|
return resizableSetSide(resizable, value)
|
||||||
delete(resizable.properties, Side)
|
|
||||||
if oldSide != resizable.getSide() {
|
|
||||||
if resizable.created {
|
|
||||||
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
|
||||||
resizable.updateResizeBorderWidth()
|
|
||||||
}
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ResizeBorderWidth:
|
case ResizeBorderWidth:
|
||||||
w := resizable.resizeBorderWidth()
|
return setSizeProperty(resizable, tag, value)
|
||||||
delete(resizable.properties, ResizeBorderWidth)
|
|
||||||
if !w.Equal(resizable.resizeBorderWidth()) {
|
|
||||||
resizable.updateResizeBorderWidth()
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Content:
|
|
||||||
if len(resizable.content) > 0 {
|
|
||||||
resizable.content = []View{}
|
|
||||||
if resizable.created {
|
|
||||||
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
|
||||||
}
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
resizable.viewData.remove(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resizable *resizableData) Set(tag string, value any) bool {
|
|
||||||
return resizable.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resizable *resizableData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
resizable.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case Side:
|
|
||||||
oldSide := resizable.getSide()
|
|
||||||
if !resizable.setSide(value) {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if oldSide != resizable.getSide() {
|
|
||||||
if resizable.created {
|
|
||||||
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
|
||||||
resizable.updateResizeBorderWidth()
|
|
||||||
}
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
case ResizeBorderWidth:
|
|
||||||
w := resizable.resizeBorderWidth()
|
|
||||||
ok := resizable.setSizeProperty(tag, value)
|
|
||||||
if ok && !w.Equal(resizable.resizeBorderWidth()) {
|
|
||||||
resizable.updateResizeBorderWidth()
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return ok
|
|
||||||
|
|
||||||
case Content:
|
case Content:
|
||||||
var newContent View = nil
|
var newContent View = nil
|
||||||
|
|
@ -176,45 +118,54 @@ func (resizable *resizableData) set(tag string, value any) bool {
|
||||||
newContent = value
|
newContent = value
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if view := CreateViewFromObject(resizable.Session(), value); view != nil {
|
if newContent = CreateViewFromObject(resizable.Session(), value); newContent == nil {
|
||||||
newContent = view
|
return nil
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resizable.content) == 0 {
|
resizable.setRaw(Content, newContent)
|
||||||
resizable.content = []View{newContent}
|
return []PropertyName{}
|
||||||
} else {
|
|
||||||
resizable.content[0] = newContent
|
|
||||||
}
|
|
||||||
if resizable.created {
|
|
||||||
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
|
||||||
}
|
|
||||||
resizable.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case CellWidth, CellHeight, GridRowGap, GridColumnGap, CellVerticalAlign, CellHorizontalAlign:
|
case CellWidth, CellHeight, GridRowGap, GridColumnGap, CellVerticalAlign, CellHorizontalAlign:
|
||||||
ErrorLogF(`Not supported "%s" property`, tag)
|
ErrorLogF(`Not supported "%s" property`, string(tag))
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return resizable.viewData.set(tag, value)
|
return resizable.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) Get(tag string) any {
|
func (resizable *resizableData) propertyChanged(tag PropertyName) {
|
||||||
return resizable.get(strings.ToLower(tag))
|
switch tag {
|
||||||
|
case Side:
|
||||||
|
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case ResizeBorderWidth:
|
||||||
|
htmlID := resizable.htmlID()
|
||||||
|
session := resizable.Session()
|
||||||
|
column, row := resizableCellSizeCSS(resizable)
|
||||||
|
|
||||||
|
session.updateCSSProperty(htmlID, "grid-template-columns", column)
|
||||||
|
session.updateCSSProperty(htmlID, "grid-template-rows", row)
|
||||||
|
|
||||||
|
case Content:
|
||||||
|
updateInnerHTML(resizable.htmlID(), resizable.Session())
|
||||||
|
|
||||||
|
default:
|
||||||
|
resizable.viewData.propertyChanged(tag)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) getSide() int {
|
func resizableSide(view View) int {
|
||||||
if value := resizable.getRaw(Side); value != nil {
|
if value := view.getRaw(Side); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if value, ok := resizable.session.resolveConstants(value); ok {
|
if value, ok := view.Session().resolveConstants(value); ok {
|
||||||
validValues := map[string]int{
|
validValues := map[string]int{
|
||||||
"top": TopSide,
|
"top": TopSide,
|
||||||
"right": RightSide,
|
"right": RightSide,
|
||||||
|
|
@ -258,15 +209,15 @@ func (resizable *resizableData) getSide() int {
|
||||||
return AllSides
|
return AllSides
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) setSide(value any) bool {
|
func resizableSetSide(properties Properties, value any) []PropertyName {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
if n, err := strconv.Atoi(value); err == nil {
|
if n, err := strconv.Atoi(value); err == nil {
|
||||||
if n >= 1 && n <= AllSides {
|
if n >= 1 && n <= AllSides {
|
||||||
resizable.properties[Side] = n
|
properties.setRaw(Side, n)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
validValues := map[string]int{
|
validValues := map[string]int{
|
||||||
"top": TopSide,
|
"top": TopSide,
|
||||||
|
|
@ -287,13 +238,13 @@ func (resizable *resizableData) setSide(value any) bool {
|
||||||
hasConst = true
|
hasConst = true
|
||||||
} else if n, err := strconv.Atoi(val); err == nil {
|
} else if n, err := strconv.Atoi(val); err == nil {
|
||||||
if n < 1 || n > AllSides {
|
if n < 1 || n > AllSides {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
sides |= n
|
sides |= n
|
||||||
} else if n, ok := validValues[val]; ok {
|
} else if n, ok := validValues[val]; ok {
|
||||||
sides |= n
|
sides |= n
|
||||||
} else {
|
} else {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,69 +253,58 @@ func (resizable *resizableData) setSide(value any) bool {
|
||||||
for i := 1; i < len(values); i++ {
|
for i := 1; i < len(values); i++ {
|
||||||
value += "|" + values[i]
|
value += "|" + values[i]
|
||||||
}
|
}
|
||||||
resizable.properties[Side] = value
|
properties.setRaw(Side, value)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sides >= 1 && sides <= AllSides {
|
if sides >= 1 && sides <= AllSides {
|
||||||
resizable.properties[Side] = sides
|
properties.setRaw(Side, sides)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if value[0] == '@' {
|
} else if value[0] == '@' {
|
||||||
resizable.properties[Side] = value
|
properties.setRaw(Side, value)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
} else if n, ok := validValues[value]; ok {
|
} else if n, ok := validValues[value]; ok {
|
||||||
resizable.properties[Side] = n
|
properties.setRaw(Side, n)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
}
|
}
|
||||||
|
|
||||||
case int:
|
case int:
|
||||||
if value >= 1 && value <= AllSides {
|
if value >= 1 && value <= AllSides {
|
||||||
resizable.properties[Side] = value
|
properties.setRaw(Side, value)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
} else {
|
} else {
|
||||||
ErrorLogF(`Invalid value %d of "side" property`, value)
|
ErrorLogF(`Invalid value %d of "side" property`, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if n, ok := isInt(value); ok {
|
if n, ok := isInt(value); ok {
|
||||||
if n >= 1 && n <= AllSides {
|
if n >= 1 && n <= AllSides {
|
||||||
resizable.properties[Side] = n
|
properties.setRaw(Side, n)
|
||||||
return true
|
return []PropertyName{Side}
|
||||||
} else {
|
} else {
|
||||||
ErrorLogF(`Invalid value %d of "side" property`, n)
|
ErrorLogF(`Invalid value %d of "side" property`, n)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) resizeBorderWidth() SizeUnit {
|
func resizableBorderWidth(view View) SizeUnit {
|
||||||
result, _ := sizeProperty(resizable, ResizeBorderWidth, resizable.Session())
|
result, _ := sizeProperty(view, ResizeBorderWidth, view.Session())
|
||||||
if result.Type == Auto || result.Value == 0 {
|
if result.Type == Auto || result.Value == 0 {
|
||||||
return Px(4)
|
return Px(4)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) updateResizeBorderWidth() {
|
func resizableCellSizeCSS(view View) (string, string) {
|
||||||
if resizable.created {
|
w := resizableBorderWidth(view).cssString("4px", view.Session())
|
||||||
htmlID := resizable.htmlID()
|
side := resizableSide(view)
|
||||||
session := resizable.Session()
|
|
||||||
column, row := resizable.cellSizeCSS()
|
|
||||||
|
|
||||||
session.updateCSSProperty(htmlID, "grid-template-columns", column)
|
|
||||||
session.updateCSSProperty(htmlID, "grid-template-rows", row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (resizable *resizableData) cellSizeCSS() (string, string) {
|
|
||||||
w := resizable.resizeBorderWidth().cssString("4px", resizable.Session())
|
|
||||||
side := resizable.getSide()
|
|
||||||
column := "1fr"
|
column := "1fr"
|
||||||
row := "1fr"
|
row := "1fr"
|
||||||
|
|
||||||
|
|
@ -392,7 +332,7 @@ func (resizable *resizableData) cellSizeCSS() (string, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resizable *resizableData) cssStyle(self View, builder cssBuilder) {
|
func (resizable *resizableData) cssStyle(self View, builder cssBuilder) {
|
||||||
column, row := resizable.cellSizeCSS()
|
column, row := resizableCellSizeCSS(resizable)
|
||||||
|
|
||||||
builder.add("grid-template-columns", column)
|
builder.add("grid-template-columns", column)
|
||||||
builder.add("grid-template-rows", row)
|
builder.add("grid-template-rows", row)
|
||||||
|
|
@ -402,12 +342,12 @@ func (resizable *resizableData) cssStyle(self View, builder cssBuilder) {
|
||||||
|
|
||||||
func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
||||||
side := resizable.getSide()
|
side := resizableSide(resizable)
|
||||||
left := 1
|
left := 1
|
||||||
top := 1
|
top := 1
|
||||||
leftSide := (side & LeftSide) != 0
|
leftSide := (side & LeftSide) != 0
|
||||||
rightSide := (side & RightSide) != 0
|
rightSide := (side & RightSide) != 0
|
||||||
w := resizable.resizeBorderWidth().cssString("4px", resizable.Session())
|
w := resizableBorderWidth(resizable).cssString("4px", resizable.Session())
|
||||||
|
|
||||||
if leftSide {
|
if leftSide {
|
||||||
left = 2
|
left = 2
|
||||||
|
|
@ -484,14 +424,13 @@ func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resizable.content) > 0 {
|
if view := resizable.content(); view != nil {
|
||||||
view := resizable.content[0]
|
|
||||||
view.addToCSSStyle(map[string]string{
|
view.addToCSSStyle(map[string]string{
|
||||||
"grid-column-start": strconv.Itoa(left),
|
"grid-column-start": strconv.Itoa(left),
|
||||||
"grid-column-end": strconv.Itoa(left + 1),
|
"grid-column-end": strconv.Itoa(left + 1),
|
||||||
"grid-row-start": strconv.Itoa(top),
|
"grid-row-start": strconv.Itoa(top),
|
||||||
"grid-row-end": strconv.Itoa(top + 1),
|
"grid-row-end": strconv.Itoa(top + 1),
|
||||||
})
|
})
|
||||||
viewHTML(view, buffer)
|
viewHTML(view, buffer, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,23 @@ package rui
|
||||||
|
|
||||||
// ResizeEvent is the constant for "resize-event" property tag.
|
// ResizeEvent is the constant for "resize-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when the view changes its size.
|
// Is fired when the view changes its size.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, frame rui.Frame)`.
|
//
|
||||||
|
// func(view rui.View, frame rui.Frame)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// frame - New offset and size of the view's visible area.
|
// - frame - New offset and size of the view's visible area.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(frame rui.Frame)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(frame rui.Frame)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
const ResizeEvent = "resize-event"
|
// func()
|
||||||
|
const ResizeEvent PropertyName = "resize-event"
|
||||||
|
|
||||||
func (view *viewData) onResize(self View, x, y, width, height float64) {
|
func (view *viewData) onResize(self View, x, y, width, height float64) {
|
||||||
view.frame.Left = x
|
view.frame.Left = x
|
||||||
|
|
@ -31,21 +33,20 @@ func (view *viewData) onResize(self View, x, y, width, height float64) {
|
||||||
func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) {
|
func (view *viewData) onItemResize(self View, index string, x, y, width, height float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *viewData) setFrameListener(tag string, value any) bool {
|
/*
|
||||||
listeners, ok := valueToEventListeners[View, Frame](value)
|
func setFrameListener(properties Properties, tag PropertyName, value any) bool {
|
||||||
if !ok {
|
if listeners, ok := valueToOneArgEventListeners[View, Frame](value); ok {
|
||||||
notCompatibleType(tag, value)
|
if len(listeners) == 0 {
|
||||||
return false
|
properties.setRaw(tag, nil)
|
||||||
|
} else {
|
||||||
|
properties.setRaw(tag, listeners)
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
notCompatibleType(tag, value)
|
||||||
if listeners == nil {
|
return false
|
||||||
delete(view.properties, tag)
|
|
||||||
} else {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
}
|
|
||||||
view.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (view *viewData) setNoResizeEvent() {
|
func (view *viewData) setNoResizeEvent() {
|
||||||
view.noResizeEvent = true
|
view.noResizeEvent = true
|
||||||
|
|
@ -74,9 +75,7 @@ func (view *viewData) Frame() Frame {
|
||||||
// GetViewFrame returns the size and location of view's viewport.
|
// 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
|
// If the second argument (subviewID) is not specified or it is "" then the value of the first argument (view) is returned
|
||||||
func GetViewFrame(view View, subviewID ...string) Frame {
|
func GetViewFrame(view View, subviewID ...string) Frame {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
if view == nil {
|
||||||
return Frame{}
|
return Frame{}
|
||||||
}
|
}
|
||||||
|
|
@ -86,5 +85,5 @@ 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
|
// 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
|
// 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) {
|
func GetResizeListeners(view View, subviewID ...string) []func(View, Frame) {
|
||||||
return getEventListeners[View, Frame](view, subviewID, ResizeEvent)
|
return getOneArgEventListeners[View, Frame](view, subviewID, ResizeEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,23 @@ package rui
|
||||||
|
|
||||||
// ScrollEvent is the constant for "scroll-event" property tag.
|
// ScrollEvent is the constant for "scroll-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when the content of the view is scrolled.
|
// Is fired when the content of the view is scrolled.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, frame rui.Frame)`.
|
//
|
||||||
|
// func(view rui.View, frame rui.Frame)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// frame - New offset and size of the view's visible area.
|
// - frame - New offset and size of the view's visible area.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(frame rui.Frame)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(frame rui.Frame)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
const ScrollEvent = "scroll-event"
|
// func()
|
||||||
|
const ScrollEvent PropertyName = "scroll-event"
|
||||||
|
|
||||||
func (view *viewData) onScroll(self View, x, y, width, height float64) {
|
func (view *viewData) onScroll(self View, x, y, width, height float64) {
|
||||||
view.scroll.Left = x
|
view.scroll.Left = x
|
||||||
|
|
@ -42,9 +44,7 @@ func (view *viewData) setScroll(x, y, width, height float64) {
|
||||||
// GetViewScroll returns ...
|
// GetViewScroll returns ...
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned
|
||||||
func GetViewScroll(view View, subviewID ...string) Frame {
|
func GetViewScroll(view View, subviewID ...string) Frame {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
if view == nil {
|
||||||
return Frame{}
|
return Frame{}
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +54,7 @@ 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
|
// 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
|
// 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) {
|
func GetScrollListeners(view View, subviewID ...string) []func(View, Frame) {
|
||||||
return getEventListeners[View, Frame](view, subviewID, ResizeEvent)
|
return getOneArgEventListeners[View, Frame](view, subviewID, ResizeEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScrollTo scrolls the view's content to the given position.
|
// ScrollTo scrolls the view's content to the given position.
|
||||||
|
|
@ -71,10 +71,7 @@ func ScrollViewTo(view View, subviewID string, x, y float64) {
|
||||||
// ScrollViewToEnd scrolls the view's content to the start of view.
|
// 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
|
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used
|
||||||
func ScrollViewToStart(view View, subviewID ...string) {
|
func ScrollViewToStart(view View, subviewID ...string) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
view.Session().callFunc("scrollToStart", view.htmlID())
|
view.Session().callFunc("scrollToStart", view.htmlID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -82,10 +79,7 @@ func ScrollViewToStart(view View, subviewID ...string) {
|
||||||
// ScrollViewToEnd scrolls the view's content to the end of view.
|
// 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
|
// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used
|
||||||
func ScrollViewToEnd(view View, subviewID ...string) {
|
func ScrollViewToEnd(view View, subviewID ...string) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
view.Session().callFunc("scrollToEnd", view.htmlID())
|
view.Session().callFunc("scrollToEnd", view.htmlID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
session.go
20
session.go
|
|
@ -89,11 +89,11 @@ type Session interface {
|
||||||
RootView() View
|
RootView() View
|
||||||
// Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument.
|
// Get returns a value of the view (with id defined by the first argument) property with name defined by the second argument.
|
||||||
// The type of return value depends on the property. If the property is not set then nil is returned.
|
// The type of return value depends on the property. If the property is not set then nil is returned.
|
||||||
Get(viewID, tag string) any
|
Get(viewID string, tag PropertyName) any
|
||||||
// Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument.
|
// Set sets the value (third argument) of the property (second argument) of the view with id defined by the first argument.
|
||||||
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
// Return "true" if the value has been set, in the opposite case "false" are returned and
|
||||||
// a description of the error is written to the log
|
// a description of the error is written to the log
|
||||||
Set(viewID, tag string, value any) bool
|
Set(viewID string, tag PropertyName, value any) bool
|
||||||
|
|
||||||
// DownloadFile downloads (saves) on the client side the file located at the specified path on the server.
|
// DownloadFile downloads (saves) on the client side the file located at the specified path on the server.
|
||||||
DownloadFile(path string)
|
DownloadFile(path string)
|
||||||
|
|
@ -134,7 +134,7 @@ type Session interface {
|
||||||
|
|
||||||
viewByHTMLID(id string) View
|
viewByHTMLID(id string) View
|
||||||
nextViewID() string
|
nextViewID() string
|
||||||
styleProperty(styleTag, property string) any
|
styleProperty(styleTag string, propertyTag PropertyName) any
|
||||||
|
|
||||||
setBridge(events chan DataObject, bridge bridge)
|
setBridge(events chan DataObject, bridge bridge)
|
||||||
writeInitScript(writer *strings.Builder)
|
writeInitScript(writer *strings.Builder)
|
||||||
|
|
@ -270,7 +270,7 @@ func (session *sessionData) close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) styleProperty(styleTag, propertyTag string) any {
|
func (session *sessionData) styleProperty(styleTag string, propertyTag PropertyName) any {
|
||||||
if style := session.getCurrentTheme().style(styleTag); style != nil {
|
if style := session.getCurrentTheme().style(styleTag); style != nil {
|
||||||
return style.getRaw(propertyTag)
|
return style.getRaw(propertyTag)
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +329,7 @@ func (session *sessionData) writeInitScript(writer *strings.Builder) {
|
||||||
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
viewHTML(session.rootView, buffer)
|
viewHTML(session.rootView, buffer, "")
|
||||||
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
|
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
|
||||||
writer.WriteString(text)
|
writer.WriteString(text)
|
||||||
writer.WriteString("';\nscanElementsSize();")
|
writer.WriteString("';\nscanElementsSize();")
|
||||||
|
|
@ -360,7 +360,7 @@ func (session *sessionData) reload() {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
viewHTML(session.rootView, buffer)
|
viewHTML(session.rootView, buffer, "")
|
||||||
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
||||||
session.bridge.callFunc("scanElementsSize")
|
session.bridge.callFunc("scanElementsSize")
|
||||||
}
|
}
|
||||||
|
|
@ -376,14 +376,14 @@ func (session *sessionData) setIgnoreViewUpdates(ignore bool) {
|
||||||
session.ignoreUpdates = ignore
|
session.ignoreUpdates = ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) Get(viewID, tag string) any {
|
func (session *sessionData) Get(viewID string, tag PropertyName) any {
|
||||||
if view := ViewByID(session.RootView(), viewID); view != nil {
|
if view := ViewByID(session.RootView(), viewID); view != nil {
|
||||||
return view.Get(tag)
|
return view.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *sessionData) Set(viewID, tag string, value any) bool {
|
func (session *sessionData) Set(viewID string, tag PropertyName, value any) bool {
|
||||||
if view := ViewByID(session.RootView(), viewID); view != nil {
|
if view := ViewByID(session.RootView(), viewID); view != nil {
|
||||||
return view.Set(tag, value)
|
return view.Set(tag, value)
|
||||||
}
|
}
|
||||||
|
|
@ -785,10 +785,10 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||||
if viewID, ok := data.PropertyValue("id"); ok {
|
if viewID, ok := data.PropertyValue("id"); ok {
|
||||||
if viewID != "body" {
|
if viewID != "body" {
|
||||||
if view := session.viewByHTMLID(viewID); view != nil {
|
if view := session.viewByHTMLID(viewID); view != nil {
|
||||||
view.handleCommand(view, command, data)
|
view.handleCommand(view, PropertyName(command), data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if command == KeyDownEvent {
|
if command == string(KeyDownEvent) {
|
||||||
var event KeyEvent
|
var event KeyEvent
|
||||||
event.init(data)
|
event.init(data)
|
||||||
session.hotKey(event)
|
session.hotKey(event)
|
||||||
|
|
|
||||||
|
|
@ -339,7 +339,7 @@ func (session *sessionData) SetLanguage(lang string) {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
viewHTML(session.rootView, buffer)
|
viewHTML(session.rootView, buffer, "")
|
||||||
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
279
shadow.go
279
shadow.go
|
|
@ -5,106 +5,110 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants for [ViewShadow] specific properties
|
// Constants for [ShadowProperty] specific properties
|
||||||
const (
|
const (
|
||||||
// ColorTag is the constant for "color" property tag.
|
// ColorTag is the constant for "color" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ColumnSeparatorProperty`, `BorderProperty`, `OutlineProperty`, `ViewShadow`.
|
// Used by ColumnSeparatorProperty, BorderProperty, OutlineProperty, ShadowProperty.
|
||||||
|
//
|
||||||
|
// # Usage in ColumnSeparatorProperty
|
||||||
//
|
//
|
||||||
// Usage in `ColumnSeparatorProperty`:
|
|
||||||
// Line color.
|
// Line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See Color description for more details.
|
||||||
|
//
|
||||||
|
// # Usage in BorderProperty
|
||||||
//
|
//
|
||||||
// Usage in `BorderProperty`:
|
|
||||||
// Border line color.
|
// Border line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See Color description for more details.
|
||||||
|
//
|
||||||
|
// # Usage in OutlineProperty
|
||||||
//
|
//
|
||||||
// Usage in `OutlineProperty`:
|
|
||||||
// Outline line color.
|
// Outline line color.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See Color description for more details.
|
||||||
|
//
|
||||||
|
// # Usage in ShadowProperty
|
||||||
//
|
//
|
||||||
// Usage in `ViewShadow`:
|
|
||||||
// Color property of the shadow.
|
// Color property of the shadow.
|
||||||
//
|
//
|
||||||
// Supported types: `Color`, `string`.
|
// Supported types: Color, string.
|
||||||
//
|
//
|
||||||
// Internal type is `Color`, other types converted to it during assignment.
|
// Internal type is Color, other types converted to it during assignment.
|
||||||
// See `Color` description for more details.
|
// See Color description for more details.
|
||||||
ColorTag = "color"
|
ColorTag PropertyName = "color"
|
||||||
|
|
||||||
// Inset is the constant for "inset" property tag.
|
// Inset is the constant for "inset" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ViewShadow`.
|
// Used by ShadowProperty.
|
||||||
// Controls whether to draw shadow inside the frame or outside. Inset shadows are drawn inside the border(even transparent
|
// Controls whether to draw shadow inside the frame or outside. Inset shadows are drawn inside the border(even transparent
|
||||||
// ones), above the background, but below content.
|
// ones), above the background, but below content.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Drop shadow inside the frame(as if the content was depressed inside the box).
|
// - true, 1, "true", "yes", "on", "1" - Drop shadow inside the frame(as if the content was depressed inside the box).
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Shadow is assumed to be a drop shadow(as if the box were raised above the content).
|
// - false, 0, "false", "no", "off", "0" - Shadow is assumed to be a drop shadow(as if the box were raised above the content).
|
||||||
Inset = "inset"
|
Inset PropertyName = "inset"
|
||||||
|
|
||||||
// XOffset is the constant for "x-offset" property tag.
|
// XOffset is the constant for "x-offset" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ViewShadow`.
|
// Used by ShadowProperty.
|
||||||
// Determines the shadow horizontal offset. Negative values place the shadow to the left of the element.
|
// Determines the shadow horizontal offset. Negative values place the shadow to the left of the element.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
XOffset = "x-offset"
|
XOffset PropertyName = "x-offset"
|
||||||
|
|
||||||
// YOffset is the constant for "y-offset" property tag.
|
// YOffset is the constant for "y-offset" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ViewShadow`.
|
// Used by ShadowProperty.
|
||||||
// Determines the shadow vertical offset. Negative values place the shadow above the element.
|
// Determines the shadow vertical offset. Negative values place the shadow above the element.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
YOffset = "y-offset"
|
YOffset PropertyName = "y-offset"
|
||||||
|
|
||||||
// BlurRadius is the constant for "blur" property tag.
|
// BlurRadius is the constant for "blur" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ViewShadow`.
|
// Used by ShadowProperty.
|
||||||
// Determines the radius of the blur effect. The larger this value, the bigger the blur, so the shadow becomes bigger and
|
// Determines the radius of the blur effect. The larger this value, the bigger the blur, so the shadow becomes bigger and
|
||||||
// lighter. Negative values are not allowed.
|
// lighter. Negative values are not allowed.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
BlurRadius = "blur"
|
BlurRadius PropertyName = "blur"
|
||||||
|
|
||||||
// SpreadRadius is the constant for "spread-radius" property tag.
|
// SpreadRadius is the constant for "spread-radius" property tag.
|
||||||
//
|
//
|
||||||
// Used by `ViewShadow`.
|
// Used by ShadowProperty.
|
||||||
// Positive values will cause the shadow to expand and grow bigger, negative values will cause the shadow to shrink.
|
// Positive values will cause the shadow to expand and grow bigger, negative values will cause the shadow to shrink.
|
||||||
//
|
//
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
//
|
//
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
// See `SizeUnit` description for more details.
|
// See SizeUnit description for more details.
|
||||||
SpreadRadius = "spread-radius"
|
SpreadRadius PropertyName = "spread-radius"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ViewShadow contains attributes of the view shadow
|
// ShadowProperty contains attributes of the view shadow
|
||||||
type ViewShadow interface {
|
type ShadowProperty interface {
|
||||||
Properties
|
Properties
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
stringWriter
|
stringWriter
|
||||||
|
|
@ -113,42 +117,36 @@ type ViewShadow interface {
|
||||||
visible(session Session) bool
|
visible(session Session) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewShadowData struct {
|
type shadowPropertyData struct {
|
||||||
propertyList
|
dataProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewViewShadow create the new shadow for a view. Arguments:
|
// NewShadow create the new shadow property for a view. Arguments:
|
||||||
//
|
// - offsetX, offsetY is x and y offset of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
// offsetX, offsetY is x and y offset of the shadow;
|
// - blurRadius is the blur radius of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
//
|
// - spreadRadius is the spread radius of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
// blurRadius is the blur radius of the shadow;
|
// - color is the color of the shadow.
|
||||||
//
|
func NewShadow[xOffsetType SizeUnit | int | float64, yOffsetType SizeUnit | int | float64, blurType SizeUnit | int | float64, spreadType SizeUnit | int | float64](
|
||||||
// spreadRadius is the spread radius of the shadow;
|
xOffset xOffsetType, yOffset yOffsetType, blurRadius blurType, spreadRadius spreadType, color Color) ShadowProperty {
|
||||||
//
|
return NewShadowProperty(Params{
|
||||||
// color is the color of the shadow.
|
XOffset: xOffset,
|
||||||
func NewViewShadow(offsetX, offsetY, blurRadius, spreadRadius SizeUnit, color Color) ViewShadow {
|
YOffset: yOffset,
|
||||||
return NewShadowWithParams(Params{
|
|
||||||
XOffset: offsetX,
|
|
||||||
YOffset: offsetY,
|
|
||||||
BlurRadius: blurRadius,
|
BlurRadius: blurRadius,
|
||||||
SpreadRadius: spreadRadius,
|
SpreadRadius: spreadRadius,
|
||||||
ColorTag: color,
|
ColorTag: color,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInsetViewShadow create the new inset shadow for a view. Arguments:
|
// NewInsetShadow create the new inset shadow property for a view. Arguments:
|
||||||
//
|
// - offsetX, offsetY is x and y offset of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
// offsetX, offsetY is x and y offset of the shadow;
|
// - blurRadius is the blur radius of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
//
|
// - spreadRadius is the spread radius of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
// blurRadius is the blur radius of the shadow;
|
// - color is the color of the shadow.
|
||||||
//
|
func NewInsetShadow[xOffsetType SizeUnit | int | float64, yOffsetType SizeUnit | int | float64, blurType SizeUnit | int | float64, spreadType SizeUnit | int | float64](
|
||||||
// spreadRadius is the spread radius of the shadow;
|
xOffset xOffsetType, yOffset yOffsetType, blurRadius blurType, spreadRadius spreadType, color Color) ShadowProperty {
|
||||||
//
|
return NewShadowProperty(Params{
|
||||||
// color is the color of the shadow.
|
XOffset: xOffset,
|
||||||
func NewInsetViewShadow(offsetX, offsetY, blurRadius, spreadRadius SizeUnit, color Color) ViewShadow {
|
YOffset: yOffset,
|
||||||
return NewShadowWithParams(Params{
|
|
||||||
XOffset: offsetX,
|
|
||||||
YOffset: offsetY,
|
|
||||||
BlurRadius: blurRadius,
|
BlurRadius: blurRadius,
|
||||||
SpreadRadius: spreadRadius,
|
SpreadRadius: spreadRadius,
|
||||||
ColorTag: color,
|
ColorTag: color,
|
||||||
|
|
@ -156,82 +154,57 @@ func NewInsetViewShadow(offsetX, offsetY, blurRadius, spreadRadius SizeUnit, col
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTextShadow create the new text shadow. Arguments:
|
// NewTextShadow create the new text shadow property. Arguments:
|
||||||
//
|
// - offsetX, offsetY is the x- and y-offset of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
// offsetX, offsetY is the x- and y-offset of the shadow;
|
// - blurRadius is the blur radius of the shadow (if the argument is specified as int or float64, the value is considered to be in pixels);
|
||||||
//
|
// - color is the color of the shadow.
|
||||||
// blurRadius is the blur radius of the shadow;
|
func NewTextShadow[xOffsetType SizeUnit | int | float64, yOffsetType SizeUnit | int | float64, blurType SizeUnit | int | float64](
|
||||||
//
|
xOffset xOffsetType, yOffset yOffsetType, blurRadius blurType, color Color) ShadowProperty {
|
||||||
// color is the color of the shadow.
|
return NewShadowProperty(Params{
|
||||||
func NewTextShadow(offsetX, offsetY, blurRadius SizeUnit, color Color) ViewShadow {
|
XOffset: xOffset,
|
||||||
return NewShadowWithParams(Params{
|
YOffset: yOffset,
|
||||||
XOffset: offsetX,
|
|
||||||
YOffset: offsetY,
|
|
||||||
BlurRadius: blurRadius,
|
BlurRadius: blurRadius,
|
||||||
ColorTag: color,
|
ColorTag: color,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewShadowWithParams create the new shadow for a view.
|
// NewShadowProperty create the new shadow property for a view.
|
||||||
|
//
|
||||||
// The following properties can be used:
|
// The following properties can be used:
|
||||||
//
|
// - "color" (ColorTag). Determines the color of the shadow (Color);
|
||||||
// "color" (ColorTag). Determines the color of the shadow (Color);
|
// - "x-offset" (XOffset). Determines the shadow horizontal offset (SizeUnit);
|
||||||
//
|
// - "y-offset" (YOffset). Determines the shadow vertical offset (SizeUnit);
|
||||||
// "x-offset" (XOffset). Determines the shadow horizontal offset (SizeUnit);
|
// - "blur" (BlurRadius). Determines the radius of the blur effect (SizeUnit);
|
||||||
//
|
// - "spread-radius" (SpreadRadius). Positive values (SizeUnit) will cause the shadow to expand and grow bigger, negative values will cause the shadow to shrink;
|
||||||
// "y-offset" (YOffset). Determines the shadow vertical offset (SizeUnit);
|
// - "inset" (Inset). Controls (bool) whether to draw shadow inside the frame or outside.
|
||||||
//
|
func NewShadowProperty(params Params) ShadowProperty {
|
||||||
// "blur" (BlurRadius). Determines the radius of the blur effect (SizeUnit);
|
shadow := new(shadowPropertyData)
|
||||||
//
|
shadow.init()
|
||||||
// "spread-radius" (SpreadRadius). Positive values (SizeUnit) will cause the shadow to expand and grow bigger, negative values will cause the shadow to shrink;
|
|
||||||
//
|
|
||||||
// "inset" (Inset). Controls (bool) whether to draw shadow inside the frame or outside.
|
|
||||||
func NewShadowWithParams(params Params) ViewShadow {
|
|
||||||
shadow := new(viewShadowData)
|
|
||||||
shadow.propertyList.init()
|
|
||||||
if params != nil {
|
if params != nil {
|
||||||
for _, tag := range []string{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius} {
|
for _, tag := range []PropertyName{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius} {
|
||||||
if value, ok := params[tag]; ok && value != nil {
|
if value, ok := params[tag]; ok && value != nil {
|
||||||
shadow.Set(tag, value)
|
shadow.set(shadow, tag, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseViewShadow parse DataObject and create ViewShadow object
|
// parseShadowProperty parse DataObject and create ShadowProperty object
|
||||||
func parseViewShadow(object DataObject) ViewShadow {
|
func parseShadowProperty(object DataObject) ShadowProperty {
|
||||||
shadow := new(viewShadowData)
|
shadow := new(shadowPropertyData)
|
||||||
shadow.propertyList.init()
|
shadow.init()
|
||||||
parseProperties(shadow, object)
|
parseProperties(shadow, object)
|
||||||
return shadow
|
return shadow
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) Remove(tag string) {
|
func (shadow *shadowPropertyData) init() {
|
||||||
delete(shadow.properties, strings.ToLower(tag))
|
shadow.dataProperty.init()
|
||||||
|
shadow.supportedProperties = []PropertyName{ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) Set(tag string, value any) bool {
|
func (shadow *shadowPropertyData) cssStyle(buffer *strings.Builder, session Session, lead string) bool {
|
||||||
if value == nil {
|
|
||||||
shadow.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
|
||||||
case ColorTag, Inset, XOffset, YOffset, BlurRadius, SpreadRadius:
|
|
||||||
return shadow.propertyList.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by Shadow`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shadow *viewShadowData) Get(tag string) any {
|
|
||||||
return shadow.propertyList.Get(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session, lead string) bool {
|
|
||||||
color, _ := colorProperty(shadow, ColorTag, session)
|
color, _ := colorProperty(shadow, ColorTag, session)
|
||||||
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
||||||
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
||||||
|
|
@ -263,7 +236,7 @@ func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session,
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) cssTextStyle(buffer *strings.Builder, session Session, lead string) bool {
|
func (shadow *shadowPropertyData) cssTextStyle(buffer *strings.Builder, session Session, lead string) bool {
|
||||||
color, _ := colorProperty(shadow, ColorTag, session)
|
color, _ := colorProperty(shadow, ColorTag, session)
|
||||||
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
||||||
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
||||||
|
|
@ -287,7 +260,7 @@ func (shadow *viewShadowData) cssTextStyle(buffer *strings.Builder, session Sess
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) visible(session Session) bool {
|
func (shadow *shadowPropertyData) visible(session Session) bool {
|
||||||
color, _ := colorProperty(shadow, ColorTag, session)
|
color, _ := colorProperty(shadow, ColorTag, session)
|
||||||
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
offsetX, _ := sizeProperty(shadow, XOffset, session)
|
||||||
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
offsetY, _ := sizeProperty(shadow, YOffset, session)
|
||||||
|
|
@ -304,11 +277,11 @@ func (shadow *viewShadowData) visible(session Session) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) String() string {
|
func (shadow *shadowPropertyData) String() string {
|
||||||
return runStringWriter(shadow)
|
return runStringWriter(shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string) {
|
func (shadow *shadowPropertyData) writeString(buffer *strings.Builder, indent string) {
|
||||||
buffer.WriteString("_{ ")
|
buffer.WriteString("_{ ")
|
||||||
comma := false
|
comma := false
|
||||||
for _, tag := range shadow.AllTags() {
|
for _, tag := range shadow.AllTags() {
|
||||||
|
|
@ -316,7 +289,7 @@ func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string
|
||||||
if comma {
|
if comma {
|
||||||
buffer.WriteString(", ")
|
buffer.WriteString(", ")
|
||||||
}
|
}
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
comma = true
|
comma = true
|
||||||
|
|
@ -325,41 +298,41 @@ func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string
|
||||||
buffer.WriteString(" }")
|
buffer.WriteString(" }")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (properties *propertyList) setShadow(tag string, value any) bool {
|
func setShadowProperty(properties Properties, tag PropertyName, value any) bool {
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
delete(properties.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case ViewShadow:
|
case ShadowProperty:
|
||||||
properties.properties[tag] = []ViewShadow{value}
|
properties.setRaw(tag, []ShadowProperty{value})
|
||||||
|
|
||||||
case []ViewShadow:
|
case []ShadowProperty:
|
||||||
if len(value) == 0 {
|
if len(value) == 0 {
|
||||||
delete(properties.properties, tag)
|
properties.setRaw(tag, nil)
|
||||||
} else {
|
} else {
|
||||||
properties.properties[tag] = value
|
properties.setRaw(tag, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
case DataValue:
|
case DataValue:
|
||||||
if !value.IsObject() {
|
if !value.IsObject() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
properties.properties[tag] = []ViewShadow{parseViewShadow(value.Object())}
|
properties.setRaw(tag, []ShadowProperty{parseShadowProperty(value.Object())})
|
||||||
|
|
||||||
case []DataValue:
|
case []DataValue:
|
||||||
shadows := []ViewShadow{}
|
shadows := []ShadowProperty{}
|
||||||
for _, data := range value {
|
for _, data := range value {
|
||||||
if data.IsObject() {
|
if data.IsObject() {
|
||||||
shadows = append(shadows, parseViewShadow(data.Object()))
|
shadows = append(shadows, parseShadowProperty(data.Object()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(shadows) == 0 {
|
if len(shadows) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
properties.properties[tag] = shadows
|
properties.setRaw(tag, shadows)
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
obj := NewDataObject(value)
|
obj := NewDataObject(value)
|
||||||
|
|
@ -367,7 +340,7 @@ func (properties *propertyList) setShadow(tag string, value any) bool {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
properties.properties[tag] = []ViewShadow{parseViewShadow(obj)}
|
properties.setRaw(tag, []ShadowProperty{parseShadowProperty(obj)})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
|
|
@ -377,20 +350,20 @@ func (properties *propertyList) setShadow(tag string, value any) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getShadows(properties Properties, tag string) []ViewShadow {
|
func getShadows(properties Properties, tag PropertyName) []ShadowProperty {
|
||||||
if value := properties.Get(tag); value != nil {
|
if value := properties.Get(tag); value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case []ViewShadow:
|
case []ShadowProperty:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
case ViewShadow:
|
case ShadowProperty:
|
||||||
return []ViewShadow{value}
|
return []ShadowProperty{value}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []ViewShadow{}
|
return []ShadowProperty{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func shadowCSS(properties Properties, tag string, session Session) string {
|
func shadowCSS(properties Properties, tag PropertyName, session Session) string {
|
||||||
shadows := getShadows(properties, tag)
|
shadows := getShadows(properties, tag)
|
||||||
if len(shadows) == 0 {
|
if len(shadows) == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SizeFunc describes a function that calculates the SizeUnit size.
|
// SizeFunc describes a function that calculates the SizeUnit size.
|
||||||
|
//
|
||||||
// Used as the value of the SizeUnit properties.
|
// Used as the value of the SizeUnit properties.
|
||||||
|
//
|
||||||
// "min", "max", "clamp", "sum", "sub", "mul", "div", mod,
|
// "min", "max", "clamp", "sum", "sub", "mul", "div", mod,
|
||||||
// "round", "round-up", "round-down" and "round-to-zero" functions are available.
|
// "round", "round-up", "round-down" and "round-to-zero" functions are available.
|
||||||
type SizeFunc interface {
|
type SizeFunc interface {
|
||||||
|
|
|
||||||
40
sizeUnit.go
40
sizeUnit.go
|
|
@ -62,53 +62,53 @@ func AutoSize() SizeUnit {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Px creates SizeUnit with SizeInPixel type
|
// Px creates SizeUnit with SizeInPixel type
|
||||||
func Px(value float64) SizeUnit {
|
func Px[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInPixel, value, nil}
|
return SizeUnit{Type: SizeInPixel, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Em creates SizeUnit with SizeInEM type
|
// Em creates SizeUnit with SizeInEM type
|
||||||
func Em(value float64) SizeUnit {
|
func Em[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInEM, value, nil}
|
return SizeUnit{Type: SizeInEM, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ex creates SizeUnit with SizeInEX type
|
// Ex creates SizeUnit with SizeInEX type
|
||||||
func Ex(value float64) SizeUnit {
|
func Ex[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInEX, value, nil}
|
return SizeUnit{Type: SizeInEX, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Percent creates SizeUnit with SizeInDIP type
|
// Percent creates SizeUnit with SizeInDIP type
|
||||||
func Percent(value float64) SizeUnit {
|
func Percent[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInPercent, value, nil}
|
return SizeUnit{Type: SizeInPercent, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pt creates SizeUnit with SizeInPt type
|
// Pt creates SizeUnit with SizeInPt type
|
||||||
func Pt(value float64) SizeUnit {
|
func Pt[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInPt, value, nil}
|
return SizeUnit{Type: SizeInPt, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pc creates SizeUnit with SizeInPc type
|
// Pc creates SizeUnit with SizeInPc type
|
||||||
func Pc(value float64) SizeUnit {
|
func Pc[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInPc, value, nil}
|
return SizeUnit{Type: SizeInPc, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mm creates SizeUnit with SizeInMM type
|
// Mm creates SizeUnit with SizeInMM type
|
||||||
func Mm(value float64) SizeUnit {
|
func Mm[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInMM, value, nil}
|
return SizeUnit{Type: SizeInMM, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cm creates SizeUnit with SizeInCM type
|
// Cm creates SizeUnit with SizeInCM type
|
||||||
func Cm(value float64) SizeUnit {
|
func Cm[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInCM, value, nil}
|
return SizeUnit{Type: SizeInCM, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inch creates SizeUnit with SizeInInch type
|
// Inch creates SizeUnit with SizeInInch type
|
||||||
func Inch(value float64) SizeUnit {
|
func Inch[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInInch, value, nil}
|
return SizeUnit{Type: SizeInInch, Value: float64(value), Function: nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fr creates SizeUnit with SizeInFraction type
|
// Fr creates SizeUnit with SizeInFraction type
|
||||||
func Fr(value float64) SizeUnit {
|
func Fr[T float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value T) SizeUnit {
|
||||||
return SizeUnit{SizeInFraction, value, nil}
|
return SizeUnit{SizeInFraction, float64(value), nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equal compare two SizeUnit. Return true if SizeUnit are equal
|
// Equal compare two SizeUnit. Return true if SizeUnit are equal
|
||||||
|
|
|
||||||
1058
stackLayout.go
1058
stackLayout.go
File diff suppressed because it is too large
Load Diff
|
|
@ -25,7 +25,7 @@ func NewSvgImageView(session Session, params Params) SvgImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSvgImageView(session Session) View {
|
func newSvgImageView(session Session) View {
|
||||||
return NewSvgImageView(session, nil)
|
return new(svgImageViewData) // NewSvgImageView(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of imageView by default values
|
// Init initialize fields of imageView by default values
|
||||||
|
|
@ -33,14 +33,14 @@ func (imageView *svgImageViewData) init(session Session) {
|
||||||
imageView.viewData.init(session)
|
imageView.viewData.init(session)
|
||||||
imageView.tag = "SvgImageView"
|
imageView.tag = "SvgImageView"
|
||||||
imageView.systemClass = "ruiSvgImageView"
|
imageView.systemClass = "ruiSvgImageView"
|
||||||
|
imageView.normalize = normalizeSvgImageViewTag
|
||||||
|
imageView.set = imageView.setFunc
|
||||||
|
imageView.changed = imageView.propertyChanged
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *svgImageViewData) String() string {
|
func normalizeSvgImageViewTag(tag PropertyName) PropertyName {
|
||||||
return getViewString(imageView, nil)
|
tag = defaultNormalize(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *svgImageViewData) normalizeTag(tag string) string {
|
|
||||||
tag = strings.ToLower(tag)
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Source, "source":
|
case Source, "source":
|
||||||
tag = Content
|
tag = Content
|
||||||
|
|
@ -54,51 +54,29 @@ func (imageView *svgImageViewData) normalizeTag(tag string) string {
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *svgImageViewData) Remove(tag string) {
|
func (imageView *svgImageViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
imageView.remove(imageView.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *svgImageViewData) remove(tag string) {
|
|
||||||
imageView.viewData.remove(tag)
|
|
||||||
|
|
||||||
if imageView.created {
|
|
||||||
switch tag {
|
|
||||||
case Content:
|
|
||||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *svgImageViewData) Set(tag string, value any) bool {
|
|
||||||
return imageView.set(imageView.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (imageView *svgImageViewData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
imageView.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Content:
|
case Content:
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
imageView.properties[Content] = text
|
imageView.setRaw(Content, text)
|
||||||
if imageView.created {
|
return []PropertyName{tag}
|
||||||
updateInnerHTML(imageView.htmlID(), imageView.session)
|
|
||||||
}
|
|
||||||
imageView.propertyChangedEvent(Content)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
notCompatibleType(Source, value)
|
notCompatibleType(Source, value)
|
||||||
return false
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return imageView.viewData.set(tag, value)
|
return imageView.viewData.setFunc(tag, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *svgImageViewData) Get(tag string) any {
|
func (imageView *svgImageViewData) propertyChanged(tag PropertyName) {
|
||||||
return imageView.viewData.get(imageView.normalizeTag(tag))
|
switch tag {
|
||||||
|
case Content:
|
||||||
|
updateInnerHTML(imageView.htmlID(), imageView.Session())
|
||||||
|
|
||||||
|
default:
|
||||||
|
imageView.viewData.propertyChanged(tag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (imageView *svgImageViewData) htmlTag() string {
|
func (imageView *svgImageViewData) htmlTag() string {
|
||||||
|
|
|
||||||
101
tableAdapter.go
101
tableAdapter.go
|
|
@ -9,19 +9,20 @@ type TableAdapter interface {
|
||||||
ColumnCount() int
|
ColumnCount() int
|
||||||
|
|
||||||
// Cell returns the contents of a table cell. The function can return elements of the following types:
|
// Cell returns the contents of a table cell. The function can return elements of the following types:
|
||||||
// * string
|
// - string
|
||||||
// * rune
|
// - rune
|
||||||
// * float32, float64
|
// - float32, float64
|
||||||
// * integer values: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
|
// - integer values: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
|
||||||
// * bool
|
// - bool
|
||||||
// * rui.Color
|
// - rui.Color
|
||||||
// * rui.View
|
// - rui.View
|
||||||
// * fmt.Stringer
|
// - fmt.Stringer
|
||||||
// * rui.VerticalTableJoin, rui.HorizontalTableJoin
|
// - rui.VerticalTableJoin, rui.HorizontalTableJoin
|
||||||
Cell(row, column int) any
|
Cell(row, column int) any
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableColumnStyle describes the style of [TableView] columns.
|
// TableColumnStyle describes the style of [TableView] columns.
|
||||||
|
//
|
||||||
// To set column styles, you must either implement the [TableColumnStyle] interface in the table adapter
|
// To set column styles, you must either implement the [TableColumnStyle] interface in the table adapter
|
||||||
// or assign its separate implementation to the "column-style" property.
|
// or assign its separate implementation to the "column-style" property.
|
||||||
type TableColumnStyle interface {
|
type TableColumnStyle interface {
|
||||||
|
|
@ -30,6 +31,7 @@ type TableColumnStyle interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableRowStyle describes the style of [TableView] rows.
|
// TableRowStyle describes the style of [TableView] rows.
|
||||||
|
//
|
||||||
// To set row styles, you must either implement the [TableRowStyle] interface in the table adapter
|
// To set row styles, you must either implement the [TableRowStyle] interface in the table adapter
|
||||||
// or assign its separate implementation to the "row-style" property.
|
// or assign its separate implementation to the "row-style" property.
|
||||||
type TableRowStyle interface {
|
type TableRowStyle interface {
|
||||||
|
|
@ -38,6 +40,7 @@ type TableRowStyle interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableCellStyle describes the style of [TableView] cells.
|
// TableCellStyle describes the style of [TableView] cells.
|
||||||
|
//
|
||||||
// To set row cells, you must either implement the [TableCellStyle] interface in the table adapter
|
// To set row cells, you must either implement the [TableCellStyle] interface in the table adapter
|
||||||
// or assign its separate implementation to the "cell-style" property.
|
// or assign its separate implementation to the "cell-style" property.
|
||||||
type TableCellStyle interface {
|
type TableCellStyle interface {
|
||||||
|
|
@ -46,7 +49,9 @@ type TableCellStyle interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableAllowCellSelection determines whether [TableView] cell selection is allowed.
|
// TableAllowCellSelection determines whether [TableView] cell selection is allowed.
|
||||||
|
//
|
||||||
// It is only used if the "selection-mode" property is set to CellSelection (1).
|
// It is only used if the "selection-mode" property is set to CellSelection (1).
|
||||||
|
//
|
||||||
// To set cell selection allowing, you must either implement the TableAllowCellSelection interface
|
// To set cell selection allowing, you must either implement the TableAllowCellSelection interface
|
||||||
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
||||||
type TableAllowCellSelection interface {
|
type TableAllowCellSelection interface {
|
||||||
|
|
@ -55,7 +60,9 @@ type TableAllowCellSelection interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableAllowRowSelection determines whether [TableView] row selection is allowed.
|
// TableAllowRowSelection determines whether [TableView] row selection is allowed.
|
||||||
|
//
|
||||||
// It is only used if the "selection-mode" property is set to RowSelection (2).
|
// It is only used if the "selection-mode" property is set to RowSelection (2).
|
||||||
|
//
|
||||||
// To set row selection allowing, you must either implement the TableAllowRowSelection interface
|
// To set row selection allowing, you must either implement the TableAllowRowSelection interface
|
||||||
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
// in the table adapter or assign its separate implementation to the "allow-selection" property.
|
||||||
type TableAllowRowSelection interface {
|
type TableAllowRowSelection interface {
|
||||||
|
|
@ -65,6 +72,7 @@ type TableAllowRowSelection interface {
|
||||||
|
|
||||||
// SimpleTableAdapter is implementation of [TableAdapter] where the content
|
// SimpleTableAdapter is implementation of [TableAdapter] where the content
|
||||||
// defines as [][]any.
|
// defines as [][]any.
|
||||||
|
//
|
||||||
// When you assign [][]any value to the "content" property, it is converted to SimpleTableAdapter
|
// When you assign [][]any value to the "content" property, it is converted to SimpleTableAdapter
|
||||||
type SimpleTableAdapter interface {
|
type SimpleTableAdapter interface {
|
||||||
TableAdapter
|
TableAdapter
|
||||||
|
|
@ -256,78 +264,3 @@ func (style *simpleTableLineStyle) RowStyle(row int) Params {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *tableViewData) setLineStyle(tag string, value any) bool {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case []Params:
|
|
||||||
if len(value) > 0 {
|
|
||||||
style := new(simpleTableLineStyle)
|
|
||||||
style.params = value
|
|
||||||
table.properties[tag] = style
|
|
||||||
} else {
|
|
||||||
delete(table.properties, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataNode:
|
|
||||||
if params := value.ArrayAsParams(); len(params) > 0 {
|
|
||||||
style := new(simpleTableLineStyle)
|
|
||||||
style.params = params
|
|
||||||
table.properties[tag] = style
|
|
||||||
} else {
|
|
||||||
delete(table.properties, tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (table *tableViewData) setRowStyle(value any) bool {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case TableRowStyle:
|
|
||||||
table.properties[RowStyle] = value
|
|
||||||
}
|
|
||||||
return table.setLineStyle(RowStyle, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (table *tableViewData) getRowStyle() TableRowStyle {
|
|
||||||
for _, tag := range []string{RowStyle, Content} {
|
|
||||||
if value := table.getRaw(tag); value != nil {
|
|
||||||
if style, ok := value.(TableRowStyle); ok {
|
|
||||||
return style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (table *tableViewData) setColumnStyle(value any) bool {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case TableColumnStyle:
|
|
||||||
table.properties[ColumnStyle] = value
|
|
||||||
}
|
|
||||||
return table.setLineStyle(ColumnStyle, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (table *tableViewData) getColumnStyle() TableColumnStyle {
|
|
||||||
for _, tag := range []string{ColumnStyle, Content} {
|
|
||||||
if value := table.getRaw(tag); value != nil {
|
|
||||||
if style, ok := value.(TableColumnStyle); ok {
|
|
||||||
return style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (table *tableViewData) getCellStyle() TableCellStyle {
|
|
||||||
for _, tag := range []string{CellStyle, Content} {
|
|
||||||
if value := table.getRaw(tag); value != nil {
|
|
||||||
if style, ok := value.(TableCellStyle); ok {
|
|
||||||
return style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
1101
tableView.go
1101
tableView.go
File diff suppressed because it is too large
Load Diff
|
|
@ -1,17 +1,19 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
import "strings"
|
func newTableCellView(session Session) *tableCellView {
|
||||||
|
view := new(tableCellView)
|
||||||
func (cell *tableCellView) Set(tag string, value any) bool {
|
view.init(session)
|
||||||
return cell.set(strings.ToLower(tag), value)
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *tableCellView) set(tag string, value any) bool {
|
func (cell *tableCellView) init(session Session) {
|
||||||
switch tag {
|
cell.viewData.init(session)
|
||||||
case VerticalAlign:
|
cell.normalize = func(tag PropertyName) PropertyName {
|
||||||
tag = TableVerticalAlign
|
if tag == VerticalAlign {
|
||||||
|
return TableVerticalAlign
|
||||||
|
}
|
||||||
|
return tag
|
||||||
}
|
}
|
||||||
return cell.viewData.set(tag, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
|
func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
|
||||||
|
|
@ -26,13 +28,11 @@ func (cell *tableCellView) cssStyle(self View, builder cssBuilder) {
|
||||||
// GetTableContent returns a TableAdapter which defines the TableView content.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTableContent(view View, subviewID ...string) TableAdapter {
|
func GetTableContent(view View, subviewID ...string) TableAdapter {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if content := view.getRaw(Content); content != nil {
|
||||||
}
|
if adapter, ok := content.(TableAdapter); ok {
|
||||||
|
return adapter
|
||||||
if view != nil {
|
}
|
||||||
if tableView, ok := view.(TableView); ok {
|
|
||||||
return tableView.content()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,13 +42,13 @@ func GetTableContent(view View, subviewID ...string) TableAdapter {
|
||||||
// GetTableRowStyle returns a TableRowStyle which defines styles of TableView rows.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
|
func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
for _, tag := range []PropertyName{RowStyle, Content} {
|
||||||
}
|
if value := view.getRaw(tag); value != nil {
|
||||||
|
if style, ok := value.(TableRowStyle); ok {
|
||||||
if view != nil {
|
return style
|
||||||
if tableView, ok := view.(TableView); ok {
|
}
|
||||||
return tableView.getRowStyle()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,13 +58,13 @@ func GetTableRowStyle(view View, subviewID ...string) TableRowStyle {
|
||||||
// GetTableColumnStyle returns a TableColumnStyle which defines styles of TableView columns.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
|
func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
for _, tag := range []PropertyName{ColumnStyle, Content} {
|
||||||
}
|
if value := view.getRaw(tag); value != nil {
|
||||||
|
if style, ok := value.(TableColumnStyle); ok {
|
||||||
if view != nil {
|
return style
|
||||||
if tableView, ok := view.(TableView); ok {
|
}
|
||||||
return tableView.getColumnStyle()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,14 +74,15 @@ func GetTableColumnStyle(view View, subviewID ...string) TableColumnStyle {
|
||||||
// GetTableCellStyle returns a TableCellStyle which defines styles of TableView cells.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTableCellStyle(view View, subviewID ...string) TableCellStyle {
|
func GetTableCellStyle(view View, subviewID ...string) TableCellStyle {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
for _, tag := range []PropertyName{CellStyle, Content} {
|
||||||
}
|
if value := view.getRaw(tag); value != nil {
|
||||||
|
if style, ok := value.(TableCellStyle); ok {
|
||||||
if view != nil {
|
return style
|
||||||
if tableView, ok := view.(TableView); ok {
|
}
|
||||||
return tableView.getCellStyle()
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -119,15 +120,9 @@ func GetTableFootHeight(view View, subviewID ...string) int {
|
||||||
// If the selection mode is RowSelection (2) then the returned column index is less than 0.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTableCurrent(view View, subviewID ...string) CellIndex {
|
func GetTableCurrent(view View, subviewID ...string) CellIndex {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if selectionMode := GetTableSelectionMode(view); selectionMode != NoneSelection {
|
if selectionMode := GetTableSelectionMode(view); selectionMode != NoneSelection {
|
||||||
if tableView, ok := view.(TableView); ok {
|
return tableViewCurrent(view)
|
||||||
return tableView.getCurrent()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CellIndex{Row: -1, Column: -1}
|
return CellIndex{Row: -1, Column: -1}
|
||||||
|
|
@ -137,84 +132,50 @@ func GetTableCurrent(view View, subviewID ...string) CellIndex {
|
||||||
// If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTableCellClickedListeners(view View, subviewID ...string) []func(TableView, int, int) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellClickedEvent)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(TableCellClickedEvent); value != nil {
|
|
||||||
if result, ok := value.([]func(TableView, int, int)); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []func(TableView, int, int){}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected.
|
// 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 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.
|
// 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) {
|
func GetTableCellSelectedListeners(view View, subviewID ...string) []func(TableView, int, int) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
return getTwoArgEventListeners[TableView, int](view, subviewID, TableCellSelectedEvent)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(TableCellSelectedEvent); value != nil {
|
|
||||||
if result, ok := value.([]func(TableView, int, int)); ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []func(TableView, int, int){}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row.
|
// 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 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.
|
// 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) {
|
func GetTableRowClickedListeners(view View, subviewID ...string) []func(TableView, int) {
|
||||||
return getEventListeners[TableView, int](view, subviewID, TableRowClickedEvent)
|
return getOneArgEventListeners[TableView, int](view, subviewID, TableRowClickedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTableRowSelectedListeners returns listeners of event which occurs when a table row becomes selected.
|
// 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 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.
|
// 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) {
|
func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableView, int) {
|
||||||
return getEventListeners[TableView, int](view, subviewID, TableRowSelectedEvent)
|
return getOneArgEventListeners[TableView, int](view, subviewID, TableRowSelectedEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadTableViewData updates TableView
|
// ReloadTableViewData updates TableView
|
||||||
// If the second argument (subviewID) is not specified or it is "" then updates the first argument (TableView).
|
// If the second argument (subviewID) is not specified or it is "" then updates the first argument (TableView).
|
||||||
func ReloadTableViewData(view View, subviewID ...string) bool {
|
func ReloadTableViewData(view View, subviewID ...string) bool {
|
||||||
var tableView TableView
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if tableView, ok := view.(TableView); ok {
|
||||||
if tableView = TableViewByID(view, subviewID[0]); tableView == nil {
|
tableView.ReloadTableData()
|
||||||
return false
|
return true
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ok bool
|
|
||||||
if tableView, ok = view.(TableView); !ok {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
tableView.ReloadTableData()
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadTableViewCell updates the given table cell.
|
// ReloadTableViewCell updates the given table cell.
|
||||||
// If the last argument (subviewID) is not specified or it is "" then updates the cell of the first argument (TableView).
|
// If the last argument (subviewID) is not specified or it is "" then updates the cell of the first argument (TableView).
|
||||||
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool {
|
func ReloadTableViewCell(row, column int, view View, subviewID ...string) bool {
|
||||||
var tableView TableView
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if tableView, ok := view.(TableView); ok {
|
||||||
if tableView = TableViewByID(view, subviewID[0]); tableView == nil {
|
tableView.ReloadCell(row, column)
|
||||||
return false
|
return true
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ok bool
|
|
||||||
if tableView, ok = view.(TableView); !ok {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
tableView.ReloadCell(row, column)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
699
tabsLayout.go
699
tabsLayout.go
|
|
@ -5,109 +5,113 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants for [TabsLayout] specific properties and events
|
// Constants for [TabsLayout] specific view and events
|
||||||
const (
|
const (
|
||||||
// CurrentTabChangedEvent is the constant for "current-tab-changed" property tag.
|
// CurrentTabChangedEvent is the constant for "current-tab-changed" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Occur when the new tab becomes active.
|
// Occur when the new tab becomes active.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(tabsLayout rui.TabsLayout, newTab, oldTab int)`.
|
//
|
||||||
|
// func(tabsLayout rui.TabsLayout, newTab, oldTab int)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// tabsLayout - Interface of a tabs layout which generated this event,
|
// - tabsLayout - Interface of a tabs layout which generated this event,
|
||||||
// newTab - Index of a new active tab,
|
// - newTab - Index of a new active tab,
|
||||||
// oldTab - Index of an old active tab.
|
// - oldTab - Index of an old active tab.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(tabsLayout rui.TabsLayout, newTab int)`,
|
//
|
||||||
// `func(newTab, oldTab int)`,
|
// func(tabsLayout rui.TabsLayout, newTab int)
|
||||||
// `func(newTab int)`,
|
// func(newTab, oldTab int)
|
||||||
// `func()`.
|
// func(newTab int)
|
||||||
CurrentTabChangedEvent = "current-tab-changed"
|
// func()
|
||||||
|
CurrentTabChangedEvent PropertyName = "current-tab-changed"
|
||||||
|
|
||||||
// Icon is the constant for "icon" property tag.
|
// Icon is the constant for "icon" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Defines the icon name that is displayed in the tab. The property is set for the child view of `TabsLayout`.
|
// Defines the icon name that is displayed in the tab. The property is set for the child view of TabsLayout.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
Icon = "icon"
|
Icon = "icon"
|
||||||
|
|
||||||
// TabCloseButton is the constant for "tab-close-button" property tag.
|
// TabCloseButton is the constant for "tab-close-button" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Controls whether to add close button to a tab(s). This property can be set separately for each child view or for tabs
|
// Controls whether to add close button to a tab(s). This property can be set separately for each child view or for tabs
|
||||||
// layout itself. Property set for child view takes precedence over the value set for tabs layout. Default value is
|
// layout itself. Property set for child view takes precedence over the value set for tabs layout. Default value is
|
||||||
// `false`.
|
// false.
|
||||||
//
|
//
|
||||||
// Supported types: `bool`, `int`, `string`.
|
// Supported types: bool, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Tab(s) has close button.
|
// - true, 1, "true", "yes", "on", "1" - Tab(s) has close button.
|
||||||
// `false` or `0` or "false", "no", "off", "0" - No close button in tab(s).
|
// - false, 0, "false", "no", "off", "0" - No close button in tab(s).
|
||||||
TabCloseButton = "tab-close-button"
|
TabCloseButton PropertyName = "tab-close-button"
|
||||||
|
|
||||||
// TabCloseEvent is the constant for "tab-close-event" property tag.
|
// TabCloseEvent is the constant for "tab-close-event" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Occurs when the user clicks on the tab close button.
|
// Occurs when the user clicks on the tab close button.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(tabsLayout rui.TabsLayout, tab int)`.
|
//
|
||||||
|
// func(tabsLayout rui.TabsLayout, tab int)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// tabsLayout - Interface of a tabs layout which generated this event,
|
// - tabsLayout - Interface of a tabs layout which generated this event,
|
||||||
// tab - Index of the tab.
|
// - tab - Index of the tab.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(tab int)`,
|
//
|
||||||
// `func(tabsLayout rui.TabsLayout)`,
|
// func(tab int)
|
||||||
// `func()`.
|
// func(tabsLayout rui.TabsLayout)
|
||||||
TabCloseEvent = "tab-close-event"
|
// func()
|
||||||
|
TabCloseEvent PropertyName = "tab-close-event"
|
||||||
|
|
||||||
// Tabs is the constant for "tabs" property tag.
|
// Tabs is the constant for "tabs" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Sets where the tabs are located. Default value is "top".
|
// Sets where the tabs are located. Default value is "top".
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// `0`(`TopTabs`) or "top" - Tabs on the top.
|
// - 0 (TopTabs) or "top" - Tabs on the top.
|
||||||
// `1`(`BottomTabs`) or "bottom" - Tabs on the bottom.
|
// - 1 (BottomTabs) or "bottom" - Tabs on the bottom.
|
||||||
// `2`(`LeftTabs`) or "left" - Tabs on the left. Each tab is rotated 90° counterclockwise.
|
// - 2 (LeftTabs) or "left" - Tabs on the left. Each tab is rotated 90° counterclockwise.
|
||||||
// `3`(`RightTabs`) or "right" - Tabs located on the right. Each tab is rotated 90° clockwise.
|
// - 3 (RightTabs) or "right" - Tabs located on the right. Each tab is rotated 90° clockwise.
|
||||||
// `4`(`LeftListTabs`) or "left-list" - Tabs on the left. The tabs are displayed as a list.
|
// - 4 (LeftListTabs) or "left-list" - Tabs on the left. The tabs are displayed as a list.
|
||||||
// `5`(`RightListTabs`) or "right-list" - Tabs on the right. The tabs are displayed as a list.
|
// - 5 (RightListTabs) or "right-list" - Tabs on the right. The tabs are displayed as a list.
|
||||||
// `6`(`HiddenTabs`) or "hidden" - Tabs are hidden.
|
// - 6 (HiddenTabs) or "hidden" - Tabs are hidden.
|
||||||
Tabs = "tabs"
|
Tabs PropertyName = "tabs"
|
||||||
|
|
||||||
// TabBarStyle is the constant for "tab-bar-style" property tag.
|
// TabBarStyle is the constant for "tab-bar-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Set the style for the display of the tab bar. The default value is "ruiTabBar".
|
// Set the style for the display of the tab bar. The default value is "ruiTabBar".
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
TabBarStyle = "tab-bar-style"
|
TabBarStyle PropertyName = "tab-bar-style"
|
||||||
|
|
||||||
// TabStyle is the constant for "tab-style" property tag.
|
// TabStyle is the constant for "tab-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Set the style for the display of the tab. The default value is "ruiTab" or "ruiVerticalTab".
|
// Set the style for the display of the tab. The default value is "ruiTab" or "ruiVerticalTab".
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
TabStyle = "tab-style"
|
TabStyle PropertyName = "tab-style"
|
||||||
|
|
||||||
// CurrentTabStyle is the constant for "current-tab-style" property tag.
|
// CurrentTabStyle is the constant for "current-tab-style" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TabsLayout`.
|
// Used by TabsLayout.
|
||||||
// Set the style for the display of the current(selected) tab. The default value is "ruiCurrentTab" or
|
// Set the style for the display of the current(selected) tab. The default value is "ruiCurrentTab" or
|
||||||
// "ruiCurrentVerticalTab".
|
// "ruiCurrentVerticalTab".
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
CurrentTabStyle = "current-tab-style"
|
CurrentTabStyle PropertyName = "current-tab-style"
|
||||||
|
|
||||||
inactiveTabStyle = "data-inactiveTabStyle"
|
inactiveTabStyle = "data-inactiveTabStyle"
|
||||||
activeTabStyle = "data-activeTabStyle"
|
activeTabStyle = "data-activeTabStyle"
|
||||||
|
|
@ -139,8 +143,6 @@ type TabsLayout interface {
|
||||||
|
|
||||||
type tabsLayoutData struct {
|
type tabsLayoutData struct {
|
||||||
viewsContainerData
|
viewsContainerData
|
||||||
tabListener []func(TabsLayout, int, int)
|
|
||||||
tabCloseListener []func(TabsLayout, int)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTabsLayout create new TabsLayout object and return it
|
// NewTabsLayout create new TabsLayout object and return it
|
||||||
|
|
@ -152,7 +154,8 @@ func NewTabsLayout(session Session, params Params) TabsLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTabsLayout(session Session) View {
|
func newTabsLayout(session Session) View {
|
||||||
return NewTabsLayout(session, nil)
|
//return NewTabsLayout(session, nil)
|
||||||
|
return new(tabsLayoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of ViewsContainer by default values
|
// Init initialize fields of ViewsContainer by default values
|
||||||
|
|
@ -160,345 +163,229 @@ func (tabsLayout *tabsLayoutData) init(session Session) {
|
||||||
tabsLayout.viewsContainerData.init(session)
|
tabsLayout.viewsContainerData.init(session)
|
||||||
tabsLayout.tag = "TabsLayout"
|
tabsLayout.tag = "TabsLayout"
|
||||||
tabsLayout.systemClass = "ruiTabsLayout"
|
tabsLayout.systemClass = "ruiTabsLayout"
|
||||||
tabsLayout.tabListener = []func(TabsLayout, int, int){}
|
tabsLayout.set = tabsLayout.setFunc
|
||||||
tabsLayout.tabCloseListener = []func(TabsLayout, int){}
|
tabsLayout.changed = tabsLayout.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) String() string {
|
func tabsLayoutCurrent(view View, defaultValue int) int {
|
||||||
return getViewString(tabsLayout, nil)
|
result, _ := intProperty(view, Current, view.Session(), defaultValue)
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) currentItem(defaultValue int) int {
|
|
||||||
result, _ := intProperty(tabsLayout, Current, tabsLayout.session, defaultValue)
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) Get(tag string) any {
|
func (tabsLayout *tabsLayoutData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
return tabsLayout.get(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) get(tag string) any {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case CurrentTabChangedEvent:
|
case CurrentTabChangedEvent:
|
||||||
return tabsLayout.tabListener
|
return setTwoArgEventListener[TabsLayout, int](tabsLayout, tag, value)
|
||||||
|
|
||||||
case TabCloseEvent:
|
case TabCloseEvent:
|
||||||
return tabsLayout.tabCloseListener
|
return setOneArgEventListener[TabsLayout, int](tabsLayout, tag, value)
|
||||||
}
|
|
||||||
|
|
||||||
return tabsLayout.viewsContainerData.get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) Remove(tag string) {
|
|
||||||
tabsLayout.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) remove(tag string) {
|
|
||||||
switch tag {
|
|
||||||
case CurrentTabChangedEvent:
|
|
||||||
if len(tabsLayout.tabListener) > 0 {
|
|
||||||
tabsLayout.tabListener = []func(TabsLayout, int, int){}
|
|
||||||
tabsLayout.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case TabCloseEvent:
|
|
||||||
if len(tabsLayout.tabCloseListener) > 0 {
|
|
||||||
tabsLayout.tabCloseListener = []func(TabsLayout, int){}
|
|
||||||
tabsLayout.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case Current:
|
case Current:
|
||||||
oldCurrent := tabsLayout.currentItem(0)
|
tabsLayout.setRaw("old-current", tabsLayoutCurrent(tabsLayout, -1))
|
||||||
delete(tabsLayout.properties, Current)
|
|
||||||
if oldCurrent == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if tabsLayout.created {
|
|
||||||
tabsLayout.session.callFunc("activateTab", tabsLayout.htmlID(), 0)
|
|
||||||
for _, listener := range tabsLayout.tabListener {
|
|
||||||
listener(tabsLayout, 0, oldCurrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case Tabs:
|
|
||||||
delete(tabsLayout.properties, Tabs)
|
|
||||||
if tabsLayout.created {
|
|
||||||
htmlID := tabsLayout.htmlID()
|
|
||||||
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
|
|
||||||
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
|
|
||||||
updateCSSStyle(htmlID, tabsLayout.session)
|
|
||||||
updateInnerHTML(htmlID, tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TabStyle, CurrentTabStyle:
|
|
||||||
delete(tabsLayout.properties, tag)
|
|
||||||
if tabsLayout.created {
|
|
||||||
htmlID := tabsLayout.htmlID()
|
|
||||||
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
|
|
||||||
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
|
|
||||||
updateInnerHTML(htmlID, tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TabCloseButton:
|
|
||||||
delete(tabsLayout.properties, tag)
|
|
||||||
if tabsLayout.created {
|
|
||||||
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
tabsLayout.viewsContainerData.remove(tag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tabsLayout.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) Set(tag string, value any) bool {
|
|
||||||
return tabsLayout.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
tabsLayout.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case CurrentTabChangedEvent:
|
|
||||||
listeners := tabsLayout.valueToTabListeners(value)
|
|
||||||
if listeners == nil {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
tabsLayout.tabListener = listeners
|
|
||||||
|
|
||||||
case TabCloseEvent:
|
|
||||||
listeners, ok := valueToEventListeners[TabsLayout, int](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(TabsLayout, int){}
|
|
||||||
}
|
|
||||||
tabsLayout.tabCloseListener = listeners
|
|
||||||
|
|
||||||
case Current:
|
|
||||||
if current, ok := value.(int); ok && current < 0 {
|
if current, ok := value.(int); ok && current < 0 {
|
||||||
tabsLayout.remove(Current)
|
tabsLayout.setRaw(Current, nil)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldCurrent := tabsLayout.currentItem(-1)
|
return setIntProperty(tabsLayout, Current, value)
|
||||||
if !tabsLayout.setIntProperty(Current, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
current := tabsLayout.currentItem(0)
|
case TabStyle, CurrentTabStyle, TabBarStyle:
|
||||||
if oldCurrent == current {
|
if text, ok := value.(string); ok {
|
||||||
return true
|
return setStringPropertyValue(tabsLayout, tag, text)
|
||||||
}
|
}
|
||||||
if tabsLayout.created {
|
notCompatibleType(tag, value)
|
||||||
tabsLayout.session.callFunc("activateTab", tabsLayout.htmlID(), current)
|
return nil
|
||||||
for _, listener := range tabsLayout.tabListener {
|
}
|
||||||
|
|
||||||
|
return tabsLayout.viewsContainerData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tabsLayout *tabsLayoutData) propertyChanged(tag PropertyName) {
|
||||||
|
switch tag {
|
||||||
|
case Current:
|
||||||
|
session := tabsLayout.Session()
|
||||||
|
current := GetCurrent(tabsLayout)
|
||||||
|
session.callFunc("activateTab", tabsLayout.htmlID(), current)
|
||||||
|
|
||||||
|
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(tabsLayout, current, oldCurrent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case Tabs:
|
case Tabs:
|
||||||
if !tabsLayout.setEnumProperty(Tabs, value, enumProperties[Tabs].values) {
|
htmlID := tabsLayout.htmlID()
|
||||||
return false
|
session := tabsLayout.Session()
|
||||||
}
|
session.updateProperty(htmlID, inactiveTabStyle, tabsLayoutInactiveTabStyle(tabsLayout))
|
||||||
if tabsLayout.created {
|
session.updateProperty(htmlID, activeTabStyle, tabsLayoutActiveTabStyle(tabsLayout))
|
||||||
htmlID := tabsLayout.htmlID()
|
updateCSSStyle(htmlID, session)
|
||||||
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
|
updateInnerHTML(htmlID, session)
|
||||||
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
|
|
||||||
updateCSSStyle(htmlID, tabsLayout.session)
|
|
||||||
updateInnerHTML(htmlID, tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TabStyle, CurrentTabStyle, TabBarStyle:
|
case TabStyle, CurrentTabStyle, TabBarStyle:
|
||||||
if text, ok := value.(string); ok {
|
htmlID := tabsLayout.htmlID()
|
||||||
if text == "" {
|
session := tabsLayout.Session()
|
||||||
delete(tabsLayout.properties, tag)
|
session.updateProperty(htmlID, inactiveTabStyle, tabsLayoutInactiveTabStyle(tabsLayout))
|
||||||
} else {
|
session.updateProperty(htmlID, activeTabStyle, tabsLayoutActiveTabStyle(tabsLayout))
|
||||||
tabsLayout.properties[tag] = text
|
updateInnerHTML(htmlID, session)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if tabsLayout.created {
|
|
||||||
htmlID := tabsLayout.htmlID()
|
|
||||||
tabsLayout.session.updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle())
|
|
||||||
tabsLayout.session.updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle())
|
|
||||||
updateInnerHTML(htmlID, tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TabCloseButton:
|
case TabCloseButton:
|
||||||
if !tabsLayout.setBoolProperty(tag, value) {
|
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.Session())
|
||||||
return false
|
|
||||||
}
|
|
||||||
if tabsLayout.created {
|
|
||||||
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return tabsLayout.viewsContainerData.set(tag, value)
|
tabsLayout.viewsContainerData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
tabsLayout.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) {
|
/*
|
||||||
if value == nil {
|
func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) {
|
||||||
return []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):
|
switch value := value.(type) {
|
||||||
fn := func(view TabsLayout, _, _ int) {
|
case func(TabsLayout, int, int):
|
||||||
value(view)
|
return []func(TabsLayout, int, int){value}
|
||||||
}
|
|
||||||
return []func(TabsLayout, int, int){fn}
|
|
||||||
|
|
||||||
case func(int, int):
|
case func(TabsLayout, int):
|
||||||
fn := func(_ TabsLayout, current, old int) {
|
fn := func(view TabsLayout, current, _ int) {
|
||||||
value(current, old)
|
value(view, current)
|
||||||
}
|
|
||||||
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) {
|
return []func(TabsLayout, int, int){fn}
|
||||||
val(view, current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
case []func(TabsLayout):
|
case func(TabsLayout):
|
||||||
listeners := make([]func(TabsLayout, int, int), len(value))
|
fn := func(view TabsLayout, _, _ int) {
|
||||||
for i, val := range value {
|
value(view)
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
listeners[i] = func(view TabsLayout, _, _ int) {
|
return []func(TabsLayout, int, int){fn}
|
||||||
val(view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
case []func(int, int):
|
case func(int, int):
|
||||||
listeners := make([]func(TabsLayout, int, int), len(value))
|
fn := func(_ TabsLayout, current, old int) {
|
||||||
for i, val := range value {
|
value(current, old)
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
listeners[i] = func(_ TabsLayout, current, old int) {
|
return []func(TabsLayout, int, int){fn}
|
||||||
val(current, old)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
case []func(int):
|
case func(int):
|
||||||
listeners := make([]func(TabsLayout, int, int), len(value))
|
fn := func(_ TabsLayout, current, _ int) {
|
||||||
for i, val := range value {
|
value(current)
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
listeners[i] = func(_ TabsLayout, current, _ int) {
|
return []func(TabsLayout, int, int){fn}
|
||||||
val(current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
case []func():
|
case func():
|
||||||
listeners := make([]func(TabsLayout, int, int), len(value))
|
fn := func(TabsLayout, int, int) {
|
||||||
for i, val := range value {
|
value()
|
||||||
if val == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
listeners[i] = func(TabsLayout, int, int) {
|
return []func(TabsLayout, int, int){fn}
|
||||||
val()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listeners
|
|
||||||
|
|
||||||
case []any:
|
case []func(TabsLayout, int, int):
|
||||||
listeners := make([]func(TabsLayout, int, int), len(value))
|
return 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):
|
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) {
|
listeners[i] = func(view TabsLayout, current, _ int) {
|
||||||
val(view, current)
|
val(view, current)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return listeners
|
||||||
|
|
||||||
case func(TabsLayout):
|
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) {
|
listeners[i] = func(view TabsLayout, _, _ int) {
|
||||||
val(view)
|
val(view)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return listeners
|
||||||
|
|
||||||
case func(int, int):
|
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) {
|
listeners[i] = func(_ TabsLayout, current, old int) {
|
||||||
val(current, old)
|
val(current, old)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return listeners
|
||||||
|
|
||||||
case func(int):
|
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) {
|
listeners[i] = func(_ TabsLayout, current, _ int) {
|
||||||
val(current)
|
val(current)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return listeners
|
||||||
|
|
||||||
case func():
|
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) {
|
listeners[i] = func(TabsLayout, int, int) {
|
||||||
val()
|
val()
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
return listeners
|
||||||
return listeners
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
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 {
|
func (tabsLayout *tabsLayoutData) tabsLocation() int {
|
||||||
tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0)
|
tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0)
|
||||||
|
|
@ -519,36 +406,42 @@ func (tabsLayout *tabsLayoutData) tabBarStyle() string {
|
||||||
return "ruiTabBar"
|
return "ruiTabBar"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) inactiveTabStyle() string {
|
func tabsLayoutInactiveTabStyle(view View) string {
|
||||||
if style, ok := stringProperty(tabsLayout, TabStyle, tabsLayout.session); ok {
|
session := view.Session()
|
||||||
|
if style, ok := stringProperty(view, TabStyle, session); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
if value := valueFromStyle(tabsLayout, TabStyle); value != nil {
|
if value := valueFromStyle(view, TabStyle); value != nil {
|
||||||
if style, ok := value.(string); ok {
|
if style, ok := value.(string); ok {
|
||||||
if style, ok = tabsLayout.session.resolveConstants(style); ok {
|
if style, ok = session.resolveConstants(style); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch tabsLayout.tabsLocation() {
|
|
||||||
|
tabs, _ := enumProperty(view, Tabs, session, 0)
|
||||||
|
switch tabs {
|
||||||
case LeftTabs, RightTabs:
|
case LeftTabs, RightTabs:
|
||||||
return "ruiVerticalTab"
|
return "ruiVerticalTab"
|
||||||
}
|
}
|
||||||
return "ruiTab"
|
return "ruiTab"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) activeTabStyle() string {
|
func tabsLayoutActiveTabStyle(view View) string {
|
||||||
if style, ok := stringProperty(tabsLayout, CurrentTabStyle, tabsLayout.session); ok {
|
session := view.Session()
|
||||||
|
if style, ok := stringProperty(view, CurrentTabStyle, session); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
if value := valueFromStyle(tabsLayout, CurrentTabStyle); value != nil {
|
if value := valueFromStyle(view, CurrentTabStyle); value != nil {
|
||||||
if style, ok := value.(string); ok {
|
if style, ok := value.(string); ok {
|
||||||
if style, ok = tabsLayout.session.resolveConstants(style); ok {
|
if style, ok = session.resolveConstants(style); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch tabsLayout.tabsLocation() {
|
|
||||||
|
tabs, _ := enumProperty(view, Tabs, session, 0)
|
||||||
|
switch tabs {
|
||||||
case LeftTabs, RightTabs:
|
case LeftTabs, RightTabs:
|
||||||
return "ruiCurrentVerticalTab"
|
return "ruiCurrentVerticalTab"
|
||||||
}
|
}
|
||||||
|
|
@ -610,7 +503,7 @@ func (tabsLayout *tabsLayoutData) ListItem(index int, session Session) View {
|
||||||
Column: 2,
|
Column: 2,
|
||||||
Content: "✕",
|
Content: "✕",
|
||||||
ClickEvent: func() {
|
ClickEvent: func() {
|
||||||
for _, listener := range tabsLayout.tabCloseListener {
|
for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
|
||||||
listener(tabsLayout, index)
|
listener(tabsLayout, index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -632,7 +525,7 @@ func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) {
|
func (tabsLayout *tabsLayoutData) updateTitle(view View, tag PropertyName) {
|
||||||
session := tabsLayout.session
|
session := tabsLayout.session
|
||||||
title, _ := stringProperty(view, Title, session)
|
title, _ := stringProperty(view, Title, session)
|
||||||
if !GetNotTranslate(tabsLayout) {
|
if !GetNotTranslate(tabsLayout) {
|
||||||
|
|
@ -641,13 +534,13 @@ func (tabsLayout *tabsLayoutData) updateTitle(view View, tag string) {
|
||||||
session.updateInnerHTML(view.htmlID()+"-title", title)
|
session.updateInnerHTML(view.htmlID()+"-title", title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) updateIcon(view View, tag string) {
|
func (tabsLayout *tabsLayoutData) updateIcon(view View, tag PropertyName) {
|
||||||
session := tabsLayout.session
|
session := tabsLayout.session
|
||||||
icon, _ := stringProperty(view, Icon, session)
|
icon, _ := stringProperty(view, Icon, session)
|
||||||
session.updateProperty(view.htmlID()+"-icon", "src", icon)
|
session.updateProperty(view.htmlID()+"-icon", "src", icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag string) {
|
func (tabsLayout *tabsLayoutData) updateTabCloseButton(view View, tag PropertyName) {
|
||||||
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -662,11 +555,8 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
|
||||||
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
view.SetChangeListener(Icon, tabsLayout.updateIcon)
|
||||||
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
view.SetChangeListener(TabCloseButton, tabsLayout.updateTabCloseButton)
|
||||||
if len(tabsLayout.views) == 1 {
|
if len(tabsLayout.views) == 1 {
|
||||||
tabsLayout.properties[Current] = 0
|
tabsLayout.setRaw(Current, nil)
|
||||||
for _, listener := range tabsLayout.tabListener {
|
tabsLayout.Set(Current, 0)
|
||||||
listener(tabsLayout, 0, -1)
|
|
||||||
}
|
|
||||||
defer tabsLayout.propertyChangedEvent(Current)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -677,9 +567,9 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index int) {
|
||||||
tabsLayout.views = []View{}
|
tabsLayout.views = []View{}
|
||||||
}
|
}
|
||||||
if view != nil {
|
if view != nil {
|
||||||
if current := tabsLayout.currentItem(0); current >= index {
|
if current := GetCurrent(tabsLayout); current >= index {
|
||||||
tabsLayout.properties[Current] = current + 1
|
tabsLayout.setRaw(Current, current+1)
|
||||||
defer tabsLayout.propertyChangedEvent(Current)
|
defer tabsLayout.currentChanged(current+1, current)
|
||||||
}
|
}
|
||||||
tabsLayout.viewsContainerData.Insert(view, index)
|
tabsLayout.viewsContainerData.Insert(view, index)
|
||||||
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
view.SetChangeListener(Title, tabsLayout.updateTitle)
|
||||||
|
|
@ -688,56 +578,90 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove removes view from list and return it
|
// Remove removes view from list and return it
|
||||||
func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
|
func (tabsLayout *tabsLayoutData) RemoveView(index int) View {
|
||||||
if tabsLayout.views == nil {
|
|
||||||
tabsLayout.views = []View{}
|
if index < 0 || index >= len(tabsLayout.views) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
count := len(tabsLayout.views)
|
oldCurrent := GetCurrent(tabsLayout)
|
||||||
if index < 0 || index >= count {
|
newCurrent := oldCurrent
|
||||||
return nil
|
if index < oldCurrent || (index == oldCurrent && oldCurrent > 0) {
|
||||||
|
newCurrent--
|
||||||
}
|
}
|
||||||
|
|
||||||
view := tabsLayout.views[index]
|
if view := tabsLayout.viewsContainerData.RemoveView(index); view != nil {
|
||||||
view.setParentID("")
|
view.SetChangeListener(Title, nil)
|
||||||
view.SetChangeListener(Title, nil)
|
view.SetChangeListener(Icon, nil)
|
||||||
view.SetChangeListener(Icon, nil)
|
view.SetChangeListener(TabCloseButton, nil)
|
||||||
view.SetChangeListener(TabCloseButton, nil)
|
|
||||||
|
|
||||||
current := tabsLayout.currentItem(0)
|
if newCurrent != oldCurrent {
|
||||||
if index < current || (index == current && current > 0) {
|
tabsLayout.setRaw(Current, newCurrent)
|
||||||
current--
|
tabsLayout.currentChanged(newCurrent, oldCurrent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
if len(tabsLayout.views) == 1 {
|
/*
|
||||||
tabsLayout.views = []View{}
|
if tabsLayout.views == nil {
|
||||||
current = -1
|
tabsLayout.views = []View{}
|
||||||
} else if index == 0 {
|
return nil
|
||||||
tabsLayout.views = tabsLayout.views[1:]
|
}
|
||||||
} else if index == count-1 {
|
|
||||||
tabsLayout.views = tabsLayout.views[:index]
|
|
||||||
} else {
|
|
||||||
tabsLayout.views = append(tabsLayout.views[:index], tabsLayout.views[index+1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInnerHTML(tabsLayout.parentHTMLID(), tabsLayout.session)
|
count := len(tabsLayout.views)
|
||||||
tabsLayout.propertyChangedEvent(Content)
|
if index < 0 || index >= count {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
delete(tabsLayout.properties, Current)
|
view := tabsLayout.views[index]
|
||||||
tabsLayout.set(Current, current)
|
view.setParentID("")
|
||||||
return view
|
view.SetChangeListener(Title, nil)
|
||||||
|
view.SetChangeListener(Icon, nil)
|
||||||
|
view.SetChangeListener(TabCloseButton, nil)
|
||||||
|
|
||||||
|
current := GetCurrent(tabsLayout)
|
||||||
|
if index < current || (index == current && current > 0) {
|
||||||
|
current--
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tabsLayout.views) == 1 {
|
||||||
|
tabsLayout.views = []View{}
|
||||||
|
current = -1
|
||||||
|
} else if index == 0 {
|
||||||
|
tabsLayout.views = tabsLayout.views[1:]
|
||||||
|
} else if index == count-1 {
|
||||||
|
tabsLayout.views = tabsLayout.views[:index]
|
||||||
|
} else {
|
||||||
|
tabsLayout.views = append(tabsLayout.views[:index], tabsLayout.views[index+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInnerHTML(tabsLayout.parentHTMLID(), tabsLayout.session)
|
||||||
|
tabsLayout.propertyChangedEvent(Content)
|
||||||
|
|
||||||
|
delete(tabsLayout.view, Current)
|
||||||
|
tabsLayout.Set(Current, current)
|
||||||
|
return view
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
|
func (tabsLayout *tabsLayoutData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
|
tabsLayout.viewsContainerData.htmlProperties(self, buffer)
|
||||||
buffer.WriteString(` data-inactiveTabStyle="`)
|
buffer.WriteString(` data-inactiveTabStyle="`)
|
||||||
buffer.WriteString(tabsLayout.inactiveTabStyle())
|
buffer.WriteString(tabsLayoutInactiveTabStyle(tabsLayout))
|
||||||
buffer.WriteString(`" data-activeTabStyle="`)
|
buffer.WriteString(`" data-activeTabStyle="`)
|
||||||
buffer.WriteString(tabsLayout.activeTabStyle())
|
buffer.WriteString(tabsLayoutActiveTabStyle(tabsLayout))
|
||||||
buffer.WriteString(`" data-current="`)
|
buffer.WriteString(`" data-current="`)
|
||||||
buffer.WriteString(strconv.Itoa(tabsLayout.currentItem(0)))
|
buffer.WriteString(strconv.Itoa(GetCurrent(tabsLayout)))
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -763,8 +687,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//viewCount := len(tabsLayout.views)
|
current := GetCurrent(tabsLayout)
|
||||||
current := tabsLayout.currentItem(0)
|
|
||||||
location := tabsLayout.tabsLocation()
|
location := tabsLayout.tabsLocation()
|
||||||
tabsLayoutID := tabsLayout.htmlID()
|
tabsLayoutID := tabsLayout.htmlID()
|
||||||
|
|
||||||
|
|
@ -796,8 +719,8 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
|
|
||||||
buffer.WriteString(`">`)
|
buffer.WriteString(`">`)
|
||||||
|
|
||||||
inactiveStyle := tabsLayout.inactiveTabStyle()
|
inactiveStyle := tabsLayoutInactiveTabStyle(tabsLayout)
|
||||||
activeStyle := tabsLayout.activeTabStyle()
|
activeStyle := tabsLayoutActiveTabStyle(tabsLayout)
|
||||||
|
|
||||||
notTranslate := GetNotTranslate(tabsLayout)
|
notTranslate := GetNotTranslate(tabsLayout)
|
||||||
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
|
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
|
||||||
|
|
@ -942,23 +865,21 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
|
||||||
buffer.WriteString(`grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;">`)
|
buffer.WriteString(`grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;">`)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewHTML(view, buffer)
|
viewHTML(view, buffer, "")
|
||||||
buffer.WriteString(`</div>`)
|
buffer.WriteString(`</div>`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tabsLayout *tabsLayoutData) handleCommand(self View, command string, data DataObject) bool {
|
func (tabsLayout *tabsLayoutData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "tabClick":
|
case "tabClick":
|
||||||
if numberText, ok := data.PropertyValue("number"); ok {
|
if numberText, ok := data.PropertyValue("number"); ok {
|
||||||
if number, err := strconv.Atoi(numberText); err == nil {
|
if number, err := strconv.Atoi(numberText); err == nil {
|
||||||
current := tabsLayout.currentItem(0)
|
current := GetCurrent(tabsLayout)
|
||||||
if current != number {
|
if current != number {
|
||||||
tabsLayout.properties[Current] = number
|
tabsLayout.setRaw(Current, number)
|
||||||
for _, listener := range tabsLayout.tabListener {
|
|
||||||
listener(tabsLayout, number, current)
|
tabsLayout.currentChanged(number, current)
|
||||||
}
|
|
||||||
tabsLayout.propertyChangedEvent(Current)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -967,7 +888,7 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command string, data
|
||||||
case "tabCloseClick":
|
case "tabCloseClick":
|
||||||
if numberText, ok := data.PropertyValue("number"); ok {
|
if numberText, ok := data.PropertyValue("number"); ok {
|
||||||
if number, err := strconv.Atoi(numberText); err == nil {
|
if number, err := strconv.Atoi(numberText); err == nil {
|
||||||
for _, listener := range tabsLayout.tabCloseListener {
|
for _, listener := range getOneArgEventListeners[TabsLayout, int](tabsLayout, nil, TabCloseEvent) {
|
||||||
listener(tabsLayout, number)
|
listener(tabsLayout, number)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
105
textView.go
105
textView.go
|
|
@ -23,116 +23,79 @@ func NewTextView(session Session, params Params) TextView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTextView(session Session) View {
|
func newTextView(session Session) View {
|
||||||
return NewTextView(session, nil)
|
return new(textViewData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initialize fields of TextView by default values
|
// Init initialize fields of TextView by default values
|
||||||
func (textView *textViewData) init(session Session) {
|
func (textView *textViewData) init(session Session) {
|
||||||
textView.viewData.init(session)
|
textView.viewData.init(session)
|
||||||
textView.tag = "TextView"
|
textView.tag = "TextView"
|
||||||
|
textView.set = textView.setFunc
|
||||||
|
textView.changed = textView.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (textView *textViewData) String() string {
|
func (textView *textViewData) propertyChanged(tag PropertyName) {
|
||||||
return getViewString(textView, nil)
|
switch tag {
|
||||||
}
|
case Text:
|
||||||
|
updateInnerHTML(textView.htmlID(), textView.Session())
|
||||||
|
|
||||||
func (textView *textViewData) Get(tag string) any {
|
case TextOverflow:
|
||||||
return textView.get(strings.ToLower(tag))
|
session := textView.Session()
|
||||||
}
|
if n, ok := enumProperty(textView, TextOverflow, session, 0); ok {
|
||||||
|
values := enumProperties[TextOverflow].cssValues
|
||||||
func (textView *textViewData) Remove(tag string) {
|
if n >= 0 && n < len(values) {
|
||||||
textView.remove(strings.ToLower(tag))
|
session.updateCSSProperty(textView.htmlID(), string(TextOverflow), values[n])
|
||||||
}
|
return
|
||||||
|
}
|
||||||
func (textView *textViewData) remove(tag string) {
|
|
||||||
textView.viewData.remove(tag)
|
|
||||||
if textView.created {
|
|
||||||
switch tag {
|
|
||||||
case Text:
|
|
||||||
updateInnerHTML(textView.htmlID(), textView.session)
|
|
||||||
|
|
||||||
case TextOverflow:
|
|
||||||
textView.textOverflowUpdated()
|
|
||||||
}
|
}
|
||||||
|
session.updateCSSProperty(textView.htmlID(), string(TextOverflow), "")
|
||||||
|
|
||||||
|
case NotTranslate:
|
||||||
|
updateInnerHTML(textView.htmlID(), textView.Session())
|
||||||
|
|
||||||
|
default:
|
||||||
|
textView.viewData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (textView *textViewData) Set(tag string, value any) bool {
|
func (textView *textViewData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
return textView.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (textView *textViewData) set(tag string, value any) bool {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Text:
|
case Text:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
textView.properties[Text] = value
|
textView.setRaw(Text, value)
|
||||||
|
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
textView.properties[Text] = value.String()
|
textView.setRaw(Text, value.String())
|
||||||
|
|
||||||
case float32:
|
case float32:
|
||||||
textView.properties[Text] = fmt.Sprintf("%g", float64(value))
|
textView.setRaw(Text, fmt.Sprintf("%g", float64(value)))
|
||||||
|
|
||||||
case float64:
|
case float64:
|
||||||
textView.properties[Text] = fmt.Sprintf("%g", value)
|
textView.setRaw(Text, fmt.Sprintf("%g", value))
|
||||||
|
|
||||||
case []rune:
|
case []rune:
|
||||||
textView.properties[Text] = string(value)
|
textView.setRaw(Text, string(value))
|
||||||
|
|
||||||
case bool:
|
case bool:
|
||||||
if value {
|
if value {
|
||||||
textView.properties[Text] = "true"
|
textView.setRaw(Text, "true")
|
||||||
} else {
|
} else {
|
||||||
textView.properties[Text] = "false"
|
textView.setRaw(Text, "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if n, ok := isInt(value); ok {
|
if n, ok := isInt(value); ok {
|
||||||
textView.properties[Text] = fmt.Sprintf("%d", n)
|
textView.setRaw(Text, fmt.Sprintf("%d", n))
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if textView.created {
|
return []PropertyName{Text}
|
||||||
updateInnerHTML(textView.htmlID(), textView.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TextOverflow:
|
|
||||||
if !textView.viewData.set(tag, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if textView.created {
|
|
||||||
textView.textOverflowUpdated()
|
|
||||||
}
|
|
||||||
|
|
||||||
case NotTranslate:
|
|
||||||
if !textView.viewData.set(tag, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if textView.created {
|
|
||||||
updateInnerHTML(textView.htmlID(), textView.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return textView.viewData.set(tag, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
textView.propertyChangedEvent(tag)
|
return textView.viewData.setFunc(tag, value)
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (textView *textViewData) textOverflowUpdated() {
|
|
||||||
session := textView.Session()
|
|
||||||
if n, ok := enumProperty(textView, TextOverflow, session, 0); ok {
|
|
||||||
values := enumProperties[TextOverflow].cssValues
|
|
||||||
if n >= 0 && n < len(values) {
|
|
||||||
session.updateCSSProperty(textView.htmlID(), TextOverflow, values[n])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
session.updateCSSProperty(textView.htmlID(), TextOverflow, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
|
|
|
||||||
6
theme.go
6
theme.go
|
|
@ -699,13 +699,13 @@ func (theme *theme) addText(themeText string) bool {
|
||||||
if node := obj.Property(i); node != nil {
|
if node := obj.Property(i); node != nil {
|
||||||
switch node.Type() {
|
switch node.Type() {
|
||||||
case ArrayNode:
|
case ArrayNode:
|
||||||
params[node.Tag()] = node.ArrayElements()
|
params[PropertyName(node.Tag())] = node.ArrayElements()
|
||||||
|
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
params[node.Tag()] = node.Object()
|
params[PropertyName(node.Tag())] = node.Object()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
params[node.Tag()] = node.Text()
|
params[PropertyName(node.Tag())] = node.Text()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
407
timePicker.go
407
timePicker.go
|
|
@ -10,86 +10,86 @@ import (
|
||||||
const (
|
const (
|
||||||
// TimeChangedEvent is the constant for "time-changed" property tag.
|
// TimeChangedEvent is the constant for "time-changed" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TimePicker`.
|
// Used by TimePicker.
|
||||||
// Occur when current time of the time picker has been changed.
|
// Occur when current time of the time picker has been changed.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(picker rui.TimePicker, newTime, oldTime time.Time)`.
|
// func(picker rui.TimePicker, newTime time.Time, oldTime time.Time).
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// picker - Interface of a time picker which generated this event,
|
// - picker - Interface of a time picker which generated this event,
|
||||||
// newTime - New time value,
|
// - newTime - New time value,
|
||||||
// oldTime - Old time value.
|
// - oldTime - Old time value.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(picker rui.TimePicker, newTime time.Time)`,
|
// func(picker rui.TimePicker, newTime time.Time),
|
||||||
// `func(newTime, oldTime time.Time)`,
|
// func(newTime time.Time, oldTime time.Time),
|
||||||
// `func(newTime time.Time)`,
|
// func(newTime time.Time),
|
||||||
// `func(picker rui.TimePicker)`,
|
// func(picker rui.TimePicker),
|
||||||
// `func()`.
|
// func().
|
||||||
TimeChangedEvent = "time-changed"
|
TimeChangedEvent PropertyName = "time-changed"
|
||||||
|
|
||||||
// TimePickerMin is the constant for "time-picker-min" property tag.
|
// TimePickerMin is the constant for "time-picker-min" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TimePicker`.
|
// Used by TimePicker.
|
||||||
// The minimum value of the time.
|
// The minimum value of the time.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "HH:MM:SS" - "08:15:00".
|
// - "HH:MM:SS" - "08:15:00".
|
||||||
// "HH:MM:SS PM" - "08:15:00 AM".
|
// - "HH:MM:SS PM" - "08:15:00 AM".
|
||||||
// "HH:MM" - "08:15".
|
// - "HH:MM" - "08:15".
|
||||||
// "HH:MM PM" - "08:15 AM".
|
// - "HH:MM PM" - "08:15 AM".
|
||||||
TimePickerMin = "time-picker-min"
|
TimePickerMin PropertyName = "time-picker-min"
|
||||||
|
|
||||||
// TimePickerMax is the constant for "time-picker-max" property tag.
|
// TimePickerMax is the constant for "time-picker-max" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TimePicker`.
|
// Used by TimePicker.
|
||||||
// The maximum value of the time.
|
// The maximum value of the time.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "HH:MM:SS" - "08:15:00".
|
// - "HH:MM:SS" - "08:15:00".
|
||||||
// "HH:MM:SS PM" - "08:15:00 AM".
|
// - "HH:MM:SS PM" - "08:15:00 AM".
|
||||||
// "HH:MM" - "08:15".
|
// - "HH:MM" - "08:15".
|
||||||
// "HH:MM PM" - "08:15 AM".
|
// - "HH:MM PM" - "08:15 AM".
|
||||||
TimePickerMax = "time-picker-max"
|
TimePickerMax PropertyName = "time-picker-max"
|
||||||
|
|
||||||
// TimePickerStep is the constant for "time-picker-step" property tag.
|
// TimePickerStep is the constant for "time-picker-step" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TimePicker`.
|
// Used by TimePicker.
|
||||||
// Time step in seconds.
|
// Time step in seconds.
|
||||||
//
|
//
|
||||||
// Supported types: `int`, `string`.
|
// Supported types: int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// >= `0` or >= "0" - Step value in seconds used to increment or decrement time.
|
// positive value - Step value in seconds used to increment or decrement time.
|
||||||
TimePickerStep = "time-picker-step"
|
TimePickerStep PropertyName = "time-picker-step"
|
||||||
|
|
||||||
// TimePickerValue is the constant for "time-picker-value" property tag.
|
// TimePickerValue is the constant for "time-picker-value" property tag.
|
||||||
//
|
//
|
||||||
// Used by `TimePicker`.
|
// Used by TimePicker.
|
||||||
// Current value.
|
// Current value.
|
||||||
//
|
//
|
||||||
// Supported types: `time.Time`, `string`.
|
// Supported types: time.Time, string.
|
||||||
//
|
//
|
||||||
// Internal type is `time.Time`, other types converted to it during assignment.
|
// Internal type is time.Time, other types converted to it during assignment.
|
||||||
//
|
//
|
||||||
// Conversion rules:
|
// Conversion rules:
|
||||||
// `string` - values of this type parsed and converted to `time.Time`. The following formats are supported:
|
// string - values of this type parsed and converted to time.Time. The following formats are supported:
|
||||||
// "HH:MM:SS" - "08:15:00".
|
// - "HH:MM:SS" - "08:15:00".
|
||||||
// "HH:MM:SS PM" - "08:15:00 AM".
|
// - "HH:MM:SS PM" - "08:15:00 AM".
|
||||||
// "HH:MM" - "08:15".
|
// - "HH:MM" - "08:15".
|
||||||
// "HH:MM PM" - "08:15 AM".
|
// - "HH:MM PM" - "08:15 AM".
|
||||||
TimePickerValue = "time-picker-value"
|
TimePickerValue PropertyName = "time-picker-value"
|
||||||
|
|
||||||
timeFormat = "15:04:05"
|
timeFormat = "15:04:05"
|
||||||
)
|
)
|
||||||
|
|
@ -101,8 +101,6 @@ type TimePicker interface {
|
||||||
|
|
||||||
type timePickerData struct {
|
type timePickerData struct {
|
||||||
viewData
|
viewData
|
||||||
dataList
|
|
||||||
timeChangedListeners []func(TimePicker, time.Time, time.Time)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTimePicker create new TimePicker object and return it
|
// NewTimePicker create new TimePicker object and return it
|
||||||
|
|
@ -114,236 +112,154 @@ func NewTimePicker(session Session, params Params) TimePicker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTimePicker(session Session) View {
|
func newTimePicker(session Session) View {
|
||||||
return NewTimePicker(session, nil)
|
return new(timePickerData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) init(session Session) {
|
func (picker *timePickerData) init(session Session) {
|
||||||
picker.viewData.init(session)
|
picker.viewData.init(session)
|
||||||
picker.tag = "TimePicker"
|
picker.tag = "TimePicker"
|
||||||
picker.hasHtmlDisabled = true
|
picker.hasHtmlDisabled = true
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
picker.normalize = normalizeTimePickerTag
|
||||||
picker.dataListInit()
|
picker.set = picker.setFunc
|
||||||
}
|
picker.changed = picker.propertyChanged
|
||||||
|
|
||||||
func (picker *timePickerData) String() string {
|
|
||||||
return getViewString(picker, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) Focusable() bool {
|
func (picker *timePickerData) Focusable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) normalizeTag(tag string) string {
|
func normalizeTimePickerTag(tag PropertyName) PropertyName {
|
||||||
tag = strings.ToLower(tag)
|
tag = defaultNormalize(tag)
|
||||||
switch tag {
|
switch tag {
|
||||||
case Type, Min, Max, Step, Value:
|
case Type, Min, Max, Step, Value:
|
||||||
return "time-picker-" + tag
|
return "time-picker-" + tag
|
||||||
}
|
}
|
||||||
|
|
||||||
return tag
|
return normalizeDataListTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) Remove(tag string) {
|
func stringToTime(value string) (time.Time, bool) {
|
||||||
picker.remove(picker.normalizeTag(tag))
|
lowText := strings.ToUpper(value)
|
||||||
}
|
pm := strings.HasSuffix(lowText, "PM") || strings.HasSuffix(lowText, "AM")
|
||||||
|
|
||||||
func (picker *timePickerData) remove(tag string) {
|
var format string
|
||||||
switch tag {
|
switch len(strings.Split(value, ":")) {
|
||||||
case TimeChangedEvent:
|
case 2:
|
||||||
if len(picker.timeChangedListeners) > 0 {
|
if pm {
|
||||||
picker.timeChangedListeners = []func(TimePicker, time.Time, time.Time){}
|
format = "3:04 PM"
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
|
|
||||||
case TimePickerMin:
|
|
||||||
delete(picker.properties, TimePickerMin)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TimePickerMax:
|
|
||||||
delete(picker.properties, TimePickerMax)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Max)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TimePickerStep:
|
|
||||||
delete(picker.properties, TimePickerStep)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Step)
|
|
||||||
}
|
|
||||||
|
|
||||||
case TimePickerValue:
|
|
||||||
if _, ok := picker.properties[TimePickerValue]; ok {
|
|
||||||
oldTime := GetTimePickerValue(picker)
|
|
||||||
delete(picker.properties, TimePickerValue)
|
|
||||||
time := GetTimePickerValue(picker)
|
|
||||||
if picker.created {
|
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
|
||||||
}
|
|
||||||
for _, listener := range picker.timeChangedListeners {
|
|
||||||
listener(picker, time, oldTime)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return
|
format = "15:04"
|
||||||
}
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
if len(picker.dataList.dataList) > 0 {
|
|
||||||
picker.setDataList(picker, []string{}, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
picker.viewData.remove(tag)
|
if pm {
|
||||||
return
|
format = "03:04:05 PM"
|
||||||
}
|
} else {
|
||||||
picker.propertyChangedEvent(tag)
|
format = "15:04:05"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) Set(tag string, value any) bool {
|
|
||||||
return picker.set(picker.normalizeTag(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *timePickerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
picker.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeValue := func(tag string) (time.Time, bool) {
|
result, err := time.Parse(format, value)
|
||||||
|
if err != nil {
|
||||||
|
ErrorLog(err.Error())
|
||||||
|
return time.Now(), false
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (picker *timePickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
|
|
||||||
|
setTimeValue := func(tag PropertyName) []PropertyName {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case time.Time:
|
case time.Time:
|
||||||
picker.properties[tag] = value
|
picker.setRaw(tag, value)
|
||||||
return value, true
|
return []PropertyName{tag}
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if text, ok := picker.Session().resolveConstants(value); ok {
|
if isConstantName(value) {
|
||||||
lowText := strings.ToLower(text)
|
picker.setRaw(tag, value)
|
||||||
pm := strings.HasSuffix(lowText, "pm") || strings.HasSuffix(lowText, "am")
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
var format string
|
if time, ok := stringToTime(value); ok {
|
||||||
switch len(strings.Split(text, ":")) {
|
picker.setRaw(tag, time)
|
||||||
case 2:
|
return []PropertyName{tag}
|
||||||
if pm {
|
|
||||||
format = "3:04 PM"
|
|
||||||
} else {
|
|
||||||
format = "15:04"
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if pm {
|
|
||||||
format = "03:04:05 PM"
|
|
||||||
} else {
|
|
||||||
format = "15:04:05"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if time, err := time.Parse(format, text); err == nil {
|
|
||||||
picker.properties[tag] = value
|
|
||||||
return time, true
|
|
||||||
} else {
|
|
||||||
ErrorLog(err.Error())
|
|
||||||
}
|
|
||||||
return time.Now(), false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
notCompatibleType(tag, value)
|
||||||
return time.Now(), false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case TimePickerMin:
|
case TimePickerMin:
|
||||||
old, oldOK := getTimeProperty(picker, TimePickerMin, Min)
|
return setTimeValue(TimePickerMin)
|
||||||
if time, ok := setTimeValue(TimePickerMin); ok {
|
|
||||||
if !oldOK || time != old {
|
case TimePickerMax:
|
||||||
if picker.created {
|
return setTimeValue(TimePickerMax)
|
||||||
picker.session.updateProperty(picker.htmlID(), Min, time.Format(timeFormat))
|
|
||||||
}
|
case TimePickerStep:
|
||||||
picker.propertyChangedEvent(tag)
|
return setIntProperty(picker, TimePickerStep, value)
|
||||||
}
|
|
||||||
return true
|
case TimePickerValue:
|
||||||
|
picker.setRaw("old-time", GetTimePickerValue(picker))
|
||||||
|
return setTimeValue(tag)
|
||||||
|
|
||||||
|
case TimeChangedEvent:
|
||||||
|
return setTwoArgEventListener[TimePicker, time.Time](picker, tag, value)
|
||||||
|
|
||||||
|
case DataList:
|
||||||
|
return setDataList(picker, value, timeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return picker.viewData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (picker *timePickerData) propertyChanged(tag PropertyName) {
|
||||||
|
|
||||||
|
session := picker.Session()
|
||||||
|
|
||||||
|
switch tag {
|
||||||
|
|
||||||
|
case TimePickerMin:
|
||||||
|
if time, ok := GetTimePickerMin(picker); ok {
|
||||||
|
session.updateProperty(picker.htmlID(), "min", time.Format(timeFormat))
|
||||||
|
} else {
|
||||||
|
session.removeProperty(picker.htmlID(), "min")
|
||||||
}
|
}
|
||||||
|
|
||||||
case TimePickerMax:
|
case TimePickerMax:
|
||||||
old, oldOK := getTimeProperty(picker, TimePickerMax, Max)
|
if time, ok := GetTimePickerMax(picker); ok {
|
||||||
if time, ok := setTimeValue(TimePickerMax); ok {
|
session.updateProperty(picker.htmlID(), "max", time.Format(timeFormat))
|
||||||
if !oldOK || time != old {
|
} else {
|
||||||
if picker.created {
|
session.removeProperty(picker.htmlID(), "max")
|
||||||
picker.session.updateProperty(picker.htmlID(), Max, time.Format(timeFormat))
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case TimePickerStep:
|
case TimePickerStep:
|
||||||
oldStep := GetTimePickerStep(picker)
|
if step := GetTimePickerStep(picker); step > 0 {
|
||||||
if picker.setIntProperty(TimePickerStep, value) {
|
session.updateProperty(picker.htmlID(), "step", strconv.Itoa(step))
|
||||||
if step := GetTimePickerStep(picker); oldStep != step {
|
} else {
|
||||||
if picker.created {
|
session.removeProperty(picker.htmlID(), "step")
|
||||||
if step > 0 {
|
|
||||||
picker.session.updateProperty(picker.htmlID(), Step, strconv.Itoa(step))
|
|
||||||
} else {
|
|
||||||
picker.session.removeProperty(picker.htmlID(), Step)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case TimePickerValue:
|
case TimePickerValue:
|
||||||
oldTime := GetTimePickerValue(picker)
|
value := GetTimePickerValue(picker)
|
||||||
if time, ok := setTimeValue(TimePickerValue); ok {
|
session.callFunc("setInputValue", picker.htmlID(), value.Format(timeFormat))
|
||||||
if time != oldTime {
|
|
||||||
if picker.created {
|
if listeners := GetTimeChangedListeners(picker); len(listeners) > 0 {
|
||||||
picker.session.callFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
|
oldTime := time.Now()
|
||||||
|
if val := picker.getRaw("old-time"); val != nil {
|
||||||
|
if time, ok := val.(time.Time); ok {
|
||||||
|
oldTime = time
|
||||||
}
|
}
|
||||||
for _, listener := range picker.timeChangedListeners {
|
|
||||||
listener(picker, time, oldTime)
|
|
||||||
}
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
}
|
}
|
||||||
return true
|
for _, listener := range listeners {
|
||||||
|
listener(picker, value, oldTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case TimeChangedEvent:
|
|
||||||
listeners, ok := valueToEventWithOldListeners[TimePicker, time.Time](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
} else if listeners == nil {
|
|
||||||
listeners = []func(TimePicker, time.Time, time.Time){}
|
|
||||||
}
|
|
||||||
picker.timeChangedListeners = listeners
|
|
||||||
picker.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return picker.setDataList(picker, value, picker.created)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return picker.viewData.set(tag, value)
|
picker.viewData.propertyChanged(tag)
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *timePickerData) Get(tag string) any {
|
|
||||||
return picker.get(picker.normalizeTag(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (picker *timePickerData) get(tag string) any {
|
|
||||||
switch tag {
|
|
||||||
case TimeChangedEvent:
|
|
||||||
return picker.timeChangedListeners
|
|
||||||
|
|
||||||
case DataList:
|
|
||||||
return picker.dataList.dataList
|
|
||||||
|
|
||||||
default:
|
|
||||||
return picker.viewData.get(tag)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +268,13 @@ func (picker *timePickerData) htmlTag() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (picker *timePickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
picker.dataListHtmlSubviews(self, buffer)
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
||||||
|
text, _ = session.resolveConstants(text)
|
||||||
|
if time, ok := stringToTime(text); ok {
|
||||||
|
return time.Format(timeFormat)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
|
|
@ -387,20 +309,24 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder)
|
||||||
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
buffer.WriteString(` onclick="stopEventPropagation(this, event)"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.dataListHtmlProperties(picker, buffer)
|
dataListHtmlProperties(picker, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (picker *timePickerData) handleCommand(self View, command string, data DataObject) bool {
|
func (picker *timePickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
||||||
switch command {
|
switch command {
|
||||||
case "textChanged":
|
case "textChanged":
|
||||||
if text, ok := data.PropertyValue("text"); ok {
|
if text, ok := data.PropertyValue("text"); ok {
|
||||||
if value, err := time.Parse(timeFormat, text); err == nil {
|
if value, ok := stringToTime(text); ok {
|
||||||
oldValue := GetTimePickerValue(picker)
|
oldValue := GetTimePickerValue(picker)
|
||||||
picker.properties[TimePickerValue] = value
|
picker.properties[TimePickerValue] = value
|
||||||
if value != oldValue {
|
if value != oldValue {
|
||||||
for _, listener := range picker.timeChangedListeners {
|
for _, listener := range GetTimeChangedListeners(picker) {
|
||||||
listener(picker, value, oldValue)
|
listener(picker, value, oldValue)
|
||||||
}
|
}
|
||||||
|
if listener, ok := picker.changeListener[TimePickerValue]; ok {
|
||||||
|
listener(picker, TimePickerValue)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -410,7 +336,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data
|
||||||
return picker.viewData.handleCommand(self, command, data)
|
return picker.viewData.handleCommand(self, command, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
func getTimeProperty(view View, mainTag, shortTag PropertyName) (time.Time, bool) {
|
||||||
valueToTime := func(value any) (time.Time, bool) {
|
valueToTime := func(value any) (time.Time, bool) {
|
||||||
if value != nil {
|
if value != nil {
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
|
|
@ -419,7 +345,7 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
if text, ok := view.Session().resolveConstants(value); ok {
|
if text, ok := view.Session().resolveConstants(value); ok {
|
||||||
if result, err := time.Parse(timeFormat, text); err == nil {
|
if result, ok := stringToTime(text); ok {
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -433,9 +359,11 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if value := valueFromStyle(view, shortTag); value != nil {
|
for _, tag := range []PropertyName{mainTag, shortTag} {
|
||||||
if result, ok := valueToTime(value); ok {
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
return result, true
|
if result, ok := valueToTime(value); ok {
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -447,10 +375,7 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) {
|
||||||
// "false" as the second value otherwise.
|
// "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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getTimeProperty(view, TimePickerMin, Min)
|
return getTimeProperty(view, TimePickerMin, Min)
|
||||||
}
|
}
|
||||||
return time.Now(), false
|
return time.Now(), false
|
||||||
|
|
@ -460,10 +385,7 @@ func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) {
|
||||||
// "false" as the second value otherwise.
|
// "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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) {
|
func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getTimeProperty(view, TimePickerMax, Max)
|
return getTimeProperty(view, TimePickerMax, Max)
|
||||||
}
|
}
|
||||||
return time.Now(), false
|
return time.Now(), false
|
||||||
|
|
@ -478,10 +400,7 @@ func GetTimePickerStep(view View, subviewID ...string) int {
|
||||||
// GetTimePickerValue returns the time of TimePicker subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTimePickerValue(view View, subviewID ...string) time.Time {
|
func GetTimePickerValue(view View, subviewID ...string) time.Time {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view == nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return time.Now()
|
return time.Now()
|
||||||
}
|
}
|
||||||
time, _ := getTimeProperty(view, TimePickerValue, Value)
|
time, _ := getTimeProperty(view, TimePickerValue, Value)
|
||||||
|
|
@ -492,5 +411,5 @@ func GetTimePickerValue(view View, subviewID ...string) time.Time {
|
||||||
// If there are no listeners then the empty list is returned
|
// 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.
|
// 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) {
|
func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time, time.Time) {
|
||||||
return getEventWithOldListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
return getTwoArgEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
134
touchEvents.go
134
touchEvents.go
|
|
@ -2,83 +2,90 @@ package rui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants which represent [View] specific touch events properties
|
// Constants which represent [View] specific touch events properties
|
||||||
const (
|
const (
|
||||||
// TouchStart is the constant for "touch-start" property tag.
|
// TouchStart is the constant for "touch-start" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when one or more touch points are placed on the touch surface.
|
// Is fired when one or more touch points are placed on the touch surface.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.TouchEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.TouchEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Touch event.
|
// - event - Touch event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.TouchEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.TouchEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
TouchStart = "touch-start"
|
// func()
|
||||||
|
TouchStart PropertyName = "touch-start"
|
||||||
|
|
||||||
// TouchEnd is the constant for "touch-end" property tag.
|
// TouchEnd is the constant for "touch-end" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Fired when one or more touch points are removed from the touch surface.
|
// Fired when one or more touch points are removed from the touch surface.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.TouchEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.TouchEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Touch event.
|
// - event - Touch event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.TouchEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.TouchEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
TouchEnd = "touch-end"
|
// func()
|
||||||
|
TouchEnd PropertyName = "touch-end"
|
||||||
|
|
||||||
// TouchMove is the constant for "touch-move" property tag.
|
// TouchMove is the constant for "touch-move" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when one or more touch points are moved along the touch surface.
|
// Is fired when one or more touch points are moved along the touch surface.
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.TouchEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.TouchEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Touch event.
|
// - event - Touch event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.TouchEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.TouchEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
TouchMove = "touch-move"
|
// func()
|
||||||
|
TouchMove PropertyName = "touch-move"
|
||||||
|
|
||||||
// TouchCancel is the constant for "touch-cancel" property tag.
|
// TouchCancel is the constant for "touch-cancel" property tag.
|
||||||
//
|
//
|
||||||
// Used by `View`.
|
// Used by View.
|
||||||
// Is fired when one or more touch points have been disrupted in an implementation-specific manner (for example, too many
|
// Is fired when one or more touch points have been disrupted in an implementation-specific manner (for example, too many
|
||||||
// touch points are created).
|
// touch points are created).
|
||||||
//
|
//
|
||||||
// General listener format:
|
// General listener format:
|
||||||
// `func(view rui.View, event rui.TouchEvent)`.
|
//
|
||||||
|
// func(view rui.View, event rui.TouchEvent)
|
||||||
//
|
//
|
||||||
// where:
|
// where:
|
||||||
// view - Interface of a view which generated this event,
|
// - view - Interface of a view which generated this event,
|
||||||
// event - Touch event.
|
// - event - Touch event.
|
||||||
//
|
//
|
||||||
// Allowed listener formats:
|
// Allowed listener formats:
|
||||||
// `func(event rui.TouchEvent)`,
|
//
|
||||||
// `func(view rui.View)`,
|
// func(event rui.TouchEvent)
|
||||||
// `func()`.
|
// func(view rui.View)
|
||||||
TouchCancel = "touch-cancel"
|
// func()
|
||||||
|
TouchCancel PropertyName = "touch-cancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Touch contains parameters of a single touch of a touch event
|
// Touch contains parameters of a single touch of a touch event
|
||||||
|
|
@ -143,55 +150,6 @@ type TouchEvent struct {
|
||||||
MetaKey bool
|
MetaKey bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var touchEvents = map[string]struct{ jsEvent, jsFunc string }{
|
|
||||||
TouchStart: {jsEvent: "ontouchstart", jsFunc: "touchStartEvent"},
|
|
||||||
TouchEnd: {jsEvent: "ontouchend", jsFunc: "touchEndEvent"},
|
|
||||||
TouchMove: {jsEvent: "ontouchmove", jsFunc: "touchMoveEvent"},
|
|
||||||
TouchCancel: {jsEvent: "ontouchcancel", jsFunc: "touchCancelEvent"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) setTouchListener(tag string, value any) bool {
|
|
||||||
listeners, ok := valueToEventListeners[View, TouchEvent](value)
|
|
||||||
if !ok {
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if listeners == nil {
|
|
||||||
view.removeTouchListener(tag)
|
|
||||||
} else if js, ok := touchEvents[tag]; ok {
|
|
||||||
view.properties[tag] = listeners
|
|
||||||
if view.created {
|
|
||||||
view.session.updateProperty(view.htmlID(), js.jsEvent, js.jsFunc+"(this, event)")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) removeTouchListener(tag string) {
|
|
||||||
delete(view.properties, tag)
|
|
||||||
if view.created {
|
|
||||||
if js, ok := touchEvents[tag]; ok {
|
|
||||||
view.session.removeProperty(view.htmlID(), js.jsEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func touchEventsHtml(view View, buffer *strings.Builder) {
|
|
||||||
for tag, js := range touchEvents {
|
|
||||||
if value := view.getRaw(tag); value != nil {
|
|
||||||
if listeners, ok := value.([]func(View, TouchEvent)); ok && len(listeners) > 0 {
|
|
||||||
buffer.WriteString(js.jsEvent)
|
|
||||||
buffer.WriteString(`="`)
|
|
||||||
buffer.WriteString(js.jsFunc)
|
|
||||||
buffer.WriteString(`(this, event)" `)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (event *TouchEvent) init(data DataObject) {
|
func (event *TouchEvent) init(data DataObject) {
|
||||||
|
|
||||||
event.Touches = []Touch{}
|
event.Touches = []Touch{}
|
||||||
|
|
@ -225,8 +183,8 @@ func (event *TouchEvent) init(data DataObject) {
|
||||||
event.MetaKey = dataBoolProperty(data, "metaKey")
|
event.MetaKey = dataBoolProperty(data, "metaKey")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleTouchEvents(view View, tag string, data DataObject) {
|
func handleTouchEvents(view View, tag PropertyName, data DataObject) {
|
||||||
listeners := getEventListeners[View, TouchEvent](view, nil, tag)
|
listeners := getOneArgEventListeners[View, TouchEvent](view, nil, tag)
|
||||||
if len(listeners) == 0 {
|
if len(listeners) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -242,23 +200,23 @@ func handleTouchEvents(view View, tag string, data DataObject) {
|
||||||
// GetTouchStartListeners returns the "touch-start" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTouchStartListeners(view View, subviewID ...string) []func(View, TouchEvent) {
|
||||||
return getEventListeners[View, TouchEvent](view, subviewID, TouchStart)
|
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTouchEndListeners returns the "touch-end" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTouchEndListeners(view View, subviewID ...string) []func(View, TouchEvent) {
|
||||||
return getEventListeners[View, TouchEvent](view, subviewID, TouchEnd)
|
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTouchMoveListeners returns the "touch-move" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTouchMoveListeners(view View, subviewID ...string) []func(View, TouchEvent) {
|
||||||
return getEventListeners[View, TouchEvent](view, subviewID, TouchMove)
|
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchMove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTouchCancelListeners returns the "touch-cancel" listener list. If there are no listeners then the empty list is returned.
|
// 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.
|
// 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) {
|
func GetTouchCancelListeners(view View, subviewID ...string) []func(View, TouchEvent) {
|
||||||
return getEventListeners[View, TouchEvent](view, subviewID, TouchCancel)
|
return getOneArgEventListeners[View, TouchEvent](view, subviewID, TouchCancel)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,739 @@
|
||||||
|
package rui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants for [TransformProperty] specific properties
|
||||||
|
const (
|
||||||
|
// Transform is the constant for "transform" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// Specify translation, scale and rotation over x, y and z axes as well as a distortion of a view along x and y axes.
|
||||||
|
//
|
||||||
|
// Supported types: TransformProperty, string.
|
||||||
|
//
|
||||||
|
// See TransformProperty description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - TransformProperty - stored as is, no conversion performed.
|
||||||
|
// - string - string representation of TransformProperty interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
||||||
|
Transform PropertyName = "transform"
|
||||||
|
|
||||||
|
// Perspective is the constant for "perspective" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
// Distance between the z-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).
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
Perspective PropertyName = "perspective"
|
||||||
|
|
||||||
|
// PerspectiveOriginX is the constant for "perspective-origin-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// x-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%.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
PerspectiveOriginX PropertyName = "perspective-origin-x"
|
||||||
|
|
||||||
|
// PerspectiveOriginY is the constant for "perspective-origin-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// 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%.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
PerspectiveOriginY PropertyName = "perspective-origin-y"
|
||||||
|
|
||||||
|
// BackfaceVisible is the constant for "backface-visibility" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// Controls whether the back face of a view is visible when turned towards the user. Default value is true.
|
||||||
|
//
|
||||||
|
// Supported types: bool, int, string.
|
||||||
|
//
|
||||||
|
// Values:
|
||||||
|
// - true, 1, "true", "yes", "on", "1" - Back face is visible when turned towards the user.
|
||||||
|
// - false, 0, "false", "no", "off", "0" - Back face is hidden, effectively making the view invisible when turned away from the user.
|
||||||
|
BackfaceVisible PropertyName = "backface-visibility"
|
||||||
|
|
||||||
|
// TransformOriginX is the constant for "transform-origin-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// x-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TransformOriginX PropertyName = "transform-origin-x"
|
||||||
|
|
||||||
|
// TransformOriginY is the constant for "transform-origin-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// y-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TransformOriginY PropertyName = "transform-origin-y"
|
||||||
|
|
||||||
|
// TransformOriginZ is the constant for "transform-origin-z" property tag.
|
||||||
|
//
|
||||||
|
// Used by View.
|
||||||
|
// z-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TransformOriginZ PropertyName = "transform-origin-z"
|
||||||
|
|
||||||
|
// TranslateX is the constant for "translate-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// x-axis translation value of a 2D/3D translation.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TranslateX PropertyName = "translate-x"
|
||||||
|
|
||||||
|
// TranslateY is the constant for "translate-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// x-axis translation value of a 2D/3D translation.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TranslateY PropertyName = "translate-y"
|
||||||
|
|
||||||
|
// TranslateZ is the constant for "translate-z" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// z-axis translation value of a 3D translation.
|
||||||
|
//
|
||||||
|
// Supported types: SizeUnit, SizeFunc, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is SizeUnit, other types converted to it during assignment.
|
||||||
|
// See SizeUnit description for more details.
|
||||||
|
TranslateZ PropertyName = "translate-z"
|
||||||
|
|
||||||
|
// ScaleX is the constant for "scale-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// x-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
||||||
|
// scale, more than 1 - to increase. The default value is 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
ScaleX PropertyName = "scale-x"
|
||||||
|
|
||||||
|
// ScaleY is the constant for "scale-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// y-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
||||||
|
// scale, more than 1 - to increase. The default value is 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
ScaleY PropertyName = "scale-y"
|
||||||
|
|
||||||
|
// ScaleZ is the constant for "scale-z" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// z-axis scaling value of a 3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
||||||
|
// scale, more than 1 - to increase. The default value is 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
ScaleZ PropertyName = "scale-z"
|
||||||
|
|
||||||
|
// Rotate is the constant for "rotate" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// Angle of the view rotation. A positive angle denotes a clockwise rotation, a negative angle a counter-clockwise.
|
||||||
|
//
|
||||||
|
// Supported types: AngleUnit, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is AngleUnit, other types will be converted to it during assignment.
|
||||||
|
// See AngleUnit description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - AngleUnit - stored as is, no conversion performed.
|
||||||
|
// - string - must contain string representation of AngleUnit. If numeric value will be provided without any suffix then AngleUnit with value and Radian value type will be created.
|
||||||
|
// - float - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
// - int - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
Rotate PropertyName = "rotate"
|
||||||
|
|
||||||
|
// RotateX is the constant for "rotate-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// x-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
RotateX PropertyName = "rotate-x"
|
||||||
|
|
||||||
|
// RotateY is the constant for "rotate-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// y-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
RotateY PropertyName = "rotate-y"
|
||||||
|
|
||||||
|
// RotateZ is the constant for "rotate-z" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// z-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
||||||
|
//
|
||||||
|
// Supported types: float, int, string.
|
||||||
|
//
|
||||||
|
// Internal type is float, other types converted to it during assignment.
|
||||||
|
RotateZ PropertyName = "rotate-z"
|
||||||
|
|
||||||
|
// SkewX is the constant for "skew-x" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// Angle to use to distort the element along the abscissa. The default value is 0.
|
||||||
|
//
|
||||||
|
// Supported types: AngleUnit, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is AngleUnit, other types will be converted to it during assignment.
|
||||||
|
// See AngleUnit description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - AngleUnit - stored as is, no conversion performed.
|
||||||
|
// - string - must contain string representation of AngleUnit. If numeric value will be provided without any suffix then AngleUnit with value and Radian value type will be created.
|
||||||
|
// - float - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
// - int - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
SkewX PropertyName = "skew-x"
|
||||||
|
|
||||||
|
// SkewY is the constant for "skew-y" property tag.
|
||||||
|
//
|
||||||
|
// Used by View, TransformProperty.
|
||||||
|
//
|
||||||
|
// Angle to use to distort the element along the ordinate. The default value is 0.
|
||||||
|
//
|
||||||
|
// Supported types: AngleUnit, string, float, int.
|
||||||
|
//
|
||||||
|
// Internal type is AngleUnit, other types will be converted to it during assignment.
|
||||||
|
// See AngleUnit description for more details.
|
||||||
|
//
|
||||||
|
// Conversion rules:
|
||||||
|
// - AngleUnit - stored as is, no conversion performed.
|
||||||
|
// - string - must contain string representation of AngleUnit. If numeric value will be provided without any suffix then AngleUnit with value and Radian value type will be created.
|
||||||
|
// - float - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
// - int - a new AngleUnit value will be created with Radian as a type.
|
||||||
|
SkewY PropertyName = "skew-y"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransformProperty interface specifies view transformation parameters: the x-, y-, and z-axis translation values,
|
||||||
|
// the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate,
|
||||||
|
// the angle of the view rotation.
|
||||||
|
//
|
||||||
|
// Valid property tags: Perspective ("perspective"), TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"),
|
||||||
|
// ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"),
|
||||||
|
// RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y")
|
||||||
|
type TransformProperty interface {
|
||||||
|
Properties
|
||||||
|
fmt.Stringer
|
||||||
|
stringWriter
|
||||||
|
transformCSS(session Session) string
|
||||||
|
getSkew(session Session) (AngleUnit, AngleUnit, bool)
|
||||||
|
getTranslate(session Session) (SizeUnit, SizeUnit, SizeUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformPropertyData struct {
|
||||||
|
dataProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransform creates a new transform property data and return its interface
|
||||||
|
//
|
||||||
|
// The following properties can be used:
|
||||||
|
//
|
||||||
|
// Perspective ("perspective"), TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"),
|
||||||
|
// ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"),
|
||||||
|
// RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y")
|
||||||
|
func NewTransformProperty(params Params) TransformProperty {
|
||||||
|
transform := new(transformPropertyData)
|
||||||
|
transform.init()
|
||||||
|
|
||||||
|
for tag, value := range params {
|
||||||
|
transform.Set(tag, value)
|
||||||
|
}
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) init() {
|
||||||
|
transform.dataProperty.init()
|
||||||
|
transform.normalize = normalizeTransformTag
|
||||||
|
transform.set = transformSet
|
||||||
|
transform.supportedProperties = []PropertyName{
|
||||||
|
RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
||||||
|
Perspective, TranslateX, TranslateY, TranslateZ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTransformTag(tag PropertyName) PropertyName {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
|
|
||||||
|
name := string(tag)
|
||||||
|
if strings.HasPrefix(name, "push-") {
|
||||||
|
tag = PropertyName(name[5:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) String() string {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
transform.writeString(buffer, "")
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) writeString(buffer *strings.Builder, indent string) {
|
||||||
|
buffer.WriteString("_{ ")
|
||||||
|
comma := false
|
||||||
|
for _, tag := range transform.supportedProperties {
|
||||||
|
if value, ok := transform.properties[tag]; ok {
|
||||||
|
if comma {
|
||||||
|
buffer.WriteString(", ")
|
||||||
|
}
|
||||||
|
buffer.WriteString(string(tag))
|
||||||
|
buffer.WriteString(" = ")
|
||||||
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
|
comma = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.WriteString(" }")
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformSet(properties Properties, tag PropertyName, value any) []PropertyName {
|
||||||
|
switch tag {
|
||||||
|
|
||||||
|
case RotateX, RotateY, RotateZ:
|
||||||
|
return setFloatProperty(properties, tag, value, 0, 1)
|
||||||
|
|
||||||
|
case Rotate, SkewX, SkewY:
|
||||||
|
return setAngleProperty(properties, tag, value)
|
||||||
|
|
||||||
|
case ScaleX, ScaleY, ScaleZ:
|
||||||
|
return setFloatProperty(properties, tag, value, -math.MaxFloat64, math.MaxFloat64)
|
||||||
|
|
||||||
|
case Perspective, TranslateX, TranslateY, TranslateZ:
|
||||||
|
return setSizeProperty(properties, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToTransformProperty(value any) TransformProperty {
|
||||||
|
|
||||||
|
parseObject := func(obj DataObject) TransformProperty {
|
||||||
|
transform := NewTransformProperty(nil)
|
||||||
|
ok := true
|
||||||
|
for i := 0; i < obj.PropertyCount(); i++ {
|
||||||
|
if prop := obj.Property(i); prop.Type() == TextNode {
|
||||||
|
if !transform.Set(PropertyName(prop.Tag()), prop.Text()) {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok && transform.empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case TransformProperty:
|
||||||
|
return value
|
||||||
|
|
||||||
|
case DataObject:
|
||||||
|
return parseObject(value)
|
||||||
|
|
||||||
|
case DataNode:
|
||||||
|
if obj := value.Object(); obj != nil {
|
||||||
|
return parseObject(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
case string:
|
||||||
|
if obj := ParseDataText(value); obj != nil {
|
||||||
|
return parseObject(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransformProperty(properties Properties, tag PropertyName, value any) bool {
|
||||||
|
if transform := valueToTransformProperty(value); transform != nil {
|
||||||
|
properties.setRaw(tag, transform)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransformProperty(properties Properties, tag PropertyName) TransformProperty {
|
||||||
|
if val := properties.getRaw(tag); val != nil {
|
||||||
|
if transform, ok := val.(TransformProperty); ok {
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTransformPropertyElement(properties Properties, tag, transformTag PropertyName, value any) []PropertyName {
|
||||||
|
srcTag := tag
|
||||||
|
tag = normalizeTransformTag(tag)
|
||||||
|
switch tag {
|
||||||
|
case Perspective, RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, TranslateX, TranslateY, TranslateZ:
|
||||||
|
var result []PropertyName = nil
|
||||||
|
if transform := getTransformProperty(properties, transformTag); transform != nil {
|
||||||
|
if result = transformSet(transform, tag, value); result != nil {
|
||||||
|
result = []PropertyName{srcTag, transformTag}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transform := NewTransformProperty(nil)
|
||||||
|
if result = transformSet(transform, tag, value); result != nil {
|
||||||
|
properties.setRaw(transformTag, transform)
|
||||||
|
result = []PropertyName{srcTag, transformTag}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorLogF(`"TransformProperty" interface does not support the "%s" property`, tag)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPerspectiveOrigin(style Properties, session Session) (SizeUnit, SizeUnit) {
|
||||||
|
x, _ := sizeProperty(style, PerspectiveOriginX, session)
|
||||||
|
y, _ := sizeProperty(style, PerspectiveOriginY, session)
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransformOrigin(style Properties, session Session) (SizeUnit, SizeUnit, SizeUnit) {
|
||||||
|
x, _ := sizeProperty(style, TransformOriginX, session)
|
||||||
|
y, _ := sizeProperty(style, TransformOriginY, session)
|
||||||
|
z, _ := sizeProperty(style, TransformOriginZ, session)
|
||||||
|
return x, y, z
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) getSkew(session Session) (AngleUnit, AngleUnit, bool) {
|
||||||
|
skewX, okX := angleProperty(transform, SkewX, session)
|
||||||
|
skewY, okY := angleProperty(transform, SkewY, session)
|
||||||
|
return skewX, skewY, okX || okY
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) getTranslate(session Session) (SizeUnit, SizeUnit, SizeUnit) {
|
||||||
|
x, _ := sizeProperty(transform, TranslateX, session)
|
||||||
|
y, _ := sizeProperty(transform, TranslateY, session)
|
||||||
|
z, _ := sizeProperty(transform, TranslateZ, session)
|
||||||
|
return x, y, z
|
||||||
|
}
|
||||||
|
|
||||||
|
func (transform *transformPropertyData) transformCSS(session Session) string {
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
if perspective, ok := sizeProperty(transform, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 {
|
||||||
|
buffer.WriteString(`perspective(`)
|
||||||
|
buffer.WriteString(perspective.cssString("0", session))
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
}
|
||||||
|
|
||||||
|
skewX, skewY, skewOK := transform.getSkew(session)
|
||||||
|
if skewOK {
|
||||||
|
buffer.WriteString(`skew(`)
|
||||||
|
buffer.WriteString(skewX.cssString())
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(skewY.cssString())
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y, z := transform.getTranslate(session)
|
||||||
|
if z.Type != Auto && z.Value != 0 {
|
||||||
|
|
||||||
|
buffer.WriteString(`translate3d(`)
|
||||||
|
buffer.WriteString(x.cssString("0", session))
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(y.cssString("0", session))
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(z.cssString("0", session))
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
|
||||||
|
} else if (x.Type != Auto && x.Value != 0) || (y.Type != Auto && y.Value != 0) {
|
||||||
|
|
||||||
|
buffer.WriteString(`translate(`)
|
||||||
|
buffer.WriteString(x.cssString("0", session))
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(y.cssString("0", session))
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
}
|
||||||
|
|
||||||
|
scaleX, okScaleX := floatTextProperty(transform, ScaleX, session, 1)
|
||||||
|
scaleY, okScaleY := floatTextProperty(transform, ScaleY, session, 1)
|
||||||
|
scaleZ, okScaleZ := floatTextProperty(transform, ScaleZ, session, 1)
|
||||||
|
if okScaleZ {
|
||||||
|
|
||||||
|
buffer.WriteString(`scale3d(`)
|
||||||
|
buffer.WriteString(scaleX)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(scaleY)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(scaleZ)
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
|
||||||
|
} else if okScaleX || okScaleY {
|
||||||
|
|
||||||
|
buffer.WriteString(`scale(`)
|
||||||
|
buffer.WriteString(scaleX)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(scaleY)
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if angle, ok := angleProperty(transform, Rotate, session); ok {
|
||||||
|
rotateX, xOK := floatTextProperty(transform, RotateX, session, 1)
|
||||||
|
rotateY, yOK := floatTextProperty(transform, RotateY, session, 1)
|
||||||
|
rotateZ, zOK := floatTextProperty(transform, RotateZ, session, 1)
|
||||||
|
|
||||||
|
if xOK || yOK || zOK {
|
||||||
|
|
||||||
|
buffer.WriteString(`rotate3d(`)
|
||||||
|
buffer.WriteString(rotateX)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(rotateY)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(rotateZ)
|
||||||
|
buffer.WriteRune(',')
|
||||||
|
buffer.WriteString(angle.cssString())
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
buffer.WriteString(`rotate(`)
|
||||||
|
buffer.WriteString(angle.cssString())
|
||||||
|
buffer.WriteString(") ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
length := buffer.Len()
|
||||||
|
if length == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := buffer.String()
|
||||||
|
return result[:length-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) {
|
||||||
|
x, y := getPerspectiveOrigin(style, session)
|
||||||
|
z := AutoSize()
|
||||||
|
if css := transformOriginCSS(x, y, z, session); css != "" {
|
||||||
|
builder.add(`perspective-origin`, css)
|
||||||
|
}
|
||||||
|
|
||||||
|
if backfaceVisible, ok := boolProperty(style, BackfaceVisible, session); ok {
|
||||||
|
if backfaceVisible {
|
||||||
|
builder.add(`backface-visibility`, `visible`)
|
||||||
|
} else {
|
||||||
|
builder.add(`backface-visibility`, `hidden`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x, y, z = getTransformOrigin(style, session)
|
||||||
|
if css := transformOriginCSS(x, y, z, session); css != "" {
|
||||||
|
builder.add(`transform-origin`, css)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transform := getTransformProperty(style, Transform); transform != nil {
|
||||||
|
builder.add(`transform`, transform.transformCSS(session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformOriginCSS(x, y, z SizeUnit, session Session) string {
|
||||||
|
if z.Type == Auto && x.Type == Auto && y.Type == Auto {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
if x.Type == SizeInPercent {
|
||||||
|
switch x.Value {
|
||||||
|
case 0:
|
||||||
|
buffer.WriteString("left")
|
||||||
|
case 50:
|
||||||
|
buffer.WriteString("center")
|
||||||
|
case 100:
|
||||||
|
buffer.WriteString("right")
|
||||||
|
|
||||||
|
default:
|
||||||
|
buffer.WriteString(x.cssString("center", session))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(x.cssString("center", session))
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
|
||||||
|
if y.Type == SizeInPercent {
|
||||||
|
switch y.Value {
|
||||||
|
case 0:
|
||||||
|
buffer.WriteString("top")
|
||||||
|
case 50:
|
||||||
|
buffer.WriteString("center")
|
||||||
|
case 100:
|
||||||
|
buffer.WriteString("bottom")
|
||||||
|
|
||||||
|
default:
|
||||||
|
buffer.WriteString(y.cssString("center", session))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(y.cssString("center", session))
|
||||||
|
}
|
||||||
|
|
||||||
|
if z.Type != Auto && z.Value != 0 {
|
||||||
|
buffer.WriteRune(' ')
|
||||||
|
buffer.WriteString(z.cssString("0", session))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.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.
|
||||||
|
func GetTransform(view View, subviewID ...string) TransformProperty {
|
||||||
|
return transformStyledProperty(view, subviewID, Transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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.
|
||||||
|
func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) {
|
||||||
|
view = getSubview(view, subviewID)
|
||||||
|
if view == nil {
|
||||||
|
return AutoSize(), AutoSize()
|
||||||
|
}
|
||||||
|
return getPerspectiveOrigin(view, view.Session())
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBackfaceVisible returns a bool property that sets whether the back face of an element is
|
||||||
|
// 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.
|
||||||
|
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.
|
||||||
|
func GetTransformOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
|
||||||
|
view = getSubview(view, subviewID)
|
||||||
|
if view == nil {
|
||||||
|
return AutoSize(), AutoSize(), AutoSize()
|
||||||
|
}
|
||||||
|
return getTransformOrigin(view, view.Session())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
|
||||||
|
if transform := GetTransform(view, subviewID...); transform != nil {
|
||||||
|
return transform.getTranslate(view.Session())
|
||||||
|
}
|
||||||
|
return AutoSize(), AutoSize(), AutoSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) {
|
||||||
|
if transform := GetTransform(view, subviewID...); transform != nil {
|
||||||
|
x, y, _ := transform.getSkew(view.Session())
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
return AngleUnit{Value: 0, Type: Radian}, AngleUnit{Value: 0, Type: Radian}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func GetScale(view View, subviewID ...string) (float64, float64, float64) {
|
||||||
|
if transform := GetTransform(view, subviewID...); transform != nil {
|
||||||
|
session := view.Session()
|
||||||
|
x, _ := floatProperty(transform, ScaleX, session, 1)
|
||||||
|
y, _ := floatProperty(transform, ScaleY, session, 1)
|
||||||
|
z, _ := floatProperty(transform, ScaleZ, session, 1)
|
||||||
|
return x, y, z
|
||||||
|
}
|
||||||
|
return 1, 1, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func GetRotate(view View, subviewID ...string) (float64, float64, float64, AngleUnit) {
|
||||||
|
if transform := GetTransform(view, subviewID...); transform != nil {
|
||||||
|
session := view.Session()
|
||||||
|
angle, _ := angleProperty(transform, Rotate, view.Session())
|
||||||
|
rotateX, _ := floatProperty(transform, RotateX, session, 1)
|
||||||
|
rotateY, _ := floatProperty(transform, RotateY, session, 1)
|
||||||
|
rotateZ, _ := floatProperty(transform, RotateZ, session, 1)
|
||||||
|
return rotateX, rotateY, rotateZ, angle
|
||||||
|
}
|
||||||
|
return 0, 0, 0, AngleUnit{Value: 0, Type: Radian}
|
||||||
|
}
|
||||||
102
videoPlayer.go
102
videoPlayer.go
|
|
@ -8,33 +8,33 @@ import (
|
||||||
const (
|
const (
|
||||||
// VideoWidth is the constant for "video-width" property tag.
|
// VideoWidth is the constant for "video-width" property tag.
|
||||||
//
|
//
|
||||||
// Used by `VideoPlayer`.
|
// Used by VideoPlayer.
|
||||||
// Defines the width of the video's display area in pixels.
|
// Defines the width of the video's display area in pixels.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Values:
|
// Values:
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
VideoWidth = "video-width"
|
VideoWidth PropertyName = "video-width"
|
||||||
|
|
||||||
// VideoHeight is the constant for "video-height" property tag.
|
// VideoHeight is the constant for "video-height" property tag.
|
||||||
//
|
//
|
||||||
// Used by `VideoPlayer`.
|
// Used by VideoPlayer.
|
||||||
// Defines the height of the video's display area in pixels.
|
// Defines the height of the video's display area in pixels.
|
||||||
//
|
//
|
||||||
// Supported types: `float`, `int`, `string`.
|
// Supported types: float, int, string.
|
||||||
//
|
//
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
// Internal type is float, other types converted to it during assignment.
|
||||||
VideoHeight = "video-height"
|
VideoHeight PropertyName = "video-height"
|
||||||
|
|
||||||
// Poster is the constant for "poster" property tag.
|
// Poster is the constant for "poster" property tag.
|
||||||
//
|
//
|
||||||
// Used by `VideoPlayer`.
|
// Used by VideoPlayer.
|
||||||
// Defines an URL for an image to be shown while the video is downloading. If this attribute isn't specified, nothing is
|
// Defines an URL for an image to be shown while the video is downloading. If this attribute isn't specified, nothing is
|
||||||
// displayed until the first frame is available, then the first frame is shown as the poster frame.
|
// displayed until the first frame is available, then the first frame is shown as the poster frame.
|
||||||
//
|
//
|
||||||
// Supported types: `string`.
|
// Supported types: string.
|
||||||
Poster = "poster"
|
Poster PropertyName = "poster"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VideoPlayer is a type of a [View] which can play video files
|
// VideoPlayer is a type of a [View] which can play video files
|
||||||
|
|
@ -50,92 +50,56 @@ type videoPlayerData struct {
|
||||||
func NewVideoPlayer(session Session, params Params) VideoPlayer {
|
func NewVideoPlayer(session Session, params Params) VideoPlayer {
|
||||||
view := new(videoPlayerData)
|
view := new(videoPlayerData)
|
||||||
view.init(session)
|
view.init(session)
|
||||||
view.tag = "VideoPlayer"
|
|
||||||
setInitParams(view, params)
|
setInitParams(view, params)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func newVideoPlayer(session Session) View {
|
func newVideoPlayer(session Session) View {
|
||||||
return NewVideoPlayer(session, nil)
|
return new(videoPlayerData) // NewVideoPlayer(session, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *videoPlayerData) init(session Session) {
|
func (player *videoPlayerData) init(session Session) {
|
||||||
player.mediaPlayerData.init(session)
|
player.mediaPlayerData.init(session)
|
||||||
player.tag = "VideoPlayer"
|
player.tag = "VideoPlayer"
|
||||||
}
|
player.changed = player.propertyChanged
|
||||||
|
|
||||||
func (player *videoPlayerData) String() string {
|
|
||||||
return getViewString(player, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *videoPlayerData) htmlTag() string {
|
func (player *videoPlayerData) htmlTag() string {
|
||||||
return "video"
|
return "video"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *videoPlayerData) Remove(tag string) {
|
func (player *videoPlayerData) propertyChanged(tag PropertyName) {
|
||||||
player.remove(strings.ToLower(tag))
|
|
||||||
}
|
session := player.Session()
|
||||||
|
updateSize := func(cssTag string) {
|
||||||
|
if size, ok := floatTextProperty(player, tag, session, 0); ok {
|
||||||
|
if size != "0" {
|
||||||
|
session.updateProperty(player.htmlID(), cssTag, size)
|
||||||
|
} else {
|
||||||
|
session.removeProperty(player.htmlID(), cssTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (player *videoPlayerData) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
|
|
||||||
case VideoWidth:
|
case VideoWidth:
|
||||||
delete(player.properties, tag)
|
updateSize("width")
|
||||||
player.session.removeProperty(player.htmlID(), "width")
|
|
||||||
|
|
||||||
case VideoHeight:
|
case VideoHeight:
|
||||||
delete(player.properties, tag)
|
updateSize("height")
|
||||||
player.session.removeProperty(player.htmlID(), "height")
|
|
||||||
|
|
||||||
case Poster:
|
case Poster:
|
||||||
delete(player.properties, tag)
|
if url, ok := stringProperty(player, Poster, session); ok {
|
||||||
player.session.removeProperty(player.htmlID(), Poster)
|
session.updateProperty(player.htmlID(), string(Poster), url)
|
||||||
|
} else {
|
||||||
|
session.removeProperty(player.htmlID(), string(Poster))
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
player.mediaPlayerData.remove(tag)
|
player.mediaPlayerData.propertyChanged(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (player *videoPlayerData) Set(tag string, value any) bool {
|
|
||||||
return player.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (player *videoPlayerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
player.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if player.mediaPlayerData.set(tag, value) {
|
|
||||||
session := player.Session()
|
|
||||||
updateSize := func(cssTag string) {
|
|
||||||
if size, ok := floatTextProperty(player, tag, session, 0); ok {
|
|
||||||
if size != "0" {
|
|
||||||
session.updateProperty(player.htmlID(), cssTag, size)
|
|
||||||
} else {
|
|
||||||
session.removeProperty(player.htmlID(), cssTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case VideoWidth:
|
|
||||||
updateSize("width")
|
|
||||||
|
|
||||||
case VideoHeight:
|
|
||||||
updateSize("height")
|
|
||||||
|
|
||||||
case Poster:
|
|
||||||
if url, ok := stringProperty(player, Poster, session); ok {
|
|
||||||
session.updateProperty(player.htmlID(), Poster, url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (player *videoPlayerData) htmlProperties(self View, buffer *strings.Builder) {
|
func (player *videoPlayerData) htmlProperties(self View, buffer *strings.Builder) {
|
||||||
player.mediaPlayerData.htmlProperties(self, buffer)
|
player.mediaPlayerData.htmlProperties(self, buffer)
|
||||||
|
|
||||||
|
|
|
||||||
375
viewByID.go
375
viewByID.go
|
|
@ -2,34 +2,45 @@ package rui
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
// ViewByID return a View with id equal to the argument of the function or nil if there is no such View
|
// ViewByID returns the child View path to which is specified using the arguments id, ids. Example
|
||||||
func ViewByID(rootView View, id string) View {
|
//
|
||||||
|
// view := ViewByID(rootView, "id1", "id2", "id3")
|
||||||
|
// view := ViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found, the function will return nil
|
||||||
|
func ViewByID(rootView View, id string, ids ...string) View {
|
||||||
if rootView == nil {
|
if rootView == nil {
|
||||||
ErrorLog(`ViewByID(nil, "` + id + `"): rootView is nil`)
|
ErrorLog(`ViewByID(nil, "` + id + `"): rootView is nil`)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if rootView.ID() == id {
|
|
||||||
return rootView
|
path := []string{id}
|
||||||
|
if len(ids) > 0 {
|
||||||
|
path = append(path, ids...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if container, ok := rootView.(ParentView); ok {
|
result := rootView
|
||||||
if view := viewByID(container, id); view != nil {
|
for _, id := range path {
|
||||||
return view
|
if result.ID() != id {
|
||||||
}
|
if container, ok := result.(ParentView); ok {
|
||||||
}
|
if view := viewByID(container, id); view != nil {
|
||||||
|
result = view
|
||||||
if index := strings.IndexRune(id, '/'); index > 0 {
|
} else if index := strings.IndexRune(id, '/'); index > 0 {
|
||||||
if view2 := ViewByID(rootView, id[:index]); view2 != nil {
|
if view := ViewByID(result, id[:index], id[index+1:]); view != nil {
|
||||||
if view := ViewByID(view2, id[index+1:]); view != nil {
|
result = view
|
||||||
return view
|
} else {
|
||||||
|
ErrorLog(`ViewByID(_, "` + id + `"): View not found`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorLog(`ViewByID(_, "` + id + `"): View not found`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
ErrorLog(`ViewByID(_, "` + id + `"): View not found`)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewByID(rootView ParentView, id string) View {
|
func viewByID(rootView ParentView, id string) View {
|
||||||
|
|
@ -49,10 +60,15 @@ func viewByID(rootView ParentView, id string) View {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ViewsContainerByID return a ViewsContainer with id equal to the argument of the function or
|
// ViewsContainerByID return the ViewsContainer path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ViewsContainer
|
//
|
||||||
func ViewsContainerByID(rootView View, id string) ViewsContainer {
|
// view := ViewsContainerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ViewsContainerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ViewsContainer, the function will return nil
|
||||||
|
func ViewsContainerByID(rootView View, id string, ids ...string) ViewsContainer {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(ViewsContainer); ok {
|
if list, ok := view.(ViewsContainer); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -61,10 +77,15 @@ func ViewsContainerByID(rootView View, id string) ViewsContainer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListLayoutByID return a ListLayout with id equal to the argument of the function or
|
// ListLayoutByID return the ListLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ListLayout
|
//
|
||||||
func ListLayoutByID(rootView View, id string) ListLayout {
|
// view := ListLayoutByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ListLayoutByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ListLayout, the function will return nil
|
||||||
|
func ListLayoutByID(rootView View, id string, ids ...string) ListLayout {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(ListLayout); ok {
|
if list, ok := view.(ListLayout); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -73,10 +94,15 @@ func ListLayoutByID(rootView View, id string) ListLayout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackLayoutByID return a StackLayout with id equal to the argument of the function or
|
// StackLayoutByID return the StackLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not StackLayout
|
//
|
||||||
func StackLayoutByID(rootView View, id string) StackLayout {
|
// view := StackLayoutByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := StackLayoutByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not StackLayout, the function will return nil
|
||||||
|
func StackLayoutByID(rootView View, id string, ids ...string) StackLayout {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(StackLayout); ok {
|
if list, ok := view.(StackLayout); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -85,10 +111,15 @@ func StackLayoutByID(rootView View, id string) StackLayout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GridLayoutByID return a GridLayout with id equal to the argument of the function or
|
// GridLayoutByID return the GridLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not GridLayout
|
//
|
||||||
func GridLayoutByID(rootView View, id string) GridLayout {
|
// view := GridLayoutByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := GridLayoutByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not GridLayout, the function will return nil
|
||||||
|
func GridLayoutByID(rootView View, id string, ids ...string) GridLayout {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(GridLayout); ok {
|
if list, ok := view.(GridLayout); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -97,10 +128,15 @@ func GridLayoutByID(rootView View, id string) GridLayout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColumnLayoutByID return a ColumnLayout with id equal to the argument of the function or
|
// ColumnLayoutByID return the ColumnLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ColumnLayout
|
//
|
||||||
func ColumnLayoutByID(rootView View, id string) ColumnLayout {
|
// view := ColumnLayoutByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ColumnLayoutByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ColumnLayout, the function will return nil
|
||||||
|
func ColumnLayoutByID(rootView View, id string, ids ...string) ColumnLayout {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(ColumnLayout); ok {
|
if list, ok := view.(ColumnLayout); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +145,15 @@ func ColumnLayoutByID(rootView View, id string) ColumnLayout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetailsViewByID return a ColumnLayout with id equal to the argument of the function or
|
// DetailsViewByID return the ColumnLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not DetailsView
|
//
|
||||||
func DetailsViewByID(rootView View, id string) DetailsView {
|
// view := DetailsViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := DetailsViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not DetailsView, the function will return nil
|
||||||
|
func DetailsViewByID(rootView View, id string, ids ...string) DetailsView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if details, ok := view.(DetailsView); ok {
|
if details, ok := view.(DetailsView); ok {
|
||||||
return details
|
return details
|
||||||
}
|
}
|
||||||
|
|
@ -121,10 +162,15 @@ func DetailsViewByID(rootView View, id string) DetailsView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropDownListByID return a DropDownListView with id equal to the argument of the function or
|
// DropDownListByID return the DropDownListView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not DropDownListView
|
//
|
||||||
func DropDownListByID(rootView View, id string) DropDownList {
|
// view := DropDownListByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := DropDownListByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not DropDownList, the function will return nil
|
||||||
|
func DropDownListByID(rootView View, id string, ids ...string) DropDownList {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(DropDownList); ok {
|
if list, ok := view.(DropDownList); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -133,10 +179,15 @@ func DropDownListByID(rootView View, id string) DropDownList {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TabsLayoutByID return a TabsLayout with id equal to the argument of the function or
|
// TabsLayoutByID return the TabsLayout path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not TabsLayout
|
//
|
||||||
func TabsLayoutByID(rootView View, id string) TabsLayout {
|
// view := TabsLayoutByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := TabsLayoutByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not TabsLayout, the function will return nil
|
||||||
|
func TabsLayoutByID(rootView View, id string, ids ...string) TabsLayout {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(TabsLayout); ok {
|
if list, ok := view.(TabsLayout); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -145,10 +196,15 @@ func TabsLayoutByID(rootView View, id string) TabsLayout {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListViewByID return a ListView with id equal to the argument of the function or
|
// ListViewByID return the ListView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ListView
|
//
|
||||||
func ListViewByID(rootView View, id string) ListView {
|
// view := ListViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ListViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ListView, the function will return nil
|
||||||
|
func ListViewByID(rootView View, id string, ids ...string) ListView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if list, ok := view.(ListView); ok {
|
if list, ok := view.(ListView); ok {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
@ -157,10 +213,15 @@ func ListViewByID(rootView View, id string) ListView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextViewByID return a TextView with id equal to the argument of the function or
|
// TextViewByID return the TextView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not TextView
|
//
|
||||||
func TextViewByID(rootView View, id string) TextView {
|
// view := TextViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := TextViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not TextView, the function will return nil
|
||||||
|
func TextViewByID(rootView View, id string, ids ...string) TextView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if text, ok := view.(TextView); ok {
|
if text, ok := view.(TextView); ok {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
@ -169,10 +230,15 @@ func TextViewByID(rootView View, id string) TextView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ButtonByID return a Button with id equal to the argument of the function or
|
// ButtonByID return the Button path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not Button
|
//
|
||||||
func ButtonByID(rootView View, id string) Button {
|
// view := ButtonByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ButtonByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not Button, the function will return nil
|
||||||
|
func ButtonByID(rootView View, id string, ids ...string) Button {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if button, ok := view.(Button); ok {
|
if button, ok := view.(Button); ok {
|
||||||
return button
|
return button
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +247,15 @@ func ButtonByID(rootView View, id string) Button {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckboxByID return a Checkbox with id equal to the argument of the function or
|
// CheckboxByID return the Checkbox path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not Checkbox
|
//
|
||||||
func CheckboxByID(rootView View, id string) Checkbox {
|
// view := CheckboxByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := CheckboxByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not Checkbox, the function will return nil
|
||||||
|
func CheckboxByID(rootView View, id string, ids ...string) Checkbox {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if checkbox, ok := view.(Checkbox); ok {
|
if checkbox, ok := view.(Checkbox); ok {
|
||||||
return checkbox
|
return checkbox
|
||||||
}
|
}
|
||||||
|
|
@ -193,10 +264,15 @@ func CheckboxByID(rootView View, id string) Checkbox {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditViewByID return a EditView with id equal to the argument of the function or
|
// EditViewByID return the EditView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not EditView
|
//
|
||||||
func EditViewByID(rootView View, id string) EditView {
|
// view := EditViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := EditViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not EditView, the function will return nil
|
||||||
|
func EditViewByID(rootView View, id string, ids ...string) EditView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if buttons, ok := view.(EditView); ok {
|
if buttons, ok := view.(EditView); ok {
|
||||||
return buttons
|
return buttons
|
||||||
}
|
}
|
||||||
|
|
@ -205,10 +281,15 @@ func EditViewByID(rootView View, id string) EditView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProgressBarByID return a ProgressBar with id equal to the argument of the function or
|
// ProgressBarByID return the ProgressBar path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ProgressBar
|
//
|
||||||
func ProgressBarByID(rootView View, id string) ProgressBar {
|
// view := ProgressBarByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ProgressBarByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ProgressBar, the function will return nil
|
||||||
|
func ProgressBarByID(rootView View, id string, ids ...string) ProgressBar {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if buttons, ok := view.(ProgressBar); ok {
|
if buttons, ok := view.(ProgressBar); ok {
|
||||||
return buttons
|
return buttons
|
||||||
}
|
}
|
||||||
|
|
@ -217,10 +298,15 @@ func ProgressBarByID(rootView View, id string) ProgressBar {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ColorPickerByID return a ColorPicker with id equal to the argument of the function or
|
// ColorPickerByID return the ColorPicker path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ColorPicker
|
//
|
||||||
func ColorPickerByID(rootView View, id string) ColorPicker {
|
// view := ColorPickerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ColorPickerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ColorPicker, the function will return nil
|
||||||
|
func ColorPickerByID(rootView View, id string, ids ...string) ColorPicker {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if input, ok := view.(ColorPicker); ok {
|
if input, ok := view.(ColorPicker); ok {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -229,10 +315,15 @@ func ColorPickerByID(rootView View, id string) ColorPicker {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumberPickerByID return a NumberPicker with id equal to the argument of the function or
|
// NumberPickerByID return the NumberPicker path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not NumberPicker
|
//
|
||||||
func NumberPickerByID(rootView View, id string) NumberPicker {
|
// view := NumberPickerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := NumberPickerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not NumberPicker, the function will return nil
|
||||||
|
func NumberPickerByID(rootView View, id string, ids ...string) NumberPicker {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if input, ok := view.(NumberPicker); ok {
|
if input, ok := view.(NumberPicker); ok {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -241,10 +332,15 @@ func NumberPickerByID(rootView View, id string) NumberPicker {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimePickerByID return a TimePicker with id equal to the argument of the function or
|
// TimePickerByID return the TimePicker path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not TimePicker
|
//
|
||||||
func TimePickerByID(rootView View, id string) TimePicker {
|
// view := TimePickerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := TimePickerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not TimePicker, the function will return nil
|
||||||
|
func TimePickerByID(rootView View, id string, ids ...string) TimePicker {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if input, ok := view.(TimePicker); ok {
|
if input, ok := view.(TimePicker); ok {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -253,10 +349,15 @@ func TimePickerByID(rootView View, id string) TimePicker {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DatePickerByID return a DatePicker with id equal to the argument of the function or
|
// DatePickerByID return the DatePicker path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not DatePicker
|
//
|
||||||
func DatePickerByID(rootView View, id string) DatePicker {
|
// view := DatePickerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := DatePickerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not DatePicker, the function will return nil
|
||||||
|
func DatePickerByID(rootView View, id string, ids ...string) DatePicker {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if input, ok := view.(DatePicker); ok {
|
if input, ok := view.(DatePicker); ok {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -265,10 +366,15 @@ func DatePickerByID(rootView View, id string) DatePicker {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilePickerByID return a FilePicker with id equal to the argument of the function or
|
// FilePickerByID return the FilePicker path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not FilePicker
|
//
|
||||||
func FilePickerByID(rootView View, id string) FilePicker {
|
// view := FilePickerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := FilePickerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not FilePicker, the function will return nil
|
||||||
|
func FilePickerByID(rootView View, id string, ids ...string) FilePicker {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if input, ok := view.(FilePicker); ok {
|
if input, ok := view.(FilePicker); ok {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
@ -277,10 +383,15 @@ func FilePickerByID(rootView View, id string) FilePicker {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanvasViewByID return a CanvasView with id equal to the argument of the function or
|
// CanvasViewByID return the CanvasView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not CanvasView
|
//
|
||||||
func CanvasViewByID(rootView View, id string) CanvasView {
|
// view := CanvasViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := CanvasViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not CanvasView, the function will return nil
|
||||||
|
func CanvasViewByID(rootView View, id string, ids ...string) CanvasView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if canvas, ok := view.(CanvasView); ok {
|
if canvas, ok := view.(CanvasView); ok {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
@ -289,24 +400,15 @@ func CanvasViewByID(rootView View, id string) CanvasView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// AudioPlayerByID return the AudioPlayer path to which is specified using the arguments id, ids. Example
|
||||||
// TableViewByID return a TableView with id equal to the argument of the function or
|
//
|
||||||
// nil if there is no such View or View is not TableView
|
// view := AudioPlayerByID(rootView, "id1", "id2", "id3")
|
||||||
func TableViewByID(rootView View, id string) TableView {
|
// view := AudioPlayerByID(rootView, "id1/id2/id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
//
|
||||||
if canvas, ok := view.(TableView); ok {
|
// These two function calls are equivalent.
|
||||||
return canvas
|
// If a View with this path is not found or View is not AudioPlayer, the function will return nil
|
||||||
}
|
func AudioPlayerByID(rootView View, id string, ids ...string) AudioPlayer {
|
||||||
ErrorLog(`TableViewByID(_, "` + id + `"): The found View is not TableView`)
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// AudioPlayerByID return a AudioPlayer with id equal to the argument of the function or
|
|
||||||
// nil if there is no such View or View is not AudioPlayer
|
|
||||||
func AudioPlayerByID(rootView View, id string) AudioPlayer {
|
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
|
||||||
if canvas, ok := view.(AudioPlayer); ok {
|
if canvas, ok := view.(AudioPlayer); ok {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
@ -315,10 +417,15 @@ func AudioPlayerByID(rootView View, id string) AudioPlayer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoPlayerByID return a VideoPlayer with id equal to the argument of the function or
|
// VideoPlayerByID return the VideoPlayer path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not VideoPlayer
|
//
|
||||||
func VideoPlayerByID(rootView View, id string) VideoPlayer {
|
// view := VideoPlayerByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := VideoPlayerByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not VideoPlayer, the function will return nil
|
||||||
|
func VideoPlayerByID(rootView View, id string, ids ...string) VideoPlayer {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if canvas, ok := view.(VideoPlayer); ok {
|
if canvas, ok := view.(VideoPlayer); ok {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
@ -327,10 +434,15 @@ func VideoPlayerByID(rootView View, id string) VideoPlayer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageViewByID return a ImageView with id equal to the argument of the function or
|
// ImageViewByID return the ImageView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not ImageView
|
//
|
||||||
func ImageViewByID(rootView View, id string) ImageView {
|
// view := ImageViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := ImageViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not ImageView, the function will return nil
|
||||||
|
func ImageViewByID(rootView View, id string, ids ...string) ImageView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if canvas, ok := view.(ImageView); ok {
|
if canvas, ok := view.(ImageView); ok {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
@ -339,10 +451,15 @@ func ImageViewByID(rootView View, id string) ImageView {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableViewByID return a TableView with id equal to the argument of the function or
|
// TableViewByID return the TableView path to which is specified using the arguments id, ids. Example
|
||||||
// nil if there is no such View or View is not TableView
|
//
|
||||||
func TableViewByID(rootView View, id string) TableView {
|
// view := TableViewByID(rootView, "id1", "id2", "id3")
|
||||||
if view := ViewByID(rootView, id); view != nil {
|
// view := TableViewByID(rootView, "id1/id2/id3")
|
||||||
|
//
|
||||||
|
// These two function calls are equivalent.
|
||||||
|
// If a View with this path is not found or View is not TableView, the function will return nil
|
||||||
|
func TableViewByID(rootView View, id string, ids ...string) TableView {
|
||||||
|
if view := ViewByID(rootView, id, ids...); view != nil {
|
||||||
if canvas, ok := view.(TableView); ok {
|
if canvas, ok := view.(TableView); ok {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
|
||||||
580
viewClip.go
580
viewClip.go
|
|
@ -1,580 +0,0 @@
|
||||||
package rui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClipShape defines a View clipping area
|
|
||||||
type ClipShape interface {
|
|
||||||
Properties
|
|
||||||
fmt.Stringer
|
|
||||||
stringWriter
|
|
||||||
cssStyle(session Session) string
|
|
||||||
valid(session Session) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type insetClip struct {
|
|
||||||
propertyList
|
|
||||||
}
|
|
||||||
|
|
||||||
type ellipseClip struct {
|
|
||||||
propertyList
|
|
||||||
}
|
|
||||||
|
|
||||||
type circleClip struct {
|
|
||||||
propertyList
|
|
||||||
}
|
|
||||||
|
|
||||||
type polygonClip struct {
|
|
||||||
points []any
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsetClip creates a rectangle View clipping area.
|
|
||||||
// top - offset from the top border of a View;
|
|
||||||
// right - offset from the right border of a View;
|
|
||||||
// bottom - offset from the bottom border of a View;
|
|
||||||
// left - offset from the left border of a View;
|
|
||||||
// radius - corner radius, pass nil if you don't need to round corners
|
|
||||||
func InsetClip(top, right, bottom, left SizeUnit, radius RadiusProperty) ClipShape {
|
|
||||||
clip := new(insetClip)
|
|
||||||
clip.init()
|
|
||||||
clip.Set(Top, top)
|
|
||||||
clip.Set(Right, right)
|
|
||||||
clip.Set(Bottom, bottom)
|
|
||||||
clip.Set(Left, left)
|
|
||||||
if radius != nil {
|
|
||||||
clip.Set(Radius, radius)
|
|
||||||
}
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
|
|
||||||
// CircleClip creates a circle View clipping area.
|
|
||||||
func CircleClip(x, y, radius SizeUnit) ClipShape {
|
|
||||||
clip := new(circleClip)
|
|
||||||
clip.init()
|
|
||||||
clip.Set(X, x)
|
|
||||||
clip.Set(Y, y)
|
|
||||||
clip.Set(Radius, radius)
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
|
|
||||||
// EllipseClip creates a ellipse View clipping area.
|
|
||||||
func EllipseClip(x, y, rx, ry SizeUnit) ClipShape {
|
|
||||||
clip := new(ellipseClip)
|
|
||||||
clip.init()
|
|
||||||
clip.Set(X, x)
|
|
||||||
clip.Set(Y, y)
|
|
||||||
clip.Set(RadiusX, rx)
|
|
||||||
clip.Set(RadiusY, ry)
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
|
|
||||||
// PolygonClip creates a polygon View clipping area.
|
|
||||||
// The elements of the function argument can be or text constants,
|
|
||||||
// or the text representation of SizeUnit, or elements of SizeUnit type.
|
|
||||||
func PolygonClip(points []any) ClipShape {
|
|
||||||
clip := new(polygonClip)
|
|
||||||
clip.points = []any{}
|
|
||||||
if clip.Set(Points, points) {
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PolygonPointsClip creates a polygon View clipping area.
|
|
||||||
func PolygonPointsClip(points []SizeUnit) ClipShape {
|
|
||||||
clip := new(polygonClip)
|
|
||||||
clip.points = []any{}
|
|
||||||
if clip.Set(Points, points) {
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *insetClip) Set(tag string, value any) bool {
|
|
||||||
switch strings.ToLower(tag) {
|
|
||||||
case Top, Right, Bottom, Left:
|
|
||||||
if value == nil {
|
|
||||||
clip.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return clip.setSizeProperty(tag, value)
|
|
||||||
|
|
||||||
case Radius:
|
|
||||||
return clip.setRadius(value)
|
|
||||||
|
|
||||||
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
|
||||||
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
|
||||||
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
|
||||||
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
|
||||||
return clip.setRadiusElement(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by the inset clip shape`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *insetClip) String() string {
|
|
||||||
return runStringWriter(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *insetClip) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
buffer.WriteString("inset { ")
|
|
||||||
comma := false
|
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left, Radius} {
|
|
||||||
if value, ok := clip.properties[tag]; ok {
|
|
||||||
if comma {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteString(" = ")
|
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
|
||||||
comma = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *insetClip) cssStyle(session Session) string {
|
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
leadText := "inset("
|
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left} {
|
|
||||||
value, _ := sizeProperty(clip, tag, session)
|
|
||||||
buffer.WriteString(leadText)
|
|
||||||
buffer.WriteString(value.cssString("0px", session))
|
|
||||||
leadText = " "
|
|
||||||
}
|
|
||||||
|
|
||||||
if radius := getRadiusProperty(clip); radius != nil {
|
|
||||||
buffer.WriteString(" round ")
|
|
||||||
buffer.WriteString(radius.BoxRadius(session).cssString(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *insetClip) valid(session Session) bool {
|
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
|
|
||||||
if value, ok := sizeProperty(clip, tag, session); ok && value.Type != Auto && value.Value != 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *circleClip) Set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
clip.Remove(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(tag) {
|
|
||||||
case X, Y, Radius:
|
|
||||||
return clip.setSizeProperty(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by the circle clip shape`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *circleClip) String() string {
|
|
||||||
return runStringWriter(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *circleClip) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
buffer.WriteString("circle { ")
|
|
||||||
comma := false
|
|
||||||
for _, tag := range []string{Radius, X, Y} {
|
|
||||||
if value, ok := clip.properties[tag]; ok {
|
|
||||||
if comma {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteString(" = ")
|
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
|
||||||
comma = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *circleClip) cssStyle(session Session) string {
|
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
buffer.WriteString("circle(")
|
|
||||||
r, _ := sizeProperty(clip, Radius, session)
|
|
||||||
buffer.WriteString(r.cssString("50%", session))
|
|
||||||
|
|
||||||
buffer.WriteString(" at ")
|
|
||||||
x, _ := sizeProperty(clip, X, session)
|
|
||||||
buffer.WriteString(x.cssString("50%", session))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
|
|
||||||
y, _ := sizeProperty(clip, Y, session)
|
|
||||||
buffer.WriteString(y.cssString("50%", session))
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *circleClip) valid(session Session) bool {
|
|
||||||
if value, ok := sizeProperty(clip, Radius, session); ok && value.Value == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *ellipseClip) Set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
clip.Remove(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(tag) {
|
|
||||||
case X, Y, RadiusX, RadiusY:
|
|
||||||
return clip.setSizeProperty(tag, value)
|
|
||||||
|
|
||||||
case Radius:
|
|
||||||
return clip.setSizeProperty(RadiusX, value) &&
|
|
||||||
clip.setSizeProperty(RadiusY, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by the ellipse clip shape`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *ellipseClip) String() string {
|
|
||||||
return runStringWriter(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *ellipseClip) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
buffer.WriteString("ellipse { ")
|
|
||||||
comma := false
|
|
||||||
for _, tag := range []string{RadiusX, RadiusY, X, Y} {
|
|
||||||
if value, ok := clip.properties[tag]; ok {
|
|
||||||
if comma {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteString(" = ")
|
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
|
||||||
comma = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *ellipseClip) cssStyle(session Session) string {
|
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
rx, _ := sizeProperty(clip, RadiusX, session)
|
|
||||||
ry, _ := sizeProperty(clip, RadiusX, session)
|
|
||||||
buffer.WriteString("ellipse(")
|
|
||||||
buffer.WriteString(rx.cssString("50%", session))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(ry.cssString("50%", session))
|
|
||||||
|
|
||||||
buffer.WriteString(" at ")
|
|
||||||
x, _ := sizeProperty(clip, X, session)
|
|
||||||
buffer.WriteString(x.cssString("50%", session))
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
|
|
||||||
y, _ := sizeProperty(clip, Y, session)
|
|
||||||
buffer.WriteString(y.cssString("50%", session))
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *ellipseClip) valid(session Session) bool {
|
|
||||||
rx, _ := sizeProperty(clip, RadiusX, session)
|
|
||||||
ry, _ := sizeProperty(clip, RadiusY, session)
|
|
||||||
return rx.Value != 0 && ry.Value != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) Get(tag string) any {
|
|
||||||
if Points == strings.ToLower(tag) {
|
|
||||||
return clip.points
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) getRaw(tag string) any {
|
|
||||||
return clip.Get(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) Set(tag string, value any) bool {
|
|
||||||
if Points == strings.ToLower(tag) {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case []any:
|
|
||||||
result := true
|
|
||||||
clip.points = make([]any, len(value))
|
|
||||||
for i, val := range value {
|
|
||||||
switch val := val.(type) {
|
|
||||||
case string:
|
|
||||||
if isConstantName(val) {
|
|
||||||
clip.points[i] = val
|
|
||||||
} else if size, ok := StringToSizeUnit(val); ok {
|
|
||||||
clip.points[i] = size
|
|
||||||
} else {
|
|
||||||
notCompatibleType(tag, val)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
clip.points[i] = val
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(tag, val)
|
|
||||||
clip.points[i] = AutoSize()
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
|
|
||||||
case []SizeUnit:
|
|
||||||
clip.points = make([]any, len(value))
|
|
||||||
for i, point := range value {
|
|
||||||
clip.points[i] = point
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
case string:
|
|
||||||
result := true
|
|
||||||
values := strings.Split(value, ",")
|
|
||||||
clip.points = make([]any, len(values))
|
|
||||||
for i, val := range values {
|
|
||||||
val = strings.Trim(val, " \t\n\r")
|
|
||||||
if isConstantName(val) {
|
|
||||||
clip.points[i] = val
|
|
||||||
} else if size, ok := StringToSizeUnit(val); ok {
|
|
||||||
clip.points[i] = size
|
|
||||||
} else {
|
|
||||||
notCompatibleType(tag, val)
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) setRaw(tag string, value any) {
|
|
||||||
clip.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) Remove(tag string) {
|
|
||||||
if Points == strings.ToLower(tag) {
|
|
||||||
clip.points = []any{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) Clear() {
|
|
||||||
clip.points = []any{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) AllTags() []string {
|
|
||||||
return []string{Points}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) String() string {
|
|
||||||
return runStringWriter(clip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
|
|
||||||
buffer.WriteString("inset { ")
|
|
||||||
|
|
||||||
if clip.points != nil {
|
|
||||||
buffer.WriteString(Points)
|
|
||||||
buffer.WriteString(` = "`)
|
|
||||||
for i, value := range clip.points {
|
|
||||||
if i > 0 {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
writePropertyValue(buffer, "", value, indent)
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(`" `)
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteRune('}')
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) cssStyle(session Session) string {
|
|
||||||
|
|
||||||
count := len(clip.points)
|
|
||||||
if count < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
writePoint := func(value any) {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if val, ok := session.resolveConstants(value); ok {
|
|
||||||
if size, ok := StringToSizeUnit(val); ok {
|
|
||||||
buffer.WriteString(size.cssString("0px", session))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case SizeUnit:
|
|
||||||
buffer.WriteString(value.cssString("0px", session))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString("0px")
|
|
||||||
}
|
|
||||||
|
|
||||||
leadText := "polygon("
|
|
||||||
for i := 1; i < count; i += 2 {
|
|
||||||
buffer.WriteString(leadText)
|
|
||||||
writePoint(clip.points[i-1])
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
writePoint(clip.points[i])
|
|
||||||
leadText = ", "
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clip *polygonClip) valid(session Session) bool {
|
|
||||||
return len(clip.points) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseClipShape(obj DataObject) ClipShape {
|
|
||||||
switch obj.Tag() {
|
|
||||||
case "inset":
|
|
||||||
clip := new(insetClip)
|
|
||||||
for _, tag := range []string{Top, Right, Bottom, Left, Radius, RadiusX, RadiusY} {
|
|
||||||
if value, ok := obj.PropertyValue(tag); ok {
|
|
||||||
clip.Set(tag, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clip
|
|
||||||
|
|
||||||
case "circle":
|
|
||||||
clip := new(ellipseClip)
|
|
||||||
for _, tag := range []string{X, Y, Radius} {
|
|
||||||
if value, ok := obj.PropertyValue(tag); ok {
|
|
||||||
clip.Set(tag, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clip
|
|
||||||
|
|
||||||
case "ellipse":
|
|
||||||
clip := new(ellipseClip)
|
|
||||||
for _, tag := range []string{X, Y, RadiusX, RadiusY} {
|
|
||||||
if value, ok := obj.PropertyValue(tag); ok {
|
|
||||||
clip.Set(tag, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clip
|
|
||||||
|
|
||||||
case "polygon":
|
|
||||||
clip := new(ellipseClip)
|
|
||||||
if value, ok := obj.PropertyValue(Points); ok {
|
|
||||||
clip.Set(Points, value)
|
|
||||||
}
|
|
||||||
return clip
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setClipShape(tag string, value any) bool {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case ClipShape:
|
|
||||||
style.properties[tag] = value
|
|
||||||
return true
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if isConstantName(value) {
|
|
||||||
style.properties[tag] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj := NewDataObject(value); obj == nil {
|
|
||||||
if clip := parseClipShape(obj); clip != nil {
|
|
||||||
style.properties[tag] = clip
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataObject:
|
|
||||||
if clip := parseClipShape(value); clip != nil {
|
|
||||||
style.properties[tag] = clip
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataValue:
|
|
||||||
if value.IsObject() {
|
|
||||||
if clip := parseClipShape(value.Object()); clip != nil {
|
|
||||||
style.properties[tag] = clip
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getClipShape(prop Properties, tag string, session Session) ClipShape {
|
|
||||||
if value := prop.getRaw(tag); value != nil {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case ClipShape:
|
|
||||||
return value
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if text, ok := session.resolveConstants(value); ok {
|
|
||||||
if obj := NewDataObject(text); obj == nil {
|
|
||||||
return parseClipShape(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClip returns a View clipping area.
|
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
|
||||||
func GetClip(view View, subviewID ...string) ClipShape {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getClipShape(view, Clip, view.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetShapeOutside returns a shape around which adjacent inline content.
|
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
|
||||||
func GetShapeOutside(view View, subviewID ...string) ClipShape {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
return getClipShape(view, ShapeOutside, view.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -85,6 +85,7 @@ func CreateViewFromObject(session Session, object DataObject) View {
|
||||||
defer session.setIgnoreViewUpdates(false)
|
defer session.setIgnoreViewUpdates(false)
|
||||||
}
|
}
|
||||||
view := creator(session)
|
view := creator(session)
|
||||||
|
view.init(session)
|
||||||
if customView, ok := view.(CustomView); ok {
|
if customView, ok := view.(CustomView); ok {
|
||||||
if !InitCustomView(customView, tag, session, nil) {
|
if !InitCustomView(customView, tag, session, nil) {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
363
viewFilter.go
363
viewFilter.go
|
|
@ -1,363 +0,0 @@
|
||||||
package rui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for [ViewFilter] specific properties and events
|
|
||||||
const (
|
|
||||||
// Blur is the constant for "blur" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Applies a Gaussian blur. The value of radius defines the value of the standard deviation to the Gaussian function, or
|
|
||||||
// how many pixels on the screen blend into each other, so a larger value will create more blur. The lacuna value for
|
|
||||||
// interpolation is 0. The parameter is specified as a length in pixels.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Blur = "blur"
|
|
||||||
|
|
||||||
// Brightness is the constant for "brightness" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Applies a linear multiplier to input image, making it appear more or less bright. A value of 0% will create an image
|
|
||||||
// that is completely black. A value of 100% leaves the input unchanged. Other values are linear multipliers on the
|
|
||||||
// effect. Values of an amount over 100% are allowed, providing brighter results.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Brightness = "brightness"
|
|
||||||
|
|
||||||
// Contrast is the constant for "contrast" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Adjusts the contrast of the input. A value of 0% will create an image that is completely black. A value of 100% leaves
|
|
||||||
// the input unchanged. Values of amount over 100% are allowed, providing results with less contrast.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Contrast = "contrast"
|
|
||||||
|
|
||||||
// DropShadow is the constant for "drop-shadow" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Applies a drop shadow effect to the input image. A drop shadow is effectively a blurred, offset version of the input
|
|
||||||
// image's alpha mask drawn in a particular color, composited below the image. Shadow parameters are set using the
|
|
||||||
// `ViewShadow` interface.
|
|
||||||
//
|
|
||||||
// Supported types: `[]ViewShadow`, `ViewShadow`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `[]ViewShadow`, other types converted to it during assignment.
|
|
||||||
// See `ViewShadow` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `[]ViewShadow` - stored as is, no conversion performed.
|
|
||||||
// `ViewShadow` - converted to `[]ViewShadow`.
|
|
||||||
// `string` - string representation of `ViewShadow`. Example: "_{blur = 1em, color = black, spread-radius = 0.5em}".
|
|
||||||
DropShadow = "drop-shadow"
|
|
||||||
|
|
||||||
// Grayscale is the constant for "grayscale" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Converts the input image to grayscale. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
|
||||||
// is completely grayscale. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
|
||||||
// the effect.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Grayscale = "grayscale"
|
|
||||||
|
|
||||||
// HueRotate is the constant for "hue-rotate" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Applies a hue rotation on the input image. The value of ‘angle’ defines the number of degrees around the color circle
|
|
||||||
// the input samples will be adjusted. A value of 0deg leaves the input unchanged. If the ‘angle’ parameter is missing, a
|
|
||||||
// value of 0deg is used. Though there is no maximum value, the effect of values above 360deg wraps around.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
HueRotate = "hue-rotate"
|
|
||||||
|
|
||||||
// Invert is the constant for "invert" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Inverts the samples in the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 100%
|
|
||||||
// is completely inverted. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on
|
|
||||||
// the effect.
|
|
||||||
//
|
|
||||||
// Supported types: `float64`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Invert = "invert"
|
|
||||||
|
|
||||||
// Saturate is the constant for "saturate" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Saturates the input image. The value of ‘amount’ defines the proportion of the conversion. A value of 0% is completely
|
|
||||||
// un-saturated. A value of 100% leaves the input unchanged. Other values are linear multipliers on the effect. Values of
|
|
||||||
// amount over 100% are allowed, providing super-saturated results.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Saturate = "saturate"
|
|
||||||
|
|
||||||
// Sepia is the constant for "sepia" property tag.
|
|
||||||
//
|
|
||||||
// Used by `ViewFilter`.
|
|
||||||
// Converts the input image to sepia. The value of ‘amount’ defines the proportion of the conversion. A value of 100% is
|
|
||||||
// completely sepia. A value of 0% leaves the input unchanged. Values between 0% and 100% are linear multipliers on the
|
|
||||||
// effect.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
Sepia = "sepia"
|
|
||||||
|
|
||||||
//Opacity = "opacity"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ViewFilter defines an applied to a View a graphical effects like blur or color shift.
|
|
||||||
// Allowable properties are Blur, Brightness, Contrast, DropShadow, Grayscale, HueRotate, Invert, Opacity, Saturate, and Sepia
|
|
||||||
type ViewFilter interface {
|
|
||||||
Properties
|
|
||||||
fmt.Stringer
|
|
||||||
stringWriter
|
|
||||||
cssStyle(session Session) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type viewFilter struct {
|
|
||||||
propertyList
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewViewFilter creates the new ViewFilter
|
|
||||||
func NewViewFilter(params Params) ViewFilter {
|
|
||||||
if params != nil {
|
|
||||||
filter := new(viewFilter)
|
|
||||||
filter.init()
|
|
||||||
for tag, value := range params {
|
|
||||||
filter.Set(tag, value)
|
|
||||||
}
|
|
||||||
if len(filter.properties) > 0 {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newViewFilter(obj DataObject) ViewFilter {
|
|
||||||
filter := new(viewFilter)
|
|
||||||
filter.init()
|
|
||||||
for i := 0; i < obj.PropertyCount(); i++ {
|
|
||||||
if node := obj.Property(i); node != nil {
|
|
||||||
tag := node.Tag()
|
|
||||||
switch node.Type() {
|
|
||||||
case TextNode:
|
|
||||||
filter.Set(tag, node.Text())
|
|
||||||
|
|
||||||
case ObjectNode:
|
|
||||||
if tag == HueRotate {
|
|
||||||
// TODO
|
|
||||||
} else {
|
|
||||||
ErrorLog(`Invalid value of "` + tag + `"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
ErrorLog(`Invalid value of "` + tag + `"`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filter.properties) > 0 {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
ErrorLog("Empty view filter")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (filter *viewFilter) Set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
filter.Remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToLower(tag) {
|
|
||||||
case Blur, Brightness, Contrast, Saturate:
|
|
||||||
return filter.setFloatProperty(tag, value, 0, 10000)
|
|
||||||
|
|
||||||
case Grayscale, Invert, Opacity, Sepia:
|
|
||||||
return filter.setFloatProperty(tag, value, 0, 100)
|
|
||||||
|
|
||||||
case HueRotate:
|
|
||||||
return filter.setAngleProperty(tag, value)
|
|
||||||
|
|
||||||
case DropShadow:
|
|
||||||
return filter.setShadow(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"%s" property is not supported by the view filter`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (filter *viewFilter) String() string {
|
|
||||||
return runStringWriter(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (filter *viewFilter) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
buffer.WriteString("filter { ")
|
|
||||||
comma := false
|
|
||||||
tags := filter.AllTags()
|
|
||||||
for _, tag := range tags {
|
|
||||||
if value, ok := filter.properties[tag]; ok {
|
|
||||||
if comma {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteString(" = ")
|
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
|
||||||
comma = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.WriteString(" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (filter *viewFilter) cssStyle(session Session) string {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
if value, ok := floatTextProperty(filter, Blur, session, 0); ok {
|
|
||||||
buffer.WriteString(Blur)
|
|
||||||
buffer.WriteRune('(')
|
|
||||||
buffer.WriteString(value)
|
|
||||||
buffer.WriteString("px)")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tag := range []string{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} {
|
|
||||||
if value, ok := floatTextProperty(filter, tag, session, 0); ok {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteRune('(')
|
|
||||||
buffer.WriteString(value)
|
|
||||||
buffer.WriteString("%)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if value, ok := angleProperty(filter, HueRotate, session); ok {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(HueRotate)
|
|
||||||
buffer.WriteRune('(')
|
|
||||||
buffer.WriteString(value.cssString())
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
var lead string
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
lead = " drop-shadow("
|
|
||||||
} else {
|
|
||||||
lead = "drop-shadow("
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, shadow := range getShadows(filter, DropShadow) {
|
|
||||||
if shadow.cssTextStyle(buffer, session, lead) {
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
lead = " drop-shadow("
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setFilter(tag string, value any) bool {
|
|
||||||
switch value := value.(type) {
|
|
||||||
case ViewFilter:
|
|
||||||
style.properties[tag] = value
|
|
||||||
return true
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if obj := NewDataObject(value); obj == nil {
|
|
||||||
if filter := newViewFilter(obj); filter != nil {
|
|
||||||
style.properties[tag] = filter
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case DataObject:
|
|
||||||
if filter := newViewFilter(value); filter != nil {
|
|
||||||
style.properties[tag] = filter
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataValue:
|
|
||||||
if value.IsObject() {
|
|
||||||
if filter := newViewFilter(value.Object()); filter != nil {
|
|
||||||
style.properties[tag] = filter
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFilter returns a View graphical effects like blur or color shift.
|
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
|
||||||
func GetFilter(view View, subviewID ...string) ViewFilter {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.getRaw(Filter); value != nil {
|
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if value := valueFromStyle(view, Filter); value != nil {
|
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBackdropFilter returns the area behind a View graphical effects like blur or color shift.
|
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
|
||||||
func GetBackdropFilter(view View, subviewID ...string) ViewFilter {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.getRaw(BackdropFilter); value != nil {
|
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if value := valueFromStyle(view, BackdropFilter); value != nil {
|
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
294
viewStyle.go
294
viewStyle.go
|
|
@ -12,72 +12,31 @@ type ViewStyle interface {
|
||||||
Properties
|
Properties
|
||||||
|
|
||||||
// Transition returns the transition animation of the property. Returns nil is there is no transition animation.
|
// Transition returns the transition animation of the property. Returns nil is there is no transition animation.
|
||||||
Transition(tag string) Animation
|
Transition(tag PropertyName) AnimationProperty
|
||||||
|
|
||||||
// Transitions returns the map of transition animations. The result is always non-nil.
|
// Transitions returns the map of transition animations. The result is always non-nil.
|
||||||
Transitions() map[string]Animation
|
Transitions() map[PropertyName]AnimationProperty
|
||||||
|
|
||||||
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
// SetTransition sets the transition animation for the property if "animation" argument is not nil, and
|
||||||
// removes the transition animation of the property if "animation" argument is nil.
|
// removes the transition animation of the property if "animation" argument is nil.
|
||||||
// The "tag" argument is the property name.
|
// The "tag" argument is the property name.
|
||||||
SetTransition(tag string, animation Animation)
|
SetTransition(tag PropertyName, animation AnimationProperty)
|
||||||
|
|
||||||
cssViewStyle(buffer cssBuilder, session Session)
|
cssViewStyle(buffer cssBuilder, session Session)
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewStyle struct {
|
type viewStyle struct {
|
||||||
propertyList
|
propertyList
|
||||||
transitions map[string]Animation
|
//transitions map[PropertyName]Animation
|
||||||
}
|
|
||||||
|
|
||||||
// Range defines range limits. The First and Last value are included in the range
|
|
||||||
type Range struct {
|
|
||||||
First, Last int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type stringWriter interface {
|
type stringWriter interface {
|
||||||
writeString(buffer *strings.Builder, indent string)
|
writeString(buffer *strings.Builder, indent string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a string representation of the Range struct
|
|
||||||
func (r Range) String() string {
|
|
||||||
if r.First == r.Last {
|
|
||||||
return fmt.Sprintf("%d", r.First)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d:%d", r.First, r.Last)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Range) setValue(value string) bool {
|
|
||||||
var err error
|
|
||||||
if strings.Contains(value, ":") {
|
|
||||||
values := strings.Split(value, ":")
|
|
||||||
if len(values) != 2 {
|
|
||||||
ErrorLog("Invalid range value: " + value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r.First, err = strconv.Atoi(strings.Trim(values[0], " \t\n\r")); err != nil {
|
|
||||||
ErrorLog(`Invalid first range value "` + value + `" (` + err.Error() + ")")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r.Last, err = strconv.Atoi(strings.Trim(values[1], " \t\n\r")); err != nil {
|
|
||||||
ErrorLog(`Invalid last range value "` + value + `" (` + err.Error() + ")")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.First, err = strconv.Atoi(value); err != nil {
|
|
||||||
ErrorLog(`Invalid range value "` + value + `" (` + err.Error() + ")")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
r.Last = r.First
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) init() {
|
func (style *viewStyle) init() {
|
||||||
style.propertyList.init()
|
style.propertyList.init()
|
||||||
//style.shadows = []ViewShadow{}
|
style.normalize = normalizeViewStyleTag
|
||||||
style.transitions = map[string]Animation{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewViewStyle create new ViewStyle object
|
// NewViewStyle create new ViewStyle object
|
||||||
|
|
@ -90,19 +49,19 @@ func NewViewStyle(params Params) ViewStyle {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) cssTextDecoration(session Session) string {
|
func textDecorationCSS(properties Properties, session Session) string {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
noDecoration := false
|
noDecoration := false
|
||||||
if strikethrough, ok := boolProperty(style, Strikethrough, session); ok {
|
if strikethrough, ok := boolProperty(properties, Strikethrough, session); ok {
|
||||||
if strikethrough {
|
if strikethrough {
|
||||||
buffer.WriteString("line-through")
|
buffer.WriteString("line-through")
|
||||||
}
|
}
|
||||||
noDecoration = true
|
noDecoration = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if overline, ok := boolProperty(style, Overline, session); ok {
|
if overline, ok := boolProperty(properties, Overline, session); ok {
|
||||||
if overline {
|
if overline {
|
||||||
if buffer.Len() > 0 {
|
if buffer.Len() > 0 {
|
||||||
buffer.WriteRune(' ')
|
buffer.WriteRune(' ')
|
||||||
|
|
@ -112,7 +71,7 @@ func (style *viewStyle) cssTextDecoration(session Session) string {
|
||||||
noDecoration = true
|
noDecoration = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if underline, ok := boolProperty(style, Underline, session); ok {
|
if underline, ok := boolProperty(properties, Underline, session); ok {
|
||||||
if underline {
|
if underline {
|
||||||
if buffer.Len() > 0 {
|
if buffer.Len() > 0 {
|
||||||
buffer.WriteRune(' ')
|
buffer.WriteRune(' ')
|
||||||
|
|
@ -149,35 +108,6 @@ func split4Values(text string) []string {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) backgroundCSS(session Session) string {
|
|
||||||
if value, ok := style.properties[Background]; ok {
|
|
||||||
if backgrounds, ok := value.([]BackgroundElement); ok {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
for _, background := range backgrounds {
|
|
||||||
if value := background.cssStyle(session); value != "" {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
backgroundColor, _ := colorProperty(style, BackgroundColor, session)
|
|
||||||
if backgroundColor != 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
buffer.WriteString(backgroundColor.cssString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
|
|
||||||
if visibility, ok := enumProperty(style, Visibility, session, Visible); ok {
|
if visibility, ok := enumProperty(style, Visibility, session, Visible); ok {
|
||||||
|
|
@ -190,15 +120,15 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if margin, ok := boundsProperty(style, Margin, session); ok {
|
if margin, ok := getBounds(style, Margin, session); ok {
|
||||||
margin.cssValue(Margin, builder, session)
|
margin.cssValue(Margin, builder, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
if padding, ok := boundsProperty(style, Padding, session); ok {
|
if padding, ok := getBounds(style, Padding, session); ok {
|
||||||
padding.cssValue(Padding, builder, session)
|
padding.cssValue(Padding, builder, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
if border := getBorder(style, Border); border != nil {
|
if border := getBorderProperty(style, Border); border != nil {
|
||||||
border.cssStyle(builder, session)
|
border.cssStyle(builder, session)
|
||||||
border.cssWidth(builder, session)
|
border.cssWidth(builder, session)
|
||||||
border.cssColor(builder, session)
|
border.cssColor(builder, session)
|
||||||
|
|
@ -207,27 +137,27 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
radius := getRadius(style, session)
|
radius := getRadius(style, session)
|
||||||
radius.cssValue(builder, session)
|
radius.cssValue(builder, session)
|
||||||
|
|
||||||
if outline := getOutline(style); outline != nil {
|
if outline := getOutlineProperty(style); outline != nil {
|
||||||
outline.ViewOutline(session).cssValue(builder, session)
|
outline.ViewOutline(session).cssValue(builder, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range []string{ZIndex, Order} {
|
for _, tag := range []PropertyName{ZIndex, Order} {
|
||||||
if value, ok := intProperty(style, tag, session, 0); ok {
|
if value, ok := intProperty(style, tag, session, 0); ok {
|
||||||
builder.add(tag, strconv.Itoa(value))
|
builder.add(string(tag), strconv.Itoa(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
|
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
|
||||||
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
|
builder.add(string(Opacity), strconv.FormatFloat(opacity, 'f', 3, 32))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range []string{ColumnCount, TabSize} {
|
for _, tag := range []PropertyName{ColumnCount, TabSize} {
|
||||||
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
|
if value, ok := intProperty(style, tag, session, 0); ok && value > 0 {
|
||||||
builder.add(tag, strconv.Itoa(value))
|
builder.add(string(tag), strconv.Itoa(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range []string{
|
for _, tag := range []PropertyName{
|
||||||
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
|
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
|
||||||
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
|
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
|
||||||
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
|
ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth, OutlineOffset} {
|
||||||
|
|
@ -235,18 +165,22 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
|
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
|
||||||
cssTag, ok := sizeProperties[tag]
|
cssTag, ok := sizeProperties[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
cssTag = tag
|
cssTag = string(tag)
|
||||||
}
|
}
|
||||||
builder.add(cssTag, size.cssString("", session))
|
builder.add(cssTag, size.cssString("", session))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
colorProperties := []struct{ property, cssTag string }{
|
type propertyCss struct {
|
||||||
//{BackgroundColor, BackgroundColor},
|
property PropertyName
|
||||||
|
cssTag string
|
||||||
|
}
|
||||||
|
colorProperties := []propertyCss{
|
||||||
|
//{BackgroundColor, string(BackgroundColor)},
|
||||||
{TextColor, "color"},
|
{TextColor, "color"},
|
||||||
{TextLineColor, "text-decoration-color"},
|
{TextLineColor, "text-decoration-color"},
|
||||||
{CaretColor, CaretColor},
|
{CaretColor, string(CaretColor)},
|
||||||
{AccentColor, AccentColor},
|
{AccentColor, string(AccentColor)},
|
||||||
}
|
}
|
||||||
for _, p := range colorProperties {
|
for _, p := range colorProperties {
|
||||||
if color, ok := colorProperty(style, p.property, session); ok && color != 0 {
|
if color, ok := colorProperty(style, p.property, session); ok && color != 0 {
|
||||||
|
|
@ -254,11 +188,15 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, ok := enumProperty(style, BackgroundClip, session, 0); ok {
|
for _, tag := range []PropertyName{BackgroundClip, BackgroundOrigin, MaskClip, MaskOrigin} {
|
||||||
builder.add(BackgroundClip, enumProperties[BackgroundClip].values[value])
|
if value, ok := enumProperty(style, tag, session, 0); ok {
|
||||||
|
if data, ok := enumProperties[tag]; ok {
|
||||||
|
builder.add(data.cssTag, data.values[value])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if background := style.backgroundCSS(session); background != "" {
|
if background := backgroundCSS(style, session); background != "" {
|
||||||
builder.add("background", background)
|
builder.add("background", background)
|
||||||
} else {
|
} else {
|
||||||
backgroundColor, _ := colorProperty(style, BackgroundColor, session)
|
backgroundColor, _ := colorProperty(style, BackgroundColor, session)
|
||||||
|
|
@ -267,12 +205,16 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mask := maskCSS(style, session); mask != "" {
|
||||||
|
builder.add("mask", mask)
|
||||||
|
}
|
||||||
|
|
||||||
if font, ok := stringProperty(style, FontName, session); ok && font != "" {
|
if font, ok := stringProperty(style, FontName, session); ok && font != "" {
|
||||||
builder.add(`font-family`, font)
|
builder.add(`font-family`, font)
|
||||||
}
|
}
|
||||||
|
|
||||||
writingMode := 0
|
writingMode := 0
|
||||||
for _, tag := range []string{
|
for _, tag := range []PropertyName{
|
||||||
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
|
Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
|
||||||
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
|
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor,
|
||||||
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
|
WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize, MixBlendMode, BackgroundBlendMode} {
|
||||||
|
|
@ -293,7 +235,11 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, prop := range []struct{ tag, cssTag, off, on string }{
|
type boolPropertyCss struct {
|
||||||
|
tag PropertyName
|
||||||
|
cssTag, off, on string
|
||||||
|
}
|
||||||
|
for _, prop := range []boolPropertyCss{
|
||||||
{tag: Italic, cssTag: "font-style", off: "normal", on: "italic"},
|
{tag: Italic, cssTag: "font-style", off: "normal", on: "italic"},
|
||||||
{tag: SmallCaps, cssTag: "font-variant", off: "normal", on: "small-caps"},
|
{tag: SmallCaps, cssTag: "font-variant", off: "normal", on: "small-caps"},
|
||||||
} {
|
} {
|
||||||
|
|
@ -306,7 +252,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if text := style.cssTextDecoration(session); text != "" {
|
if text := textDecorationCSS(style, session); text != "" {
|
||||||
builder.add("text-decoration", text)
|
builder.add("text-decoration", text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,46 +373,46 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
||||||
if r, ok := rangeProperty(style, Column, session); ok {
|
if r, ok := rangeProperty(style, Column, session); ok {
|
||||||
builder.add("grid-column", fmt.Sprintf("%d / %d", r.First+1, r.Last+2))
|
builder.add("grid-column", fmt.Sprintf("%d / %d", r.First+1, r.Last+2))
|
||||||
}
|
}
|
||||||
if text := style.gridCellSizesCSS(CellWidth, session); text != "" {
|
if text := gridCellSizesCSS(style, CellWidth, session); text != "" {
|
||||||
builder.add(`grid-template-columns`, text)
|
builder.add(`grid-template-columns`, text)
|
||||||
}
|
}
|
||||||
if text := style.gridCellSizesCSS(CellHeight, session); text != "" {
|
if text := gridCellSizesCSS(style, CellHeight, session); text != "" {
|
||||||
builder.add(`grid-template-rows`, text)
|
builder.add(`grid-template-rows`, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
style.writeViewTransformCSS(builder, session)
|
style.writeViewTransformCSS(builder, session)
|
||||||
|
|
||||||
if clip := getClipShape(style, Clip, session); clip != nil && clip.valid(session) {
|
if clip := getClipShapeProperty(style, Clip, session); clip != nil && clip.valid(session) {
|
||||||
builder.add(`clip-path`, clip.cssStyle(session))
|
builder.add(`clip-path`, clip.cssStyle(session))
|
||||||
}
|
}
|
||||||
|
|
||||||
if clip := getClipShape(style, ShapeOutside, session); clip != nil && clip.valid(session) {
|
if clip := getClipShapeProperty(style, ShapeOutside, session); clip != nil && clip.valid(session) {
|
||||||
builder.add(`shape-outside`, clip.cssStyle(session))
|
builder.add(`shape-outside`, clip.cssStyle(session))
|
||||||
}
|
}
|
||||||
|
|
||||||
if value := style.getRaw(Filter); value != nil {
|
if value := style.getRaw(Filter); value != nil {
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
if text := filter.cssStyle(session); text != "" {
|
if text := filter.cssStyle(session); text != "" {
|
||||||
builder.add(Filter, text)
|
builder.add(string(Filter), text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if value := style.getRaw(BackdropFilter); value != nil {
|
if value := style.getRaw(BackdropFilter); value != nil {
|
||||||
if filter, ok := value.(ViewFilter); ok {
|
if filter, ok := value.(FilterProperty); ok {
|
||||||
if text := filter.cssStyle(session); text != "" {
|
if text := filter.cssStyle(session); text != "" {
|
||||||
builder.add(`-webkit-backdrop-filter`, text)
|
builder.add(`-webkit-backdrop-filter`, text)
|
||||||
builder.add(BackdropFilter, text)
|
builder.add(string(BackdropFilter), text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if transition := style.transitionCSS(session); transition != "" {
|
if transition := transitionCSS(style, session); transition != "" {
|
||||||
builder.add(`transition`, transition)
|
builder.add(`transition`, transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
if animation := style.animationCSS(session); animation != "" {
|
if animation := animationCSS(style, session); animation != "" {
|
||||||
builder.add(AnimationTag, animation)
|
builder.add(string(Animation), animation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pause, ok := boolProperty(style, AnimationPaused, session); ok {
|
if pause, ok := boolProperty(style, AnimationPaused, session); ok {
|
||||||
|
|
@ -515,20 +461,58 @@ func valueToOrientation(value any, session Session) (int, bool) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) Get(tag string) any {
|
func normalizeViewStyleTag(tag PropertyName) PropertyName {
|
||||||
return style.get(strings.ToLower(tag))
|
tag = defaultNormalize(tag)
|
||||||
|
switch tag {
|
||||||
|
case "top-margin":
|
||||||
|
return MarginTop
|
||||||
|
|
||||||
|
case "right-margin":
|
||||||
|
return MarginRight
|
||||||
|
|
||||||
|
case "bottom-margin":
|
||||||
|
return MarginBottom
|
||||||
|
|
||||||
|
case "left-margin":
|
||||||
|
return MarginLeft
|
||||||
|
|
||||||
|
case "top-padding":
|
||||||
|
return PaddingTop
|
||||||
|
|
||||||
|
case "right-padding":
|
||||||
|
return PaddingRight
|
||||||
|
|
||||||
|
case "bottom-padding":
|
||||||
|
return PaddingBottom
|
||||||
|
|
||||||
|
case "left-padding":
|
||||||
|
return PaddingLeft
|
||||||
|
|
||||||
|
case "origin-x":
|
||||||
|
return TransformOriginX
|
||||||
|
|
||||||
|
case "origin-y":
|
||||||
|
return TransformOriginY
|
||||||
|
|
||||||
|
case "origin-z":
|
||||||
|
return TransformOriginZ
|
||||||
|
|
||||||
|
}
|
||||||
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (style *viewStyle) get(tag string) any {
|
func (style *viewStyle) Get(tag PropertyName) any {
|
||||||
|
return viewStyleGet(style, normalizeViewStyleTag(tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewStyleGet(style Properties, tag PropertyName) any {
|
||||||
switch tag {
|
switch tag {
|
||||||
case Border, CellBorder:
|
|
||||||
return getBorder(&style.propertyList, tag)
|
|
||||||
|
|
||||||
case BorderLeft, BorderRight, BorderTop, BorderBottom,
|
case BorderLeft, BorderRight, BorderTop, BorderBottom,
|
||||||
BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle,
|
BorderStyle, BorderLeftStyle, BorderRightStyle, BorderTopStyle, BorderBottomStyle,
|
||||||
BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor,
|
BorderColor, BorderLeftColor, BorderRightColor, BorderTopColor, BorderBottomColor,
|
||||||
BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth:
|
BorderWidth, BorderLeftWidth, BorderRightWidth, BorderTopWidth, BorderBottomWidth:
|
||||||
if border := getBorder(style, Border); border != nil {
|
if border := getBorderProperty(style, Border); border != nil {
|
||||||
return border.Get(tag)
|
return border.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -537,7 +521,7 @@ func (style *viewStyle) get(tag string) any {
|
||||||
CellBorderStyle, CellBorderLeftStyle, CellBorderRightStyle, CellBorderTopStyle, CellBorderBottomStyle,
|
CellBorderStyle, CellBorderLeftStyle, CellBorderRightStyle, CellBorderTopStyle, CellBorderBottomStyle,
|
||||||
CellBorderColor, CellBorderLeftColor, CellBorderRightColor, CellBorderTopColor, CellBorderBottomColor,
|
CellBorderColor, CellBorderLeftColor, CellBorderRightColor, CellBorderTopColor, CellBorderBottomColor,
|
||||||
CellBorderWidth, CellBorderLeftWidth, CellBorderRightWidth, CellBorderTopWidth, CellBorderBottomWidth:
|
CellBorderWidth, CellBorderLeftWidth, CellBorderRightWidth, CellBorderTopWidth, CellBorderBottomWidth:
|
||||||
if border := getBorder(style, CellBorder); border != nil {
|
if border := getBorderProperty(style, CellBorder); border != nil {
|
||||||
return border.Get(tag)
|
return border.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -548,46 +532,22 @@ func (style *viewStyle) get(tag string) any {
|
||||||
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
||||||
return getRadiusElement(style, tag)
|
return getRadiusElement(style, tag)
|
||||||
|
|
||||||
case ColumnSeparator:
|
|
||||||
if val, ok := style.properties[ColumnSeparator]; ok {
|
|
||||||
return val.(ColumnSeparatorProperty)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
|
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
|
||||||
if val, ok := style.properties[ColumnSeparator]; ok {
|
if val := style.getRaw(ColumnSeparator); val != nil {
|
||||||
separator := val.(ColumnSeparatorProperty)
|
separator := val.(ColumnSeparatorProperty)
|
||||||
return separator.Get(tag)
|
return separator.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case Transition:
|
|
||||||
if len(style.transitions) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
result := map[string]Animation{}
|
|
||||||
for tag, animation := range style.transitions {
|
|
||||||
result[tag] = animation
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
|
|
||||||
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
||||||
TranslateX, TranslateY, TranslateZ:
|
TranslateX, TranslateY, TranslateZ:
|
||||||
if transform := style.transformProperty(); transform != nil {
|
if transform := getTransformProperty(style, Transform); transform != nil {
|
||||||
return transform.Get(tag)
|
return transform.Get(tag)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return style.propertyList.getRaw(tag)
|
return style.getRaw(tag)
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) AllTags() []string {
|
|
||||||
result := style.propertyList.AllTags()
|
|
||||||
if len(style.transitions) > 0 {
|
|
||||||
result = append(result, Transition)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func supportedPropertyValue(value any) bool {
|
func supportedPropertyValue(value any) bool {
|
||||||
|
|
@ -600,20 +560,20 @@ func supportedPropertyValue(value any) bool {
|
||||||
case int:
|
case int:
|
||||||
case stringWriter:
|
case stringWriter:
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
case []ViewShadow:
|
case []ShadowProperty:
|
||||||
case []View:
|
case []View:
|
||||||
case []any:
|
case []any:
|
||||||
case []BackgroundElement:
|
case []BackgroundElement:
|
||||||
case []BackgroundGradientPoint:
|
case []BackgroundGradientPoint:
|
||||||
case []BackgroundGradientAngle:
|
case []BackgroundGradientAngle:
|
||||||
case map[string]Animation:
|
case map[PropertyName]AnimationProperty:
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePropertyValue(buffer *strings.Builder, tag string, value any, indent string) {
|
func writePropertyValue(buffer *strings.Builder, tag PropertyName, value any, indent string) {
|
||||||
|
|
||||||
writeString := func(text string) {
|
writeString := func(text string) {
|
||||||
simple := (tag != Text && tag != Title && tag != Summary)
|
simple := (tag != Text && tag != Title && tag != Summary)
|
||||||
|
|
@ -711,7 +671,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
writeString(value.String())
|
writeString(value.String())
|
||||||
|
|
||||||
case []ViewShadow:
|
case []ShadowProperty:
|
||||||
switch len(value) {
|
switch len(value) {
|
||||||
case 0:
|
case 0:
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|
@ -815,7 +775,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
}
|
}
|
||||||
buffer.WriteRune('"')
|
buffer.WriteRune('"')
|
||||||
|
|
||||||
case map[string]Animation:
|
case map[PropertyName]AnimationProperty:
|
||||||
switch count := len(value); count {
|
switch count := len(value); count {
|
||||||
case 0:
|
case 0:
|
||||||
buffer.WriteString("[]")
|
buffer.WriteString("[]")
|
||||||
|
|
@ -827,11 +787,13 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
tags := make([]string, 0, len(value))
|
tags := make([]PropertyName, 0, len(value))
|
||||||
for tag := range value {
|
for tag := range value {
|
||||||
tags = append(tags, tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
sort.Strings(tags)
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
|
return tags[i] < tags[j]
|
||||||
|
})
|
||||||
buffer.WriteString("[\n")
|
buffer.WriteString("[\n")
|
||||||
indent2 := indent + "\t"
|
indent2 := indent + "\t"
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
|
|
@ -847,12 +809,12 @@ func writePropertyValue(buffer *strings.Builder, tag string, value any, indent s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent string, excludeTags []string) {
|
func writeViewStyle(name string, view Properties, buffer *strings.Builder, indent string, excludeTags []PropertyName) {
|
||||||
buffer.WriteString(name)
|
buffer.WriteString(name)
|
||||||
buffer.WriteString(" {\n")
|
buffer.WriteString(" {\n")
|
||||||
indent += "\t"
|
indent += "\t"
|
||||||
|
|
||||||
writeProperty := func(tag string, value any) {
|
writeProperty := func(tag PropertyName, value any) {
|
||||||
for _, exclude := range excludeTags {
|
for _, exclude := range excludeTags {
|
||||||
if exclude == tag {
|
if exclude == tag {
|
||||||
return
|
return
|
||||||
|
|
@ -861,7 +823,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
|
||||||
|
|
||||||
if supportedPropertyValue(value) {
|
if supportedPropertyValue(value) {
|
||||||
buffer.WriteString(indent)
|
buffer.WriteString(indent)
|
||||||
buffer.WriteString(tag)
|
buffer.WriteString(string(tag))
|
||||||
buffer.WriteString(" = ")
|
buffer.WriteString(" = ")
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
writePropertyValue(buffer, tag, value, indent)
|
||||||
buffer.WriteString(",\n")
|
buffer.WriteString(",\n")
|
||||||
|
|
@ -869,7 +831,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := view.AllTags()
|
tags := view.AllTags()
|
||||||
removeTag := func(tag string) {
|
removeTag := func(tag PropertyName) {
|
||||||
for i, t := range tags {
|
for i, t := range tags {
|
||||||
if t == tag {
|
if t == tag {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
|
@ -884,10 +846,11 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagOrder := []string{
|
tagOrder := []PropertyName{
|
||||||
ID, Row, Column, Top, Right, Bottom, Left, Semantics, Cursor, Visibility,
|
ID, Row, Column, Top, Right, Bottom, Left, Semantics, Cursor, Visibility,
|
||||||
Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight,
|
Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight,
|
||||||
Margin, Padding, BackgroundClip, BackgroundColor, Background, Border, Radius, Outline, Shadow,
|
Margin, Padding, BackgroundColor, Background, BackgroundClip, BackgroundOrigin,
|
||||||
|
Mask, MaskClip, MaskOrigin, Border, Radius, Outline, Shadow,
|
||||||
Orientation, ListWrap, VerticalAlign, HorizontalAlign, CellWidth, CellHeight,
|
Orientation, ListWrap, VerticalAlign, HorizontalAlign, CellWidth, CellHeight,
|
||||||
CellVerticalAlign, CellHorizontalAlign, ListRowGap, ListColumnGap, GridRowGap, GridColumnGap,
|
CellVerticalAlign, CellHorizontalAlign, ListRowGap, ListColumnGap, GridRowGap, GridColumnGap,
|
||||||
ColumnCount, ColumnWidth, ColumnSeparator, ColumnGap, AvoidBreak,
|
ColumnCount, ColumnWidth, ColumnSeparator, ColumnGap, AvoidBreak,
|
||||||
|
|
@ -905,9 +868,10 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finalTags := []string{
|
finalTags := []PropertyName{
|
||||||
Perspective, PerspectiveOriginX, PerspectiveOriginY, BackfaceVisible, OriginX, OriginY, OriginZ,
|
PerspectiveOriginX, PerspectiveOriginY, BackfaceVisible,
|
||||||
TransformTag, Clip, Filter, BackdropFilter, Summary, Content, Transition}
|
TransformOriginX, TransformOriginY, TransformOriginZ,
|
||||||
|
Transform, Clip, Filter, BackdropFilter, Summary, Content, Transition}
|
||||||
for _, tag := range finalTags {
|
for _, tag := range finalTags {
|
||||||
removeTag(tag)
|
removeTag(tag)
|
||||||
}
|
}
|
||||||
|
|
@ -929,14 +893,6 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent
|
||||||
buffer.WriteString("}")
|
buffer.WriteString("}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getViewString(view View, excludeTags []string) string {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
writeViewStyle(view.Tag(), view, buffer, "", excludeTags)
|
|
||||||
return buffer.String()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func runStringWriter(writer stringWriter) string {
|
func runStringWriter(writer stringWriter) string {
|
||||||
buffer := allocStringBuilder()
|
buffer := allocStringBuilder()
|
||||||
defer freeStringBuilder(buffer)
|
defer freeStringBuilder(buffer)
|
||||||
|
|
|
||||||
521
viewStyleSet.go
521
viewStyleSet.go
|
|
@ -4,93 +4,19 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (style *viewStyle) setRange(tag string, value any) bool {
|
func setTransitionProperty(properties Properties, value any) bool {
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if strings.Contains(value, "@") {
|
|
||||||
style.properties[tag] = value
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var r Range
|
|
||||||
if !r.setValue(value) {
|
|
||||||
invalidPropertyValue(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
style.properties[tag] = r
|
|
||||||
|
|
||||||
case int:
|
transitions := map[PropertyName]AnimationProperty{}
|
||||||
style.properties[tag] = Range{First: value, Last: value}
|
|
||||||
|
|
||||||
case Range:
|
|
||||||
style.properties[tag] = value
|
|
||||||
|
|
||||||
default:
|
|
||||||
notCompatibleType(tag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setBackground(value any) bool {
|
|
||||||
background := []BackgroundElement{}
|
|
||||||
|
|
||||||
switch value := value.(type) {
|
|
||||||
case BackgroundElement:
|
|
||||||
background = []BackgroundElement{value}
|
|
||||||
|
|
||||||
case []BackgroundElement:
|
|
||||||
background = value
|
|
||||||
|
|
||||||
case []DataValue:
|
|
||||||
for _, el := range value {
|
|
||||||
if el.IsObject() {
|
|
||||||
if element := createBackground(el.Object()); element != nil {
|
|
||||||
background = append(background, element)
|
|
||||||
}
|
|
||||||
} else if obj := ParseDataText(el.Value()); obj != nil {
|
|
||||||
if element := createBackground(obj); element != nil {
|
|
||||||
background = append(background, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataObject:
|
|
||||||
if element := createBackground(value); element != nil {
|
|
||||||
background = []BackgroundElement{element}
|
|
||||||
}
|
|
||||||
|
|
||||||
case []DataObject:
|
|
||||||
for _, obj := range value {
|
|
||||||
if element := createBackground(obj); element != nil {
|
|
||||||
background = append(background, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if obj := ParseDataText(value); obj != nil {
|
|
||||||
if element := createBackground(obj); element != nil {
|
|
||||||
background = []BackgroundElement{element}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(background) > 0 {
|
|
||||||
style.properties[Background] = background
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setTransition(tag string, value any) bool {
|
|
||||||
setObject := func(obj DataObject) bool {
|
setObject := func(obj DataObject) bool {
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
tag := strings.ToLower(tag)
|
tag := strings.ToLower(obj.Tag())
|
||||||
switch tag {
|
switch tag {
|
||||||
case "", "_":
|
case "", "_":
|
||||||
ErrorLog("Invalid transition property name")
|
ErrorLog("Invalid transition property name")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
style.transitions[tag] = parseAnimation(obj)
|
transitions[PropertyName(tag)] = parseAnimation(obj)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,111 +25,140 @@ func (style *viewStyle) setTransition(tag string, value any) bool {
|
||||||
|
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case Params:
|
case Params:
|
||||||
result := true
|
|
||||||
for tag, val := range value {
|
for tag, val := range value {
|
||||||
tag = strings.ToLower(strings.Trim(tag, " \t"))
|
tag = defaultNormalize(tag)
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
ErrorLog("Invalid transition property name")
|
ErrorLog("Invalid transition property name")
|
||||||
result = false
|
return false
|
||||||
} else if val == nil {
|
}
|
||||||
delete(style.transitions, tag)
|
|
||||||
} else if animation, ok := val.(Animation); ok {
|
if val != nil {
|
||||||
style.transitions[tag] = animation
|
if animation, ok := val.(AnimationProperty); ok {
|
||||||
} else {
|
transitions[PropertyName(tag)] = animation
|
||||||
notCompatibleType(Transition, val)
|
} else {
|
||||||
result = false
|
notCompatibleType(Transition, val)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
if len(transitions) == 0 {
|
||||||
|
transitions = nil
|
||||||
|
}
|
||||||
|
properties.setRaw(Transition, transitions)
|
||||||
|
return true
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
return setObject(value)
|
if setObject(value) {
|
||||||
|
properties.setRaw(Transition, transitions)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
case DataNode:
|
case DataNode:
|
||||||
switch value.Type() {
|
switch value.Type() {
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
return setObject(value.Object())
|
if setObject(value.Object()) {
|
||||||
|
properties.setRaw(Transition, transitions)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
case ArrayNode:
|
case ArrayNode:
|
||||||
result := true
|
|
||||||
for i := 0; i < value.ArraySize(); i++ {
|
for i := 0; i < value.ArraySize(); i++ {
|
||||||
if obj := value.ArrayElement(i).Object(); obj != nil {
|
if obj := value.ArrayElement(i).Object(); obj != nil {
|
||||||
result = setObject(obj) && result
|
if !setObject(obj) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notCompatibleType(tag, value.ArrayElement(i))
|
notCompatibleType(Transition, value.ArrayElement(i))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(transitions) == 0 {
|
||||||
|
transitions = nil
|
||||||
|
}
|
||||||
|
properties.setRaw(Transition, transitions)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notCompatibleType(Transition, value)
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (style *viewStyle) setTransition(tag PropertyName, value any) bool {
|
||||||
|
setObject := func(obj DataObject) bool {
|
||||||
|
if obj != nil {
|
||||||
|
tag := defaultNormalize(tag)
|
||||||
|
switch tag {
|
||||||
|
case "", "_":
|
||||||
|
ErrorLog("Invalid transition property name")
|
||||||
|
|
||||||
|
default:
|
||||||
|
style.transitions[tag] = parseAnimation(obj)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case Params:
|
||||||
|
result := true
|
||||||
|
for tag, val := range value {
|
||||||
|
tag = defaultNormalize(tag)
|
||||||
|
if tag == "" {
|
||||||
|
ErrorLog("Invalid transition property name")
|
||||||
|
result = false
|
||||||
|
} else if val == nil {
|
||||||
|
delete(style.transitions, tag)
|
||||||
|
} else if animation, ok := val.(Animation); ok {
|
||||||
|
style.transitions[tag] = animation
|
||||||
|
} else {
|
||||||
|
notCompatibleType(Transition, val)
|
||||||
result = false
|
result = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
case DataObject:
|
||||||
return false
|
return setObject(value)
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setAnimation(tag string, value any) bool {
|
case DataNode:
|
||||||
|
switch value.Type() {
|
||||||
|
case ObjectNode:
|
||||||
|
return setObject(value.Object())
|
||||||
|
|
||||||
set := func(animations []Animation) {
|
case ArrayNode:
|
||||||
style.properties[tag] = animations
|
result := true
|
||||||
for _, animation := range animations {
|
for i := 0; i < value.ArraySize(); i++ {
|
||||||
animation.used()
|
if obj := value.ArrayElement(i).Object(); obj != nil {
|
||||||
}
|
result = setObject(obj) && result
|
||||||
}
|
} else {
|
||||||
|
notCompatibleType(tag, value.ArrayElement(i))
|
||||||
switch value := value.(type) {
|
result = false
|
||||||
case Animation:
|
}
|
||||||
set([]Animation{value})
|
|
||||||
return true
|
|
||||||
|
|
||||||
case []Animation:
|
|
||||||
set(value)
|
|
||||||
return true
|
|
||||||
|
|
||||||
case DataObject:
|
|
||||||
if animation := parseAnimation(value); animation.hasAnimatedProperty() {
|
|
||||||
set([]Animation{animation})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
case DataNode:
|
|
||||||
animations := []Animation{}
|
|
||||||
result := true
|
|
||||||
for i := 0; i < value.ArraySize(); i++ {
|
|
||||||
if obj := value.ArrayElement(i).Object(); obj != nil {
|
|
||||||
if anim := parseAnimation(obj); anim.hasAnimatedProperty() {
|
|
||||||
animations = append(animations, anim)
|
|
||||||
} else {
|
|
||||||
result = false
|
|
||||||
}
|
}
|
||||||
} else {
|
return result
|
||||||
notCompatibleType(tag, value.ArrayElement(i))
|
|
||||||
result = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if result && len(animations) > 0 {
|
|
||||||
set(animations)
|
notCompatibleType(tag, value)
|
||||||
}
|
return false
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
notCompatibleType(tag, value)
|
func viewStyleRemove(properties Properties, tag PropertyName) []PropertyName {
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) Remove(tag string) {
|
|
||||||
style.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case BorderStyle, BorderColor, BorderWidth,
|
case BorderStyle, BorderColor, BorderWidth,
|
||||||
BorderLeft, BorderLeftStyle, BorderLeftColor, BorderLeftWidth,
|
BorderLeft, BorderLeftStyle, BorderLeftColor, BorderLeftWidth,
|
||||||
BorderRight, BorderRightStyle, BorderRightColor, BorderRightWidth,
|
BorderRight, BorderRightStyle, BorderRightColor, BorderRightWidth,
|
||||||
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
|
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
|
||||||
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
|
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
|
||||||
if border := getBorder(style, Border); border != nil {
|
if border := getBorderProperty(properties, Border); border != nil && border.deleteTag(tag) {
|
||||||
border.delete(tag)
|
return []PropertyName{Border}
|
||||||
}
|
}
|
||||||
|
|
||||||
case CellBorderStyle, CellBorderColor, CellBorderWidth,
|
case CellBorderStyle, CellBorderColor, CellBorderWidth,
|
||||||
|
|
@ -211,58 +166,59 @@ func (style *viewStyle) remove(tag string) {
|
||||||
CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth,
|
CellBorderRight, CellBorderRightStyle, CellBorderRightColor, CellBorderRightWidth,
|
||||||
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
|
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
|
||||||
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
|
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
|
||||||
if border := getBorder(style, CellBorder); border != nil {
|
if border := getBorderProperty(properties, CellBorder); border != nil && border.deleteTag(tag) {
|
||||||
border.delete(tag)
|
return []PropertyName{CellBorder}
|
||||||
}
|
}
|
||||||
|
|
||||||
case MarginTop, MarginRight, MarginBottom, MarginLeft,
|
case MarginTop, MarginRight, MarginBottom, MarginLeft:
|
||||||
"top-margin", "right-margin", "bottom-margin", "left-margin":
|
return removeBoundsPropertySide(properties, Margin, tag)
|
||||||
style.removeBoundsSide(Margin, tag)
|
|
||||||
|
|
||||||
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
|
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft:
|
||||||
"top-padding", "right-padding", "bottom-padding", "left-padding":
|
return removeBoundsPropertySide(properties, Padding, tag)
|
||||||
style.removeBoundsSide(Padding, tag)
|
|
||||||
|
|
||||||
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
||||||
style.removeBoundsSide(CellPadding, tag)
|
return removeBoundsPropertySide(properties, CellPadding, tag)
|
||||||
|
|
||||||
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
||||||
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
||||||
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
||||||
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
||||||
style.removeRadiusElement(tag)
|
if removeRadiusPropertyElement(properties, tag) {
|
||||||
|
return []PropertyName{Radius, tag}
|
||||||
|
}
|
||||||
|
|
||||||
case OutlineStyle, OutlineWidth, OutlineColor:
|
case OutlineStyle, OutlineWidth, OutlineColor:
|
||||||
if outline := getOutline(style); outline != nil {
|
if outline := getOutlineProperty(properties); outline != nil {
|
||||||
outline.Remove(tag)
|
outline.Remove(tag)
|
||||||
|
if outline.empty() {
|
||||||
|
properties.setRaw(Outline, nil)
|
||||||
|
}
|
||||||
|
return []PropertyName{Outline, tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
style.propertyList.remove(tag)
|
return propertiesRemove(properties, tag)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) Set(tag string, value any) bool {
|
|
||||||
return style.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
style.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return []PropertyName{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewStyleSet(style Properties, tag PropertyName, value any) []PropertyName {
|
||||||
switch tag {
|
switch tag {
|
||||||
case Shadow, TextShadow:
|
case Shadow, TextShadow:
|
||||||
return style.setShadow(tag, value)
|
if setShadowProperty(style, tag, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
|
||||||
case Background:
|
case Background, Mask:
|
||||||
return style.setBackground(value)
|
return setBackgroundProperty(style, tag, value)
|
||||||
|
|
||||||
case Border, CellBorder:
|
case Border, CellBorder:
|
||||||
if border := newBorderProperty(value); border != nil {
|
if border := newBorderProperty(value); border != nil {
|
||||||
style.properties[tag] = border
|
style.setRaw(tag, border)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case BorderStyle, BorderColor, BorderWidth,
|
case BorderStyle, BorderColor, BorderWidth,
|
||||||
|
|
@ -271,16 +227,7 @@ func (style *viewStyle) set(tag string, value any) bool {
|
||||||
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
|
BorderTop, BorderTopStyle, BorderTopColor, BorderTopWidth,
|
||||||
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
|
BorderBottom, BorderBottomStyle, BorderBottomColor, BorderBottomWidth:
|
||||||
|
|
||||||
border := getBorder(style, Border)
|
return setBorderPropertyElement(style, Border, tag, value)
|
||||||
if border == nil {
|
|
||||||
border = NewBorder(nil)
|
|
||||||
if border.Set(tag, value) {
|
|
||||||
style.properties[Border] = border
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return border.Set(tag, value)
|
|
||||||
|
|
||||||
case CellBorderStyle, CellBorderColor, CellBorderWidth,
|
case CellBorderStyle, CellBorderColor, CellBorderWidth,
|
||||||
CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth,
|
CellBorderLeft, CellBorderLeftStyle, CellBorderLeftColor, CellBorderLeftWidth,
|
||||||
|
|
@ -288,173 +235,215 @@ func (style *viewStyle) set(tag string, value any) bool {
|
||||||
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
|
CellBorderTop, CellBorderTopStyle, CellBorderTopColor, CellBorderTopWidth,
|
||||||
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
|
CellBorderBottom, CellBorderBottomStyle, CellBorderBottomColor, CellBorderBottomWidth:
|
||||||
|
|
||||||
border := getBorder(style, CellBorder)
|
return setBorderPropertyElement(style, CellBorder, tag, value)
|
||||||
if border == nil {
|
|
||||||
border = NewBorder(nil)
|
|
||||||
if border.Set(tag, value) {
|
|
||||||
style.properties[CellBorder] = border
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return border.Set(tag, value)
|
|
||||||
|
|
||||||
case Radius:
|
case Radius:
|
||||||
return style.setRadius(value)
|
return setRadiusProperty(style, value)
|
||||||
|
|
||||||
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
case RadiusX, RadiusY, RadiusTopLeft, RadiusTopLeftX, RadiusTopLeftY,
|
||||||
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
RadiusTopRight, RadiusTopRightX, RadiusTopRightY,
|
||||||
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
||||||
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
||||||
return style.setRadiusElement(tag, value)
|
if setRadiusPropertyElement(style, tag, value) {
|
||||||
|
return []PropertyName{Radius, tag}
|
||||||
|
}
|
||||||
|
|
||||||
case Margin, Padding, CellPadding:
|
case Margin, Padding, CellPadding:
|
||||||
return style.setBounds(tag, value)
|
return setBoundsProperty(style, tag, value)
|
||||||
|
|
||||||
case MarginTop, MarginRight, MarginBottom, MarginLeft,
|
case MarginTop, MarginRight, MarginBottom, MarginLeft:
|
||||||
"top-margin", "right-margin", "bottom-margin", "left-margin":
|
return setBoundsPropertySide(style, Margin, tag, value)
|
||||||
return style.setBoundsSide(Margin, tag, value)
|
|
||||||
|
|
||||||
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
|
case PaddingTop, PaddingRight, PaddingBottom, PaddingLeft:
|
||||||
"top-padding", "right-padding", "bottom-padding", "left-padding":
|
return setBoundsPropertySide(style, Padding, tag, value)
|
||||||
return style.setBoundsSide(Padding, tag, value)
|
|
||||||
|
|
||||||
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
case CellPaddingTop, CellPaddingRight, CellPaddingBottom, CellPaddingLeft:
|
||||||
return style.setBoundsSide(CellPadding, tag, value)
|
return setBoundsPropertySide(style, CellPadding, tag, value)
|
||||||
|
|
||||||
case HeadStyle, FootStyle:
|
case HeadStyle, FootStyle:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
style.properties[tag] = value
|
style.setRaw(tag, value)
|
||||||
return true
|
|
||||||
|
|
||||||
case Params:
|
case Params:
|
||||||
style.properties[tag] = value
|
style.setRaw(tag, value)
|
||||||
return true
|
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if params := value.ToParams(); len(params) > 0 {
|
if params := value.ToParams(); len(params) > 0 {
|
||||||
style.properties[tag] = params
|
style.setRaw(tag, params)
|
||||||
|
} else {
|
||||||
|
style.setRaw(tag, nil)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
default:
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
case CellStyle, ColumnStyle, RowStyle:
|
case CellStyle, ColumnStyle, RowStyle:
|
||||||
switch value := value.(type) {
|
switch value := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
style.properties[tag] = value
|
style.setRaw(tag, value)
|
||||||
return true
|
|
||||||
|
|
||||||
case Params:
|
case Params:
|
||||||
style.properties[tag] = value
|
style.setRaw(tag, value)
|
||||||
return true
|
|
||||||
|
|
||||||
case DataObject:
|
case DataObject:
|
||||||
if params := value.ToParams(); len(params) > 0 {
|
if params := value.ToParams(); len(params) > 0 {
|
||||||
style.properties[tag] = params
|
style.setRaw(tag, params)
|
||||||
|
} else {
|
||||||
|
style.setRaw(tag, nil)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
|
||||||
case DataNode:
|
case DataNode:
|
||||||
switch value.Type() {
|
switch value.Type() {
|
||||||
case TextNode:
|
case TextNode:
|
||||||
if text := value.Text(); text != "" {
|
if text := value.Text(); text != "" {
|
||||||
style.properties[tag] = text
|
style.setRaw(tag, text)
|
||||||
|
} else {
|
||||||
|
style.setRaw(tag, nil)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
|
||||||
case ObjectNode:
|
case ObjectNode:
|
||||||
if obj := value.Object(); obj != nil {
|
if obj := value.Object(); obj != nil {
|
||||||
if params := obj.ToParams(); len(params) > 0 {
|
if params := obj.ToParams(); len(params) > 0 {
|
||||||
style.properties[tag] = params
|
style.setRaw(tag, params)
|
||||||
|
} else {
|
||||||
|
style.setRaw(tag, nil)
|
||||||
}
|
}
|
||||||
return true
|
} else {
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case ArrayNode:
|
default:
|
||||||
// TODO
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
notCompatibleType(tag, value)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return []PropertyName{tag}
|
||||||
|
|
||||||
case Outline:
|
case Outline:
|
||||||
return style.setOutline(value)
|
return setOutlineProperty(style, value)
|
||||||
|
|
||||||
case OutlineStyle, OutlineWidth, OutlineColor:
|
case OutlineStyle, OutlineWidth, OutlineColor:
|
||||||
if outline := getOutline(style); outline != nil {
|
if outline := getOutlineProperty(style); outline != nil {
|
||||||
return outline.Set(tag, value)
|
if outline.Set(tag, value) {
|
||||||
|
return []PropertyName{Outline, tag}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outline := NewOutlineProperty(nil)
|
||||||
|
if outline.Set(tag, value) {
|
||||||
|
style.setRaw(Outline, outline)
|
||||||
|
return []PropertyName{Outline, tag}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
style.properties[Outline] = NewOutlineProperty(Params{tag: value})
|
return nil
|
||||||
return true
|
|
||||||
|
|
||||||
case TransformTag:
|
case Transform, PushTransform:
|
||||||
return style.setTransform(value)
|
if setTransformProperty(style, tag, value) {
|
||||||
|
return []PropertyName{Transform}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
case Perspective, RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ,
|
||||||
TranslateX, TranslateY, TranslateZ:
|
TranslateX, TranslateY, TranslateZ:
|
||||||
return style.setTransformProperty(tag, value)
|
return setTransformPropertyElement(style, tag, Transform, value)
|
||||||
|
|
||||||
|
case PushPerspective, PushRotateX, PushRotateY, PushRotateZ, PushRotate, PushSkewX, PushSkewY,
|
||||||
|
PushScaleX, PushScaleY, PushScaleZ, PushTranslateX, PushTranslateY, PushTranslateZ:
|
||||||
|
return setTransformPropertyElement(style, tag, PushTransform, value)
|
||||||
|
|
||||||
case Orientation:
|
case Orientation:
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
switch strings.ToLower(text) {
|
switch strings.ToLower(text) {
|
||||||
case "vertical":
|
case "vertical":
|
||||||
style.properties[Orientation] = TopDownOrientation
|
style.setRaw(Orientation, TopDownOrientation)
|
||||||
return true
|
return []PropertyName{Orientation}
|
||||||
|
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
style.properties[Orientation] = StartToEndOrientation
|
style.setRaw(Orientation, StartToEndOrientation)
|
||||||
return true
|
return []PropertyName{Orientation}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case TextWeight:
|
case TextWeight:
|
||||||
if n, ok := value.(int); ok && n >= 100 && n%100 == 0 {
|
if n, ok := value.(int); ok {
|
||||||
n /= 100
|
if n >= 100 && n%100 == 0 {
|
||||||
if n > 0 && n <= 9 {
|
n /= 100
|
||||||
style.properties[TextWeight] = n
|
if n > 0 && n <= 9 {
|
||||||
return true
|
style.setRaw(TextWeight, n)
|
||||||
|
return []PropertyName{TextWeight}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case Row, Column:
|
case Row, Column:
|
||||||
return style.setRange(tag, value)
|
return setRangeProperty(style, tag, value)
|
||||||
|
|
||||||
case CellWidth, CellHeight:
|
case CellWidth, CellHeight:
|
||||||
return style.setGridCellSize(tag, value)
|
return setGridCellSize(style, tag, value)
|
||||||
|
|
||||||
case ColumnSeparator:
|
case ColumnSeparator:
|
||||||
if separator := newColumnSeparatorProperty(value); separator != nil {
|
if separator := newColumnSeparatorProperty(value); separator != nil {
|
||||||
style.properties[ColumnSeparator] = separator
|
style.setRaw(ColumnSeparator, separator)
|
||||||
return true
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
|
|
||||||
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
|
case ColumnSeparatorStyle, ColumnSeparatorWidth, ColumnSeparatorColor:
|
||||||
var separator ColumnSeparatorProperty = nil
|
if separator := getColumnSeparatorProperty(style); separator != nil {
|
||||||
if val, ok := style.properties[ColumnSeparator]; ok {
|
if separator.Set(tag, value) {
|
||||||
separator = val.(ColumnSeparatorProperty)
|
return []PropertyName{ColumnSeparator, tag}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
separator := newColumnSeparatorProperty(nil)
|
||||||
|
if separator.Set(tag, value) {
|
||||||
|
style.setRaw(ColumnSeparator, separator)
|
||||||
|
return []PropertyName{ColumnSeparator, tag}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if separator == nil {
|
return nil
|
||||||
separator = newColumnSeparatorProperty(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if separator.Set(tag, value) {
|
|
||||||
style.properties[ColumnSeparator] = separator
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
|
|
||||||
case Clip, ShapeOutside:
|
case Clip, ShapeOutside:
|
||||||
return style.setClipShape(tag, value)
|
return setClipShapePropertyProperty(style, tag, value)
|
||||||
|
|
||||||
case Filter, BackdropFilter:
|
case Filter, BackdropFilter:
|
||||||
return style.setFilter(tag, value)
|
return setFilterProperty(style, tag, value)
|
||||||
|
|
||||||
case Transition:
|
case Transition:
|
||||||
return style.setTransition(tag, value)
|
if setTransitionProperty(style, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
case AnimationTag:
|
case Animation:
|
||||||
return style.setAnimation(tag, value)
|
if setAnimationProperty(style, tag, value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return style.propertyList.set(tag, value)
|
return propertiesSet(style, tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (style *viewStyle) Set(tag PropertyName, value any) bool {
|
||||||
|
if value == nil {
|
||||||
|
style.Remove(tag)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewStyleSet(style, normalizeViewStyleTag(tag), value) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (style *viewStyle) Remove(tag PropertyName) {
|
||||||
|
viewStyleRemove(style, normalizeViewStyleTag(tag))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
775
viewTransform.go
775
viewTransform.go
|
|
@ -1,775 +0,0 @@
|
||||||
package rui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Constants for [Transform] specific properties
|
|
||||||
const (
|
|
||||||
// Perspective is the constant for "perspective" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// Distance between the z-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).
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
Perspective = "perspective"
|
|
||||||
|
|
||||||
// PerspectiveOriginX is the constant for "perspective-origin-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// x-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%.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
PerspectiveOriginX = "perspective-origin-x"
|
|
||||||
|
|
||||||
// PerspectiveOriginY is the constant for "perspective-origin-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// 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%.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
PerspectiveOriginY = "perspective-origin-y"
|
|
||||||
|
|
||||||
// BackfaceVisible is the constant for "backface-visibility" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// Controls whether the back face of a view is visible when turned towards the user. Default value is `true`.
|
|
||||||
//
|
|
||||||
// Supported types: `bool`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Values:
|
|
||||||
// `true` or `1` or "true", "yes", "on", "1" - Back face is visible when turned towards the user.
|
|
||||||
// `false` or `0` or "false", "no", "off", "0" - Back face is hidden, effectively making the view invisible when turned away from the user.
|
|
||||||
BackfaceVisible = "backface-visibility"
|
|
||||||
|
|
||||||
// OriginX is the constant for "origin-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// x-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
OriginX = "origin-x"
|
|
||||||
|
|
||||||
// OriginY is the constant for "origin-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// y-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
OriginY = "origin-y"
|
|
||||||
|
|
||||||
// OriginZ is the constant for "origin-z" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// z-coordinate of the point around which a view transformation is applied. The default value is 50%.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
OriginZ = "origin-z"
|
|
||||||
|
|
||||||
// TransformTag is the constant for "transform" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`.
|
|
||||||
// Specify translation, scale and rotation over x, y and z axes as well as a distorsion of a view along x and y axes.
|
|
||||||
//
|
|
||||||
// Supported types: `Transform`, `string`.
|
|
||||||
//
|
|
||||||
// See `Transform` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `Transform` - stored as is, no conversion performed.
|
|
||||||
// `string` - string representation of `Transform` interface. Example: "_{translate-x = 10px, scale-y = 1.1}".
|
|
||||||
TransformTag = "transform"
|
|
||||||
|
|
||||||
// TranslateX is the constant for "translate-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// x-axis translation value of a 2D/3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// x-axis translation value of a 2D/3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
TranslateX = "translate-x"
|
|
||||||
|
|
||||||
// TranslateY is the constant for "translate-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// y-axis translation value of a 2D/3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// x-axis translation value of a 2D/3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
TranslateY = "translate-y"
|
|
||||||
|
|
||||||
// TranslateZ is the constant for "translate-z" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// z-axis translation value of a 3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// z-axis translation value of a 3D translation.
|
|
||||||
//
|
|
||||||
// Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `SizeUnit`, other types converted to it during assignment.
|
|
||||||
// See `SizeUnit` description for more details.
|
|
||||||
TranslateZ = "translate-z"
|
|
||||||
|
|
||||||
// ScaleX is the constant for "scale-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// x-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// x-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
ScaleX = "scale-x"
|
|
||||||
|
|
||||||
// ScaleY is the constant for "scale-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// y-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// y-axis scaling value of a 2D/3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
ScaleY = "scale-y"
|
|
||||||
|
|
||||||
// ScaleZ is the constant for "scale-z" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// z-axis scaling value of a 3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// z-axis scaling value of a 3D scale. The original scale is 1. Values between 0 and 1 are used to decrease original
|
|
||||||
// scale, more than 1 - to increase. The default value is 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
ScaleZ = "scale-z"
|
|
||||||
|
|
||||||
// Rotate is the constant for "rotate" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// Angle of the view rotation. A positive angle denotes a clockwise rotation, a negative angle a counter-clockwise.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// Angle of the view rotation. A positive angle denotes a clockwise rotation, a negative angle a counter-clockwise.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
Rotate = "rotate"
|
|
||||||
|
|
||||||
// RotateX is the constant for "rotate-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// x-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// x-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
RotateX = "rotate-x"
|
|
||||||
|
|
||||||
// RotateY is the constant for "rotate-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// y-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// y-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
RotateY = "rotate-y"
|
|
||||||
|
|
||||||
// RotateZ is the constant for "rotate-z" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// z-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// z-coordinate of the vector denoting the axis of rotation in range 0 to 1.
|
|
||||||
//
|
|
||||||
// Supported types: `float`, `int`, `string`.
|
|
||||||
//
|
|
||||||
// Internal type is `float`, other types converted to it during assignment.
|
|
||||||
RotateZ = "rotate-z"
|
|
||||||
|
|
||||||
// SkewX is the constant for "skew-x" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// Angle to use to distort the element along the abscissa. The default value is 0.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// Angle to use to distort the element along the abscissa. The default value is 0.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
SkewX = "skew-x"
|
|
||||||
|
|
||||||
// SkewY is the constant for "skew-y" property tag.
|
|
||||||
//
|
|
||||||
// Used by `View`, `Transform`.
|
|
||||||
//
|
|
||||||
// Usage in `View`:
|
|
||||||
// Angle to use to distort the element along the ordinate. The default value is 0.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
//
|
|
||||||
// Usage in `Transform`:
|
|
||||||
// Angle to use to distort the element along the ordinate. The default value is 0.
|
|
||||||
//
|
|
||||||
// Supported types: `AngleUnit`, `string`, `float`, `int`.
|
|
||||||
//
|
|
||||||
// Internal type is `AngleUnit`, other types will be converted to it during assignment.
|
|
||||||
// See `AngleUnit` description for more details.
|
|
||||||
//
|
|
||||||
// Conversion rules:
|
|
||||||
// `AngleUnit` - stored as is, no conversion performed.
|
|
||||||
// `string` - must contain string representation of `AngleUnit`. If numeric value will be provided without any suffix then `AngleUnit` with value and `Radian` value type will be created.
|
|
||||||
// `float` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
// `int` - a new `AngleUnit` value will be created with `Radian` as a type.
|
|
||||||
SkewY = "skew-y"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Transform interface specifies view transformation parameters: the x-, y-, and z-axis translation values,
|
|
||||||
// the x-, y-, and z-axis scaling values, the angle to use to distort the element along the abscissa and ordinate,
|
|
||||||
// the angle of the view rotation.
|
|
||||||
// Valid property tags: TranslateX ("translate-x"), TranslateY ("translate-y"), TranslateZ ("translate-z"),
|
|
||||||
// ScaleX ("scale-x"), ScaleY ("scale-y"), ScaleZ ("scale-z"), Rotate ("rotate"), RotateX ("rotate-x"),
|
|
||||||
// RotateY ("rotate-y"), RotateZ ("rotate-z"), SkewX ("skew-x"), and SkewY ("skew-y")
|
|
||||||
type Transform interface {
|
|
||||||
Properties
|
|
||||||
fmt.Stringer
|
|
||||||
stringWriter
|
|
||||||
transformCSS(session Session, transform3D bool) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type transformData struct {
|
|
||||||
propertyList
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransform creates a new transform property data and return its interface
|
|
||||||
func NewTransform(params Params) Transform {
|
|
||||||
transform := new(transformData)
|
|
||||||
transform.properties = map[string]any{}
|
|
||||||
for tag, value := range params {
|
|
||||||
transform.Set(tag, value)
|
|
||||||
}
|
|
||||||
return transform
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setTransform(value any) bool {
|
|
||||||
|
|
||||||
setObject := func(obj DataObject) bool {
|
|
||||||
transform := NewTransform(nil)
|
|
||||||
ok := true
|
|
||||||
for i := 0; i < obj.PropertyCount(); i++ {
|
|
||||||
if prop := obj.Property(i); prop.Type() == TextNode {
|
|
||||||
if !transform.Set(prop.Tag(), prop.Text()) {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok && len(transform.AllTags()) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
style.properties[TransformTag] = transform
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch value := value.(type) {
|
|
||||||
case Transform:
|
|
||||||
style.properties[TransformTag] = value
|
|
||||||
return true
|
|
||||||
|
|
||||||
case DataObject:
|
|
||||||
return setObject(value)
|
|
||||||
|
|
||||||
case DataNode:
|
|
||||||
if obj := value.Object(); obj != nil {
|
|
||||||
return setObject(obj)
|
|
||||||
}
|
|
||||||
notCompatibleType(TransformTag, value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
case string:
|
|
||||||
if obj := ParseDataText(value); obj != nil {
|
|
||||||
return setObject(obj)
|
|
||||||
}
|
|
||||||
notCompatibleType(TransformTag, value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) transformProperty() Transform {
|
|
||||||
if val, ok := style.properties[TransformTag]; ok {
|
|
||||||
if transform, ok := val.(Transform); ok {
|
|
||||||
return transform
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) setTransformProperty(tag string, value any) bool {
|
|
||||||
switch tag {
|
|
||||||
case RotateX, RotateY, RotateZ, Rotate, SkewX, SkewY, ScaleX, ScaleY, ScaleZ, TranslateX, TranslateY, TranslateZ:
|
|
||||||
if transform := style.transformProperty(); transform != nil {
|
|
||||||
return transform.Set(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
transform := NewTransform(nil)
|
|
||||||
if !transform.Set(tag, value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
style.properties[TransformTag] = transform
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorLogF(`"Transform" interface does not support the "%s" property`, tag)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) String() string {
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
transform.writeString(buffer, "")
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) writeString(buffer *strings.Builder, indent string) {
|
|
||||||
buffer.WriteString("_{ ")
|
|
||||||
comma := false
|
|
||||||
for _, tag := range []string{SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
|
|
||||||
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ} {
|
|
||||||
if value, ok := transform.properties[tag]; ok {
|
|
||||||
if comma {
|
|
||||||
buffer.WriteString(", ")
|
|
||||||
}
|
|
||||||
buffer.WriteString(tag)
|
|
||||||
buffer.WriteString(" = ")
|
|
||||||
writePropertyValue(buffer, tag, value, indent)
|
|
||||||
comma = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.WriteString(" }")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) Set(tag string, value any) bool {
|
|
||||||
return transform.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
_, exist := transform.properties[tag]
|
|
||||||
if exist {
|
|
||||||
delete(transform.properties, tag)
|
|
||||||
}
|
|
||||||
return exist
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
|
|
||||||
case RotateX, RotateY, RotateZ:
|
|
||||||
return transform.setFloatProperty(tag, value, 0, 1)
|
|
||||||
|
|
||||||
case Rotate, SkewX, SkewY:
|
|
||||||
return transform.setAngleProperty(tag, value)
|
|
||||||
|
|
||||||
case ScaleX, ScaleY, ScaleZ:
|
|
||||||
return transform.setFloatProperty(tag, value, -math.MaxFloat64, math.MaxFloat64)
|
|
||||||
|
|
||||||
case TranslateX, TranslateY, TranslateZ:
|
|
||||||
return transform.setSizeProperty(tag, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTransform3D(style Properties, session Session) bool {
|
|
||||||
perspective, ok := sizeProperty(style, Perspective, session)
|
|
||||||
return ok && perspective.Type != Auto && perspective.Value != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPerspectiveOrigin(style Properties, session Session) (SizeUnit, SizeUnit) {
|
|
||||||
x, _ := sizeProperty(style, PerspectiveOriginX, session)
|
|
||||||
y, _ := sizeProperty(style, PerspectiveOriginY, session)
|
|
||||||
return x, y
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrigin(style Properties, session Session) (SizeUnit, SizeUnit, SizeUnit) {
|
|
||||||
x, _ := sizeProperty(style, OriginX, session)
|
|
||||||
y, _ := sizeProperty(style, OriginY, session)
|
|
||||||
z, _ := sizeProperty(style, OriginZ, session)
|
|
||||||
return x, y, z
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) getSkew(session Session) (AngleUnit, AngleUnit, bool) {
|
|
||||||
skewX, okX := angleProperty(transform, SkewX, session)
|
|
||||||
skewY, okY := angleProperty(transform, SkewY, session)
|
|
||||||
return skewX, skewY, okX || okY
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) getTranslate(session Session) (SizeUnit, SizeUnit, SizeUnit) {
|
|
||||||
x, _ := sizeProperty(transform, TranslateX, session)
|
|
||||||
y, _ := sizeProperty(transform, TranslateY, session)
|
|
||||||
z, _ := sizeProperty(transform, TranslateZ, session)
|
|
||||||
return x, y, z
|
|
||||||
}
|
|
||||||
|
|
||||||
func (transform *transformData) transformCSS(session Session, transform3D bool) string {
|
|
||||||
|
|
||||||
buffer := allocStringBuilder()
|
|
||||||
defer freeStringBuilder(buffer)
|
|
||||||
|
|
||||||
skewX, skewY, skewOK := transform.getSkew(session)
|
|
||||||
if skewOK {
|
|
||||||
buffer.WriteString(`skew(`)
|
|
||||||
buffer.WriteString(skewX.cssString())
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(skewY.cssString())
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y, z := transform.getTranslate(session)
|
|
||||||
|
|
||||||
scaleX, okScaleX := floatTextProperty(transform, ScaleX, session, 1)
|
|
||||||
scaleY, okScaleY := floatTextProperty(transform, ScaleY, session, 1)
|
|
||||||
|
|
||||||
if transform3D {
|
|
||||||
if x.Type != Auto || y.Type != Auto || z.Type != Auto {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`translate3d(`)
|
|
||||||
buffer.WriteString(x.cssString("0", session))
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(y.cssString("0", session))
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(z.cssString("0", session))
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleZ, okScaleZ := floatTextProperty(transform, ScaleZ, session, 1)
|
|
||||||
if okScaleX || okScaleY || okScaleZ {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`scale3d(`)
|
|
||||||
buffer.WriteString(scaleX)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(scaleY)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(scaleZ)
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
if angle, ok := angleProperty(transform, Rotate, session); ok {
|
|
||||||
rotateX, _ := floatTextProperty(transform, RotateX, session, 1)
|
|
||||||
rotateY, _ := floatTextProperty(transform, RotateY, session, 1)
|
|
||||||
rotateZ, _ := floatTextProperty(transform, RotateZ, session, 1)
|
|
||||||
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`rotate3d(`)
|
|
||||||
buffer.WriteString(rotateX)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(rotateY)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(rotateZ)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(angle.cssString())
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`translate(`)
|
|
||||||
buffer.WriteString(x.cssString("0", session))
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(y.cssString("0", session))
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
if okScaleX || okScaleY {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`scale(`)
|
|
||||||
buffer.WriteString(scaleX)
|
|
||||||
buffer.WriteRune(',')
|
|
||||||
buffer.WriteString(scaleY)
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
if angle, ok := angleProperty(transform, Rotate, session); ok {
|
|
||||||
if buffer.Len() > 0 {
|
|
||||||
buffer.WriteRune(' ')
|
|
||||||
}
|
|
||||||
buffer.WriteString(`rotate(`)
|
|
||||||
buffer.WriteString(angle.cssString())
|
|
||||||
buffer.WriteRune(')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) {
|
|
||||||
transform3D := getTransform3D(style, session)
|
|
||||||
if transform3D {
|
|
||||||
if perspective, ok := sizeProperty(style, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 {
|
|
||||||
builder.add(`perspective`, perspective.cssString("0", session))
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y := getPerspectiveOrigin(style, session)
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
builder.addValues(`perspective-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session))
|
|
||||||
}
|
|
||||||
|
|
||||||
if backfaceVisible, ok := boolProperty(style, BackfaceVisible, session); ok {
|
|
||||||
if backfaceVisible {
|
|
||||||
builder.add(`backface-visibility`, `visible`)
|
|
||||||
} else {
|
|
||||||
builder.add(`backface-visibility`, `hidden`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y, z := getOrigin(style, session)
|
|
||||||
if x.Type != Auto || y.Type != Auto || z.Type != Auto {
|
|
||||||
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session), z.cssString("0", session))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x, y, _ := getOrigin(style, session)
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if transform := style.transformProperty(); transform != nil {
|
|
||||||
builder.add(`transform`, transform.transformCSS(session, transform3D))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (view *viewData) updateTransformProperty(tag string) bool {
|
|
||||||
htmlID := view.htmlID()
|
|
||||||
session := view.session
|
|
||||||
|
|
||||||
switch tag {
|
|
||||||
case Perspective:
|
|
||||||
updateCSSStyle(htmlID, session)
|
|
||||||
|
|
||||||
case PerspectiveOriginX, PerspectiveOriginY:
|
|
||||||
if getTransform3D(view, session) {
|
|
||||||
x, y := GetPerspectiveOrigin(view)
|
|
||||||
value := ""
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
value = x.cssString("50%", session) + " " + y.cssString("50%", session)
|
|
||||||
}
|
|
||||||
session.updateCSSProperty(htmlID, "perspective-origin", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case BackfaceVisible:
|
|
||||||
if getTransform3D(view, session) {
|
|
||||||
if GetBackfaceVisible(view) {
|
|
||||||
session.updateCSSProperty(htmlID, BackfaceVisible, "visible")
|
|
||||||
} else {
|
|
||||||
session.updateCSSProperty(htmlID, BackfaceVisible, "hidden")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case OriginX, OriginY, OriginZ:
|
|
||||||
x, y, z := getOrigin(view, session)
|
|
||||||
value := ""
|
|
||||||
if getTransform3D(view, session) {
|
|
||||||
if x.Type != Auto || y.Type != Auto || z.Type != Auto {
|
|
||||||
value = x.cssString("50%", session) + " " + y.cssString("50%", session) + " " + z.cssString("50%", session)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if x.Type != Auto || y.Type != Auto {
|
|
||||||
value = x.cssString("50%", session) + " " + y.cssString("50%", session)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
session.updateCSSProperty(htmlID, "transform-origin", value)
|
|
||||||
|
|
||||||
case TransformTag, SkewX, SkewY, TranslateX, TranslateY, TranslateZ,
|
|
||||||
ScaleX, ScaleY, ScaleZ, Rotate, RotateX, RotateY, RotateZ:
|
|
||||||
if transform := view.transformProperty(); transform != nil {
|
|
||||||
transform3D := getTransform3D(view, session)
|
|
||||||
session.updateCSSProperty(htmlID, "transform", transform.transformCSS(session, transform3D))
|
|
||||||
} else {
|
|
||||||
session.updateCSSProperty(htmlID, "transform", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
363
viewUtils.go
363
viewUtils.go
|
|
@ -1,9 +1,13 @@
|
||||||
package rui
|
package rui
|
||||||
|
|
||||||
// Get returns a value of the property with name "tag" of the "rootView" subview with "viewID" id value.
|
// Get returns a value of the property with name "tag" of the "rootView" subview with "viewID" id value.
|
||||||
|
//
|
||||||
// The type of return value depends on the property.
|
// The type of return value depends on the property.
|
||||||
|
//
|
||||||
// If the subview don't exists or the property is not set then nil is returned.
|
// If the subview don't exists or the property is not set then nil is returned.
|
||||||
func Get(rootView View, viewID, tag string) any {
|
//
|
||||||
|
// If the second argument (subviewID) is "" then a listener for the first argument (view) is get
|
||||||
|
func Get(rootView View, viewID string, tag PropertyName) any {
|
||||||
var view View
|
var view View
|
||||||
if viewID != "" {
|
if viewID != "" {
|
||||||
view = ViewByID(rootView, viewID)
|
view = ViewByID(rootView, viewID)
|
||||||
|
|
@ -17,9 +21,11 @@ func Get(rootView View, viewID, tag string) any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
|
// Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result:
|
||||||
// true - success,
|
// - true - success,
|
||||||
// false - error (incompatible type or invalid format of a string value, see AppLog).
|
// - false - error (incompatible type or invalid format of a string value, see AppLog).
|
||||||
func Set(rootView View, viewID, tag string, value any) bool {
|
//
|
||||||
|
// If the second argument (subviewID) is "" then a listener for the first argument (view) is set
|
||||||
|
func Set(rootView View, viewID string, tag PropertyName, value any) bool {
|
||||||
var view View
|
var view View
|
||||||
if viewID != "" {
|
if viewID != "" {
|
||||||
view = ViewByID(rootView, viewID)
|
view = ViewByID(rootView, viewID)
|
||||||
|
|
@ -33,8 +39,9 @@ func Set(rootView View, viewID, tag string, value any) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetChangeListener sets a listener for changing a subview property value.
|
// SetChangeListener sets a listener for changing a subview property value.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a listener for the first argument (view) is set
|
//
|
||||||
func SetChangeListener(view View, viewID, tag string, listener func(View, string)) {
|
// If the second argument (subviewID) is "" then a listener for the first argument (view) is set
|
||||||
|
func SetChangeListener(view View, viewID string, tag PropertyName, listener func(View, PropertyName)) {
|
||||||
if viewID != "" {
|
if viewID != "" {
|
||||||
view = ViewByID(view, viewID)
|
view = ViewByID(view, viewID)
|
||||||
}
|
}
|
||||||
|
|
@ -44,8 +51,8 @@ func SetChangeListener(view View, viewID, tag string, listener func(View, string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetParams sets properties with name "tag" of the "rootView" subview. Result:
|
// SetParams sets properties with name "tag" of the "rootView" subview. Result:
|
||||||
// true - all properties were set successful,
|
// - true - all properties were set successful,
|
||||||
// false - error (incompatible type or invalid format of a string value, see AppLog).
|
// - false - error (incompatible type or invalid format of a string value, see AppLog).
|
||||||
func SetParams(rootView View, viewID string, params Params) bool {
|
func SetParams(rootView View, viewID string, params Params) bool {
|
||||||
if viewID != "" {
|
if viewID != "" {
|
||||||
rootView = ViewByID(rootView, viewID)
|
rootView = ViewByID(rootView, viewID)
|
||||||
|
|
@ -70,13 +77,24 @@ func SetParams(rootView View, viewID string, params Params) bool {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getSubview(view View, subviewID []string) View {
|
||||||
|
if view != nil {
|
||||||
|
for _, id := range subviewID {
|
||||||
|
if id != "" {
|
||||||
|
if view = ViewByID(view, id); view == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
// IsDisabled returns "true" if the subview is disabled
|
// IsDisabled returns "true" if the subview is disabled
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a state of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a state of the first argument (view) is returned
|
||||||
func IsDisabled(view View, subviewID ...string) bool {
|
func IsDisabled(view View, subviewID ...string) bool {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if disabled, _ := boolProperty(view, Disabled, view.Session()); disabled {
|
if disabled, _ := boolProperty(view, Disabled, view.Session()); disabled {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -106,10 +124,7 @@ func GetOpacity(view View, subviewID ...string) float64 {
|
||||||
// GetStyle returns the subview style id.
|
// GetStyle returns the subview style id.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a style of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a style of the first argument (view) is returned
|
||||||
func GetStyle(view View, subviewID ...string) string {
|
func GetStyle(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if style, ok := stringProperty(view, Style, view.Session()); ok {
|
if style, ok := stringProperty(view, Style, view.Session()); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
|
@ -120,10 +135,7 @@ func GetStyle(view View, subviewID ...string) string {
|
||||||
// GetDisabledStyle returns the disabled subview style id.
|
// GetDisabledStyle returns the disabled subview style id.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a style of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a style of the first argument (view) is returned
|
||||||
func GetDisabledStyle(view View, subviewID ...string) string {
|
func GetDisabledStyle(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if style, ok := stringProperty(view, StyleDisabled, view.Session()); ok {
|
if style, ok := stringProperty(view, StyleDisabled, view.Session()); ok {
|
||||||
return style
|
return style
|
||||||
}
|
}
|
||||||
|
|
@ -142,12 +154,8 @@ func GetVisibility(view View, subviewID ...string) int {
|
||||||
// OverflowHidden (0), OverflowVisible (1), OverflowScroll (2), OverflowAuto (3)
|
// OverflowHidden (0), OverflowVisible (1), OverflowScroll (2), OverflowAuto (3)
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned
|
||||||
func GetOverflow(view View, subviewID ...string) int {
|
func GetOverflow(view View, subviewID ...string) int {
|
||||||
defaultOverflow := OverflowHidden
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view2 := view
|
defaultOverflow := OverflowHidden
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view2 = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view2 != nil {
|
|
||||||
switch view.(type) {
|
switch view.(type) {
|
||||||
case EditView:
|
case EditView:
|
||||||
defaultOverflow = OverflowAuto
|
defaultOverflow = OverflowAuto
|
||||||
|
|
@ -155,16 +163,16 @@ func GetOverflow(view View, subviewID ...string) int {
|
||||||
case ListView:
|
case ListView:
|
||||||
defaultOverflow = OverflowAuto
|
defaultOverflow = OverflowAuto
|
||||||
}
|
}
|
||||||
|
return enumStyledProperty(view, nil, Overflow, defaultOverflow, false)
|
||||||
}
|
}
|
||||||
return enumStyledProperty(view, subviewID, Overflow, defaultOverflow, false)
|
|
||||||
|
return OverflowHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTabIndex returns the subview tab-index.
|
// GetTabIndex returns the subview tab-index.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a tab-index of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a tab-index of the first argument (view) is returned
|
||||||
func GetTabIndex(view View, subviewID ...string) int {
|
func GetTabIndex(view View, subviewID ...string) int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultValue := -1
|
defaultValue := -1
|
||||||
if view != nil {
|
if view != nil {
|
||||||
|
|
@ -196,6 +204,13 @@ func GetWidth(view View, subviewID ...string) SizeUnit {
|
||||||
return sizeStyledProperty(view, subviewID, Width, false)
|
return sizeStyledProperty(view, subviewID, Width, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetWidth[T SizeUnit | float64 | float32 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](size T, view View, subviewID ...string) bool {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
return view.Set(Width, size)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetHeight returns the subview height.
|
// GetHeight returns the subview height.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a height of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a height of the first argument (view) is returned
|
||||||
func GetHeight(view View, subviewID ...string) SizeUnit {
|
func GetHeight(view View, subviewID ...string) SizeUnit {
|
||||||
|
|
@ -264,11 +279,8 @@ func GetBottom(view View, subviewID ...string) SizeUnit {
|
||||||
// Margin returns the subview margin.
|
// Margin returns the subview margin.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a margin of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a margin of the first argument (view) is returned
|
||||||
func GetMargin(view View, subviewID ...string) Bounds {
|
func GetMargin(view View, subviewID ...string) Bounds {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
var bounds Bounds
|
var bounds Bounds
|
||||||
if view != nil {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
bounds.setFromProperties(Margin, MarginTop, MarginRight, MarginBottom, MarginLeft, view, view.Session())
|
bounds.setFromProperties(Margin, MarginTop, MarginRight, MarginBottom, MarginLeft, view, view.Session())
|
||||||
}
|
}
|
||||||
return bounds
|
return bounds
|
||||||
|
|
@ -277,11 +289,8 @@ func GetMargin(view View, subviewID ...string) Bounds {
|
||||||
// GetPadding returns the subview padding.
|
// GetPadding returns the subview padding.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a padding of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a padding of the first argument (view) is returned
|
||||||
func GetPadding(view View, subviewID ...string) Bounds {
|
func GetPadding(view View, subviewID ...string) Bounds {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
var bounds Bounds
|
var bounds Bounds
|
||||||
if view != nil {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
bounds.setFromProperties(Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, view, view.Session())
|
bounds.setFromProperties(Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, view, view.Session())
|
||||||
}
|
}
|
||||||
return bounds
|
return bounds
|
||||||
|
|
@ -290,11 +299,8 @@ func GetPadding(view View, subviewID ...string) Bounds {
|
||||||
// GetBorder returns ViewBorders of the subview.
|
// GetBorder returns ViewBorders of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a ViewBorders of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a ViewBorders of the first argument (view) is returned.
|
||||||
func GetBorder(view View, subviewID ...string) ViewBorders {
|
func GetBorder(view View, subviewID ...string) ViewBorders {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if border := getBorderProperty(view, Border); border != nil {
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if border := getBorder(view, Border); border != nil {
|
|
||||||
return border.ViewBorders(view.Session())
|
return border.ViewBorders(view.Session())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -304,9 +310,7 @@ func GetBorder(view View, subviewID ...string) ViewBorders {
|
||||||
// Radius returns the BoxRadius structure of the subview.
|
// Radius returns the BoxRadius structure of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a BoxRadius of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a BoxRadius of the first argument (view) is returned.
|
||||||
func GetRadius(view View, subviewID ...string) BoxRadius {
|
func GetRadius(view View, subviewID ...string) BoxRadius {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
if view == nil {
|
||||||
return BoxRadius{}
|
return BoxRadius{}
|
||||||
}
|
}
|
||||||
|
|
@ -316,11 +320,8 @@ func GetRadius(view View, subviewID ...string) BoxRadius {
|
||||||
// GetOutline returns ViewOutline of the subview.
|
// GetOutline returns ViewOutline of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a ViewOutline of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a ViewOutline of the first argument (view) is returned.
|
||||||
func GetOutline(view View, subviewID ...string) ViewOutline {
|
func GetOutline(view View, subviewID ...string) ViewOutline {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if outline := getOutlineProperty(view); outline != nil {
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if outline := getOutline(view); outline != nil {
|
|
||||||
return outline.ViewOutline(view.Session())
|
return outline.ViewOutline(view.Session())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -333,26 +334,22 @@ func GetOutlineOffset(view View, subviewID ...string) SizeUnit {
|
||||||
return sizeStyledProperty(view, subviewID, OutlineOffset, false)
|
return sizeStyledProperty(view, subviewID, OutlineOffset, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetViewShadows returns shadows of the subview.
|
// GetShadowPropertys returns shadows of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
|
||||||
func GetViewShadows(view View, subviewID ...string) []ViewShadow {
|
func GetShadowPropertys(view View, subviewID ...string) []ShadowProperty {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
if view == nil {
|
||||||
return []ViewShadow{}
|
return []ShadowProperty{}
|
||||||
}
|
}
|
||||||
return getShadows(view, Shadow)
|
return getShadows(view, Shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTextShadows returns text shadows of the subview.
|
// GetTextShadows returns text shadows of the subview.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then shadows of the first argument (view) is returned.
|
||||||
func GetTextShadows(view View, subviewID ...string) []ViewShadow {
|
func GetTextShadows(view View, subviewID ...string) []ShadowProperty {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
view = getSubview(view, subviewID)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
if view == nil {
|
||||||
return []ViewShadow{}
|
return []ShadowProperty{}
|
||||||
}
|
}
|
||||||
return getShadows(view, TextShadow)
|
return getShadows(view, TextShadow)
|
||||||
}
|
}
|
||||||
|
|
@ -372,23 +369,7 @@ func GetAccentColor(view View, subviewID ...string) Color {
|
||||||
// GetFontName returns the subview font.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetFontName(view View, subviewID ...string) string {
|
func GetFontName(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
return stringStyledProperty(view, nil, FontName, true)
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if font, ok := stringProperty(view, FontName, view.Session()); ok {
|
|
||||||
return font
|
|
||||||
}
|
|
||||||
if value := valueFromStyle(view, FontName); value != nil {
|
|
||||||
if font, ok := value.(string); ok {
|
|
||||||
return font
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parent := view.Parent(); parent != nil {
|
|
||||||
return GetFontName(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTextColor returns a text color of the subview.
|
// GetTextColor returns a text color of the subview.
|
||||||
|
|
@ -546,10 +527,7 @@ func GetVerticalTextOrientation(view View, subviewID ...string) int {
|
||||||
// GetRow returns the range of row numbers of a GridLayout in which the subview is placed.
|
// GetRow returns the range of row numbers of a GridLayout in which the subview is placed.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a values from the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a values from the first argument (view) is returned.
|
||||||
func GetRow(view View, subviewID ...string) Range {
|
func GetRow(view View, subviewID ...string) Range {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
if result, ok := rangeProperty(view, Row, session); ok {
|
if result, ok := rangeProperty(view, Row, session); ok {
|
||||||
return result
|
return result
|
||||||
|
|
@ -566,10 +544,7 @@ func GetRow(view View, subviewID ...string) Range {
|
||||||
// GetColumn returns the range of column numbers of a GridLayout in which the subview is placed.
|
// GetColumn returns the range of column numbers of a GridLayout in which the subview is placed.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a values from the first argument (view) is returned.
|
// If the second argument (subviewID) is not specified or it is "" then a values from the first argument (view) is returned.
|
||||||
func GetColumn(view View, subviewID ...string) Range {
|
func GetColumn(view View, subviewID ...string) Range {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
if result, ok := rangeProperty(view, Column, session); ok {
|
if result, ok := rangeProperty(view, Column, session); ok {
|
||||||
return result
|
return result
|
||||||
|
|
@ -583,116 +558,6 @@ func GetColumn(view View, subviewID ...string) Range {
|
||||||
return Range{}
|
return Range{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
|
||||||
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.
|
|
||||||
func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return AutoSize(), AutoSize()
|
|
||||||
}
|
|
||||||
return getPerspectiveOrigin(view, view.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBackfaceVisible returns a bool property that sets whether the back face of an element is
|
|
||||||
// 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.
|
|
||||||
func GetBackfaceVisible(view View, subviewID ...string) bool {
|
|
||||||
return boolStyledProperty(view, subviewID, BackfaceVisible, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrigin 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.
|
|
||||||
func GetOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return AutoSize(), AutoSize(), AutoSize()
|
|
||||||
}
|
|
||||||
return getOrigin(view, view.Session())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return AutoSize(), AutoSize(), AutoSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
session := view.Session()
|
|
||||||
x, _ := sizeProperty(view, TranslateX, session)
|
|
||||||
y, _ := sizeProperty(view, TranslateY, session)
|
|
||||||
z, _ := sizeProperty(view, TranslateZ, session)
|
|
||||||
return x, y, z
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return AngleUnit{Value: 0, Type: Radian}, AngleUnit{Value: 0, Type: Radian}
|
|
||||||
}
|
|
||||||
x, _ := angleProperty(view, SkewX, view.Session())
|
|
||||||
y, _ := angleProperty(view, SkewY, view.Session())
|
|
||||||
return x, y
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func GetScale(view View, subviewID ...string) (float64, float64, float64) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return 1, 1, 1
|
|
||||||
}
|
|
||||||
|
|
||||||
session := view.Session()
|
|
||||||
x, _ := floatProperty(view, ScaleX, session, 1)
|
|
||||||
y, _ := floatProperty(view, ScaleY, session, 1)
|
|
||||||
z, _ := floatProperty(view, ScaleZ, session, 1)
|
|
||||||
return x, y, z
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func GetRotate(view View, subviewID ...string) (float64, float64, float64, AngleUnit) {
|
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view == nil {
|
|
||||||
return 0, 0, 0, AngleUnit{Value: 0, Type: Radian}
|
|
||||||
}
|
|
||||||
|
|
||||||
session := view.Session()
|
|
||||||
angle, _ := angleProperty(view, Rotate, view.Session())
|
|
||||||
rotateX, _ := floatProperty(view, RotateX, session, 1)
|
|
||||||
rotateY, _ := floatProperty(view, RotateY, session, 1)
|
|
||||||
rotateZ, _ := floatProperty(view, RotateZ, session, 1)
|
|
||||||
return rotateX, rotateY, rotateZ, angle
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAvoidBreak returns "true" if avoids any break from being inserted within the principal box,
|
// GetAvoidBreak returns "true" if avoids any break from being inserted within the principal box,
|
||||||
// and "false" if allows, but does not force, any break to be inserted within the principal box.
|
// and "false" if allows, but does not force, any break to be inserted within the principal box.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned
|
||||||
|
|
@ -706,9 +571,9 @@ func GetNotTranslate(view View, subviewID ...string) bool {
|
||||||
return boolStyledProperty(view, subviewID, NotTranslate, true)
|
return boolStyledProperty(view, subviewID, NotTranslate, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func valueFromStyle(view View, tag string) any {
|
func valueFromStyle(view View, tag PropertyName) any {
|
||||||
session := view.Session()
|
session := view.Session()
|
||||||
getValue := func(styleTag string) any {
|
getValue := func(styleTag PropertyName) any {
|
||||||
if style, ok := stringProperty(view, styleTag, session); ok {
|
if style, ok := stringProperty(view, styleTag, session); ok {
|
||||||
if style, ok := session.resolveConstants(style); ok {
|
if style, ok := session.resolveConstants(style); ok {
|
||||||
return session.styleProperty(style, tag)
|
return session.styleProperty(style, tag)
|
||||||
|
|
@ -725,12 +590,28 @@ func valueFromStyle(view View, tag string) any {
|
||||||
return getValue(Style)
|
return getValue(Style)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sizeStyledProperty(view View, subviewID []string, tag string, inherit bool) SizeUnit {
|
func stringStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
if text, ok := stringProperty(view, tag, view.Session()); ok {
|
||||||
}
|
return text
|
||||||
|
}
|
||||||
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
|
if text, ok := value.(string); ok {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inherit {
|
||||||
|
if parent := view.Parent(); parent != nil {
|
||||||
|
return stringStyledProperty(parent, nil, tag, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if view != nil {
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func sizeStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) SizeUnit {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
if value, ok := sizeProperty(view, tag, view.Session()); ok {
|
if value, ok := sizeProperty(view, tag, view.Session()); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -749,12 +630,8 @@ func sizeStyledProperty(view View, subviewID []string, tag string, inherit bool)
|
||||||
return AutoSize()
|
return AutoSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
func enumStyledProperty(view View, subviewID []string, tag string, defaultValue int, inherit bool) int {
|
func enumStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue int, inherit bool) int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := enumProperty(view, tag, view.Session(), defaultValue); ok {
|
if value, ok := enumProperty(view, tag, view.Session(), defaultValue); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -773,12 +650,8 @@ func enumStyledProperty(view View, subviewID []string, tag string, defaultValue
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolStyledProperty(view View, subviewID []string, tag string, inherit bool) bool {
|
func boolStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) bool {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := boolProperty(view, tag, view.Session()); ok {
|
if value, ok := boolProperty(view, tag, view.Session()); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -798,12 +671,8 @@ func boolStyledProperty(view View, subviewID []string, tag string, inherit bool)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func intStyledProperty(view View, subviewID []string, tag string, defaultValue int) int {
|
func intStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue int) int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := intProperty(view, tag, view.Session(), defaultValue); ok {
|
if value, ok := intProperty(view, tag, view.Session(), defaultValue); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -815,11 +684,8 @@ func intStyledProperty(view View, subviewID []string, tag string, defaultValue i
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func floatStyledProperty(view View, subviewID []string, tag string, defaultValue float64) float64 {
|
func floatStyledProperty(view View, subviewID []string, tag PropertyName, defaultValue float64) float64 {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := floatProperty(view, tag, view.Session(), defaultValue); ok {
|
if value, ok := floatProperty(view, tag, view.Session(), defaultValue); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -831,11 +697,8 @@ func floatStyledProperty(view View, subviewID []string, tag string, defaultValue
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func colorStyledProperty(view View, subviewID []string, tag string, inherit bool) Color {
|
func colorStyledProperty(view View, subviewID []string, tag PropertyName, inherit bool) Color {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
if value, ok := colorProperty(view, tag, view.Session()); ok {
|
if value, ok := colorProperty(view, tag, view.Session()); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -853,14 +716,24 @@ func colorStyledProperty(view View, subviewID []string, tag string, inherit bool
|
||||||
return Color(0)
|
return Color(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transformStyledProperty(view View, subviewID []string, tag PropertyName) TransformProperty {
|
||||||
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
|
if transform := getTransformProperty(view, tag); transform != nil {
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
|
||||||
|
if value := valueFromStyle(view, tag); value != nil {
|
||||||
|
return valueToTransformProperty(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FocusView sets focus on the specified subview, if it can be focused.
|
// FocusView sets focus on the specified subview, if it can be focused.
|
||||||
// The focused View is the View which will receive keyboard events by default.
|
// The focused View is the View which will receive keyboard events by default.
|
||||||
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
|
// If the second argument (subviewID) is not specified or it is "" then focus is set on the first argument (view)
|
||||||
func FocusView(view View, subviewID ...string) {
|
func FocusView(view View, subviewID ...string) {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
if view != nil {
|
|
||||||
view.Session().callFunc("focus", view.htmlID())
|
view.Session().callFunc("focus", view.htmlID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -890,12 +763,8 @@ 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).
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetCurrent(view View, subviewID ...string) int {
|
func GetCurrent(view View, subviewID ...string) int {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultValue := -1
|
defaultValue := -1
|
||||||
if view != nil {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
if result, ok := intProperty(view, Current, view.Session(), defaultValue); ok {
|
if result, ok := intProperty(view, Current, view.Session(), defaultValue); ok {
|
||||||
return result
|
return result
|
||||||
} else if view.Tag() != "ListView" {
|
} else if view.Tag() != "ListView" {
|
||||||
|
|
@ -908,11 +777,7 @@ func GetCurrent(view View, subviewID ...string) int {
|
||||||
// IsUserSelect returns "true" if the user can select text, "false" otherwise.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func IsUserSelect(view View, subviewID ...string) bool {
|
func IsUserSelect(view View, subviewID ...string) bool {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
value, _ := isUserSelect(view)
|
value, _ := isUserSelect(view)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
@ -976,11 +841,7 @@ func GetBackgroundBlendMode(view View, subviewID ...string) int {
|
||||||
// GetTooltip returns a tooltip text of the subview.
|
// 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.
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
||||||
func GetTooltip(view View, subviewID ...string) string {
|
func GetTooltip(view View, subviewID ...string) string {
|
||||||
if len(subviewID) > 0 && subviewID[0] != "" {
|
if view = getSubview(view, subviewID); view != nil {
|
||||||
view = ViewByID(view, subviewID[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if view != nil {
|
|
||||||
if value := view.Get(Tooltip); value != nil {
|
if value := view.Get(Tooltip); value != nil {
|
||||||
if text, ok := value.(string); ok {
|
if text, ok := value.(string); ok {
|
||||||
return text
|
return text
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ type ViewsContainer interface {
|
||||||
|
|
||||||
// ViewIndex returns the index of view, -1 overwise
|
// ViewIndex returns the index of view, -1 overwise
|
||||||
ViewIndex(view View) int
|
ViewIndex(view View) int
|
||||||
|
|
||||||
|
setContent(value any) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type viewsContainerData struct {
|
type viewsContainerData struct {
|
||||||
|
|
@ -36,10 +38,10 @@ func (container *viewsContainerData) init(session Session) {
|
||||||
container.viewData.init(session)
|
container.viewData.init(session)
|
||||||
container.tag = "ViewsContainer"
|
container.tag = "ViewsContainer"
|
||||||
container.views = []View{}
|
container.views = []View{}
|
||||||
}
|
container.get = container.getFunc
|
||||||
|
container.set = container.setFunc
|
||||||
func (container *viewsContainerData) String() string {
|
container.remove = container.removeFunc
|
||||||
return getViewString(container, nil)
|
container.changed = container.propertyChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *viewsContainerData) setParentID(parentID string) {
|
func (container *viewsContainerData) setParentID(parentID string) {
|
||||||
|
|
@ -72,27 +74,42 @@ func (container *viewsContainerData) Append(view View) {
|
||||||
} else {
|
} else {
|
||||||
container.views = append(container.views, view)
|
container.views = append(container.views, view)
|
||||||
}
|
}
|
||||||
updateInnerHTML(container.htmlID(), container.session)
|
|
||||||
container.propertyChangedEvent(Content)
|
if container.created {
|
||||||
|
buffer := allocStringBuilder()
|
||||||
|
defer freeStringBuilder(buffer)
|
||||||
|
|
||||||
|
viewHTML(view, buffer, "")
|
||||||
|
container.Session().appendToInnerHTML(htmlID, buffer.String())
|
||||||
|
|
||||||
|
if listener, ok := container.changeListener[Content]; ok {
|
||||||
|
listener(container, Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert inserts a view to the "index" position in the list of a view children
|
// Insert inserts a view to the "index" position in the list of a view children
|
||||||
func (container *viewsContainerData) Insert(view View, index int) {
|
func (container *viewsContainerData) Insert(view View, index int) {
|
||||||
if view != nil {
|
if view != nil {
|
||||||
htmlID := container.htmlID()
|
|
||||||
if container.views == nil || index < 0 || index >= len(container.views) {
|
if container.views == nil || index < 0 || index >= len(container.views) {
|
||||||
container.Append(view)
|
container.Append(view)
|
||||||
} else if index > 0 {
|
return
|
||||||
view.setParentID(htmlID)
|
}
|
||||||
|
|
||||||
|
htmlID := container.htmlID()
|
||||||
|
view.setParentID(htmlID)
|
||||||
|
if index > 0 {
|
||||||
container.views = append(container.views[:index], append([]View{view}, container.views[index:]...)...)
|
container.views = append(container.views[:index], append([]View{view}, container.views[index:]...)...)
|
||||||
updateInnerHTML(container.htmlID(), container.session)
|
|
||||||
container.propertyChangedEvent(Content)
|
|
||||||
} else {
|
} else {
|
||||||
view.setParentID(htmlID)
|
|
||||||
container.views = append([]View{view}, container.views...)
|
container.views = append([]View{view}, container.views...)
|
||||||
updateInnerHTML(container.htmlID(), container.session)
|
}
|
||||||
container.propertyChangedEvent(Content)
|
|
||||||
|
if container.created {
|
||||||
|
updateInnerHTML(htmlID, container.Session())
|
||||||
|
if listener, ok := container.changeListener[Content]; ok {
|
||||||
|
listener(container, Content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +136,13 @@ func (container *viewsContainerData) RemoveView(index int) View {
|
||||||
}
|
}
|
||||||
|
|
||||||
view.setParentID("")
|
view.setParentID("")
|
||||||
updateInnerHTML(container.htmlID(), container.session)
|
|
||||||
container.propertyChangedEvent(Content)
|
if container.created {
|
||||||
|
container.Session().callFunc("removeView", view.htmlID())
|
||||||
|
if listener, ok := container.changeListener[Content]; ok {
|
||||||
|
listener(container, Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +163,7 @@ func (container *viewsContainerData) cssStyle(self View, builder cssBuilder) {
|
||||||
func (container *viewsContainerData) htmlSubviews(self View, buffer *strings.Builder) {
|
func (container *viewsContainerData) htmlSubviews(self View, buffer *strings.Builder) {
|
||||||
if container.views != nil {
|
if container.views != nil {
|
||||||
for _, view := range container.views {
|
for _, view := range container.views {
|
||||||
viewHTML(view, buffer)
|
viewHTML(view, buffer, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,67 +180,60 @@ func viewFromTextValue(text string, session Session) View {
|
||||||
return NewTextView(session, Params{Text: text})
|
return NewTextView(session, Params{Text: text})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *viewsContainerData) Remove(tag string) {
|
func (container *viewsContainerData) removeFunc(tag PropertyName) []PropertyName {
|
||||||
container.remove(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (container *viewsContainerData) remove(tag string) {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Content:
|
case Content:
|
||||||
if container.views == nil || len(container.views) > 0 {
|
if len(container.views) > 0 {
|
||||||
container.views = []View{}
|
container.views = []View{}
|
||||||
updateInnerHTML(container.htmlID(), container.Session())
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
container.propertyChangedEvent(Content)
|
return []PropertyName{}
|
||||||
|
|
||||||
case Disabled:
|
case Disabled:
|
||||||
if _, ok := container.properties[Disabled]; ok {
|
if container.getRaw(Disabled) != nil {
|
||||||
delete(container.properties, Disabled)
|
container.setRaw(Disabled, nil)
|
||||||
if container.views != nil {
|
for _, view := range container.views {
|
||||||
for _, view := range container.views {
|
view.Remove(Disabled)
|
||||||
view.Remove(Disabled)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
container.propertyChangedEvent(tag)
|
return []PropertyName{tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
container.viewData.remove(tag)
|
|
||||||
}
|
}
|
||||||
|
return container.viewData.removeFunc(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *viewsContainerData) Set(tag string, value any) bool {
|
func (container *viewsContainerData) setFunc(tag PropertyName, value any) []PropertyName {
|
||||||
return container.set(strings.ToLower(tag), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (container *viewsContainerData) set(tag string, value any) bool {
|
|
||||||
if value == nil {
|
|
||||||
container.remove(tag)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Content:
|
case Content:
|
||||||
return container.setContent(value)
|
if container.setContent(value) {
|
||||||
|
return []PropertyName{tag}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
case Disabled:
|
case Disabled:
|
||||||
oldDisabled := IsDisabled(container)
|
oldDisabled := IsDisabled(container)
|
||||||
if container.viewData.Set(Disabled, value) {
|
result := container.viewData.setFunc(Disabled, value)
|
||||||
|
if result != nil {
|
||||||
disabled := IsDisabled(container)
|
disabled := IsDisabled(container)
|
||||||
if oldDisabled != disabled {
|
if oldDisabled != disabled {
|
||||||
if container.views != nil {
|
for _, view := range container.views {
|
||||||
for _, view := range container.views {
|
view.Set(Disabled, disabled)
|
||||||
view.Set(Disabled, disabled)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
container.propertyChangedEvent(tag)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
return container.viewData.set(tag, value)
|
return container.viewData.setFunc(tag, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (container *viewsContainerData) propertyChanged(tag PropertyName) {
|
||||||
|
switch tag {
|
||||||
|
case Content:
|
||||||
|
updateInnerHTML(container.htmlID(), container.Session())
|
||||||
|
|
||||||
|
default:
|
||||||
|
container.viewData.propertyChanged(tag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *viewsContainerData) setContent(value any) bool {
|
func (container *viewsContainerData) setContent(value any) bool {
|
||||||
|
|
@ -291,25 +306,16 @@ func (container *viewsContainerData) setContent(value any) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.created {
|
|
||||||
updateInnerHTML(htmlID, container.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
container.propertyChangedEvent(Content)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (container *viewsContainerData) Get(tag string) any {
|
func (container *viewsContainerData) getFunc(tag PropertyName) any {
|
||||||
return container.get(strings.ToLower(tag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (container *viewsContainerData) get(tag string) any {
|
|
||||||
switch tag {
|
switch tag {
|
||||||
case Content:
|
case Content:
|
||||||
return container.views
|
return container.views
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return container.viewData.get(tag)
|
return container.viewData.getFunc(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -421,8 +421,8 @@ func (bridge *webBridge) remoteValue(funcName string, args ...any) (DataObject,
|
||||||
|
|
||||||
funcArgs := append([]any{answerID}, args...)
|
funcArgs := append([]any{answerID}, args...)
|
||||||
var result DataObject = nil
|
var result DataObject = nil
|
||||||
ok := bridge.callFuncImmediately(funcName, funcArgs...)
|
|
||||||
if ok {
|
if bridge.callFuncImmediately(funcName, funcArgs...) {
|
||||||
result = <-answer
|
result = <-answer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue