diff --git a/CHANGELOG.md b/CHANGELOG.md index b2078be..c87de36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v0.9.0 + +* Requires go 1.18 or higher +* The "interface{}" type replaced by "any" +* Added SizeFunc interface and Function field to SizeUnit struct +* Added MaxSize, MinSize, SumSize, SubSize, MulSize, DivSize, ClampSize functions +* Added "list-row-gap", "list-column-gap", "accent-color", "tab-size", "overflow", +"arrow", "arrow-align", "arrow-size", "arrow-width", and "arrow-offset" properties +* Added "@ruiArrowSize" and "@ruiArrowWidth" constants to the default theme +* Added Transition, Transitions, and SetTransition functions to the ViewStyle interface +* Added GetListRowGap, GetListColumnGap, GetAccentColor, GetTabSize, GetOverflow, IsTimingFunctionValid, and GetTransitions functions +* Changed GetTransition functions +* Added the OpenURL function to the Session interface +* Changed the type of the second argument of all Get functions to "subviewID ...string" (previously "subviewID string") + # v0.8.0 * Added "loaded-event" and "error-event" events to ImageView diff --git a/README-ru.md b/README-ru.md index 2968b42..9058210 100644 --- a/README-ru.md +++ b/README-ru.md @@ -30,7 +30,7 @@ В функции main вызывается функция StartApp. Она создает rui приложение и запускает его основной цикл. Функция StartApp имеет 3 параметра: 1) IP адрес по которому будет доступно приложение (в нашем примере это "localhost:8000") -2) Фуекция создает структуру реализующую интерфейс SessionContent +2) Функция создает структуру реализующую интерфейс SessionContent 3) Дополнительные опциональные параметры (в нашем примере это заголовок и имя файла иконки) Интерфейс SessionContent объявлен как: @@ -57,12 +57,12 @@ SizeUnit объявлена как type SizeUnit struct { - Type SizeUnitType - Value float64 + Type SizeUnitType + Value float64 + Function SizeFunc } -где Type - тип размера; -Value - размер +где Type - тип размера; Value - размер; Function - функция (используется только если Type == SizeFunction, в остальных случаях игнорируется). Тип может принимать следующие значения: @@ -79,12 +79,13 @@ Value - размер | 8 | SizeInMM | поле Value определяет размер в миллиметрах. | | 9 | SizeInCM | поле Value определяет размер в сантиметрах. | | 10 | SizeInFraction | поле Value определяет размер в частях. Используется только для задания размеров ячеек в GridLayout. | +| 11 | SizeFunction | поле Function задает функцию для вычисления размера. Значение поля Value игнорируется | Для более наглядного и простого задания переменных типа SizeUnit могут использоваться функции приведенные ниже | Функция | Эквивалентное определение | |----------------|----------------------------------------------------| -| rui.AutoSize() | rui.SizeUnit{ Type: rui.Auto, Value: 0 } | +| rui.AutoSize() | rui.SizeUnit{ Type: rui.Auto } | | rui.Px(n) | rui.SizeUnit{ Type: rui.SizeInPixel, Value: n } | | rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } | | rui.Ex(n) | rui.SizeUnit{ Type: rui.SizeInEX, Value: n } | @@ -96,7 +97,10 @@ Value - размер | rui.Cm(n) | rui.SizeUnit{ Type: rui.SizeInCM, Value: n } | | rui.Fr(n) | rui.SizeUnit{ Type: rui.SizeInFraction, Value: n } | -Переменные типа SizeUnit имеют текстовое представление (зачем оно нужно будет описано ниже). Текстовое представление состоит из числа (равному значению поля Value) и следующим за ним суффиксом определяющим тип. Исключением является значение типа Auto, которое имеет представление “auto”. Суффиксы перечислены в следующей таблице: +Переменные типа SizeUnit имеют текстовое представление (зачем оно нужно будет описано ниже). +Текстовое представление состоит из числа (равному значению поля Value) и следующим за ним суффиксом определяющим тип. +Исключением является значение типа Auto, которое имеет представление “auto” и +значение типа SizeFunction, которое имеет особое представление. Суффиксы перечислены в следующей таблице: | Суффикс | Тип | |:-------:|----------------| @@ -119,6 +123,52 @@ Value - размер Получить текстовое представление структуры можно свойством String() +#### SizeFunc + +Интерфейс SizeFunc используется для задания функции вычисляющей SizeUnit. Рассмотрим функции на примере функции min. + +Функция min находит минимальное значение среди заданных аргументов. Данная функция задается с помощью функции MinSize, объявленной как: + + func MinSize(arg0, arg1 any, args ...any) SizeFunc + +Функция имеет 2 и более аргументов, каждый из которых может быть или SizeUnit или SizeFunc или string являющееся константой или +текстовым представлением SizeUnit или SizeFunc. + +Примеры + + rui.MizSize(rui.Percent(50), rui.Px(250)) + rui.MizSize("50%", rui.Px(250), "40em") + rui.MizSize(rui.Percent(50), "@a1") + +Функция min имеет следующее текстовое представление + + "min(, , ...)" + +где arg1, arg2, ... должны быть текстовым представлением SizeUnit или SizeFunc или константой. Например + + "min(50%, 250px)" + "min(75%, @a1)" + +Интерфейс SizeFunc реализует интерфейс fmt.Stringer. +Функция String() этого интерфейса возвращает текстовое представление SizeFunc. + +Помимо min имеются следующие функции + +| Текстовое представление | Функция для создания | Описание +|------------------------------|--------------------------------------|-------------------------------------------------| +| "min(, , ...)" | MaxSize(arg0, arg1 any, args ...any) | находит минимальное значение среди аргументов | +| "sum(, , ...)" | SumSize(arg0, arg1 any, args ...any) | находит сумму значений аргументов | +| "sub(, )" | SubSize(arg0, arg1 any) | находит разность значений аргументов | +| "mul(, )" | MulSize(arg0, arg1 any) | находит результат умножения значений аргументов | +| "div(, )" | DivSize(arg0, arg1 any) | находит результат деления значений аргументов | +| "clamp(, , )" | ClampSize(min, val, max any) | ограничивает значение заданным диапазоном | + +Дополнительные пояснения к функции "clamp(, , )": результат вычисляется следующим образом: + +* if min ≤ val ≤ max then val; +* if val < min then min; +* if max < val then max; + ### Color Тип Color описывает 32-битный цвет в формате ARGB: @@ -358,8 +408,8 @@ View имеет ряд свойств, таких как высота, шири (View реализует данный интерфейс): type Properties interface { - Get(tag string) interface{} - Set(tag string, value interface{}) bool + Get(tag string) any + Set(tag string, value any) bool Remove(tag string) Clear() AllTags() []string @@ -374,8 +424,8 @@ View имеет ряд свойств, таких как высота, шири Для упрощения установки/чтения свойств имеются также две глобальные функции Get и Set: - func Get(rootView View, viewID, tag string) interface{} - func Set(rootView View, viewID, tag string, value interface{}) bool + func Get(rootView View, viewID, tag string) any + func Set(rootView View, viewID, tag string, value any) bool Данные функции возвращают/устанавливают значение дочернего View @@ -439,7 +489,7 @@ View имеет ряд свойств, таких как высота, шири * функция func([<параметры>]) * массив функций []func(< View >[, <параметры>]) * массив функций []func([<параметры>]) -* []interface{} содержащий только func(< View >[, <параметры>]) и func([<параметры>]) +* []any содержащий только func(< View >[, <параметры>]) и func([<параметры>]) После присваивания свойству все эти типы преобразуются в массив функций []func(, [<параметры>]). Соответственно функция Get всегда возвращает массив функций []func(, [<параметры>]). @@ -451,7 +501,7 @@ View имеет ряд свойств, таких как высота, шири * func(newText string) * []func(editor EditView, newText string) * []func(newText string) -* []interface{} содержащий только func(editor EditView, newText string) и func(newText string) +* []any содержащий только func(editor EditView, newText string) и func(newText string) А свойство "edit-text-changed" всегда хранит и возвращает []func(EditView, string). @@ -538,17 +588,17 @@ SizeUnit или имя константы (о константах ниже): Это довольно громоздко поэтому для каждого свойства существует одноимённая глобальная функция с префиксом Get, которая выполняет данное приведение типа, получает значение константы, если необходимо, и -возвращает его. Все функции данного типа имеют два аргумента: View и subviewID string. -Первый аргумент это корневой View, второй - ID дочернего View. Если ID дочернего View передать как "", +возвращает его. Все функции данного типа имеют два аргумента: View и subviewID ...string. +Первый аргумент это корневой View, второй - ID дочернего View. Если ID дочернего View не задать или передать как "", то возвращается значение корневого View. Для свойств "width", "height", "min-width", "min-height", "max-width", "max-height" это функции: - func GetWidth(view View, subviewID string) SizeUnit - func GetHeight(view View, subviewID string) SizeUnit - func GetMinWidth(view View, subviewID string) SizeUnit - func GetMinHeight(view View, subviewID string) SizeUnit - func GetMaxWidth(view View, subviewID string) SizeUnit - func GetMaxHeight(view View, subviewID string) SizeUnit + func GetWidth(view View, subviewID ...string) SizeUnit + func GetHeight(view View, subviewID ...string) SizeUnit + func GetMinWidth(view View, subviewID ...string) SizeUnit + func GetMinHeight(view View, subviewID ...string) SizeUnit + func GetMaxWidth(view View, subviewID ...string) SizeUnit + func GetMaxHeight(view View, subviewID ...string) SizeUnit ### Свойство "resize" @@ -567,7 +617,7 @@ SizeUnit или имя константы (о константах ниже): Получить значение данного свойства можно с помощью функции - func GetResize(view View, subviewID string) int + func GetResize(view View, subviewID ...string) int ### Свойства "margin" и "padding" @@ -608,8 +658,8 @@ BoundsProperty с помощью функции "Bounds(session Session) Bounds" Для этого используется также могут использоваться глобальные функции: - func GetMargin(view View, subviewID string) Bounds - func GetPadding(view View, subviewID string) Bounds + func GetMargin(view View, subviewID ...string) Bounds + func GetPadding(view View, subviewID ...string) Bounds Текстовое представление BoundsProperty имеет следующий вид: @@ -743,7 +793,7 @@ BoundsProperty с помощью функции "Bounds(session Session) Bounds" BorderProperty, а не структура ViewBorders. Получить структуру ViewBorders без дополнительных преобразований можно с помощью глобальной функции - func GetBorder(view View, subviewID string) ViewBorders + func GetBorder(view View, subviewID ...string) ViewBorders Кроме вспомогательных свойств "style", "width" и "color" есть еще 4: "left", "right", "top" и "bottom". В качестве значения эти свойства могут принимать только структуру ViewBorder и позволяю установить все @@ -897,7 +947,7 @@ RadiusProperty имеет текстовое представление след RadiusProperty, а не структура BoxRadius. Получить структуру BoxRadius без дополнительных преобразований можно с помощью глобальной функции - func GetRadius(view View, subviewID string) BoxRadius + func GetRadius(view View, subviewID ...string) BoxRadius Вы также можете устанавливать отдельные радиусы использую функцию Set интерфейса View. Для этого используются следующие свойства @@ -973,7 +1023,7 @@ RadiusProperty, а не структура BoxRadius. Получить стру Получить значение данного свойства можно с помощью функции - func GetViewShadows(view View, subviewID string) []ViewShadow + func GetViewShadows(view View, subviewID ...string) []ViewShadow Если тень не задана, то данная функция вернет пустой массив @@ -1037,7 +1087,7 @@ RadiusProperty, а не структура BoxRadius. Получить стру Pos определяет положение точки относительно начала линии градиента. Массив должен иметь не менее 2 точек. В качестве значения градиента можно также передать массив Color. В этом случае точки равномерно распределяются вдоль линии градиента. -Также в качестве массива ключевых точек можно использовать массив типа []interface{}. +Также в качестве массива ключевых точек можно использовать массив типа []any. Элементами этого массива могут быть BackgroundGradientPoint, Color, текстовое представление BackgroundGradientPoint или Color и имя константы @@ -1105,8 +1155,8 @@ Pos определяет положение точки относительно описывается структурой BackgroundGradientAngle: type BackgroundGradientAngle struct { - Color interface{} - Angle interface{} + Color any + Angle any } где Color задает цвет ключевого угла и может принимать значение типа Color или string (цветовая константа @@ -1216,7 +1266,7 @@ radius необходимо передать nil Многоугольная область обрезки. Создается с помощью функций: - func PolygonClip(points []interface{}) ClipShape + func PolygonClip(points []any) ClipShape func PolygonPointsClip(points []SizeUnit) ClipShape в качестве аргумента передается массив угловых точек многоугольника в следующем порядке: x1, y1, x2, y2, … @@ -1234,7 +1284,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetOpacity(view View, subviewID string) float64 + func GetOpacity(view View, subviewID ...string) float64 ### Свойство "z-index" @@ -1244,7 +1294,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetZIndex(view View, subviewID string) int + func GetZIndex(view View, subviewID ...string) int ### Свойство "visibility" @@ -1258,7 +1308,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetVisibility(view View, subviewID string) int + func GetVisibility(view View, subviewID ...string) int ### Свойства "filter" и "backdrop-filter" @@ -1287,8 +1337,8 @@ radius необходимо передать nil Получить значение текущего фильтра можно с помощью функций - func GetFilter(view View, subviewID string) ViewFilter - func GetBackdropFilter(view View, subviewID string) ViewFilter + func GetFilter(view View, subviewID ...string) ViewFilter + func GetBackdropFilter(view View, subviewID ...string) ViewFilter ### Свойство "semantics" @@ -1335,7 +1385,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetFontName(view View, subviewID string) string + func GetFontName(view View, subviewID ...string) string #### Свойство "text-color" @@ -1343,7 +1393,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextColor(view View, subviewID string) Color + func GetTextColor(view View, subviewID ...string) Color #### Свойство "text-size" @@ -1351,7 +1401,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextSize(view View, subviewID string) SizeUnit + func GetTextSize(view View, subviewID ...string) SizeUnit #### Свойство "italic" @@ -1359,7 +1409,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func IsItalic(view View, subviewID string) bool + func IsItalic(view View, subviewID ...string) bool #### Свойство "small-caps" @@ -1367,7 +1417,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func IsSmallCaps(view View, subviewID string) bool + func IsSmallCaps(view View, subviewID ...string) bool #### Свойство "white-space" @@ -1408,6 +1458,11 @@ radius необходимо передать nil | WhiteSpacePreLine | Сохраняются как в источнике | Объединяются в один пробел | Переносится | Удаляются | | WhiteSpaceBreakSpaces | Сохраняются как в источнике | Сохраняются как в источнике | Переносится | Переносятся | +#### Свойство "tab-size" + +Свойство "tab-size" (константа TabSize) типа int задает размер символа табуляции (U+0009) в пробелах. +Значение свойства "tab-size" должно быть больше 0. + #### Свойство "word-break" Свойство "word-break" (константа WordBreak) типа int определяет, где будет установлен перевод @@ -1437,9 +1492,9 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функций - func IsStrikethrough(view View, subviewID string) bool - func IsOverline(view View, subviewID string) bool - func IsUnderline(view View, subviewID string) bool + func IsStrikethrough(view View, subviewID ...string) bool + func IsOverline(view View, subviewID ...string) bool + func IsUnderline(view View, subviewID ...string) bool #### Свойство "text-line-thickness" @@ -1448,7 +1503,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - GetTextLineThickness(view View, subviewID string) SizeUnit + GetTextLineThickness(view View, subviewID ...string) SizeUnit #### Свойство "text-line-style" @@ -1468,7 +1523,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextLineStyle(view View, subviewID string) int + func GetTextLineStyle(view View, subviewID ...string) int #### Свойство "text-line-color" @@ -1478,7 +1533,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextLineColor(view View, subviewID string) Color + func GetTextLineColor(view View, subviewID ...string) Color #### Свойство "text-weight" @@ -1500,7 +1555,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextWeight(view View, subviewID string) int + func GetTextWeight(view View, subviewID ...string) int #### Свойство "text-shadow" @@ -1527,7 +1582,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextShadows(view View, subviewID string) []ViewShadow + func GetTextShadows(view View, subviewID ...string) []ViewShadow Если тень не задана, то данная функция вернет пустой массив @@ -1544,7 +1599,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextAlign(view View, subviewID string) int + func GetTextAlign(view View, subviewID ...string) int #### Свойство "text-indent" @@ -1552,7 +1607,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextIndent(view View, subviewID string) SizeUnit + func GetTextIndent(view View, subviewID ...string) SizeUnit #### Свойство "letter-spacing" @@ -1562,7 +1617,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetLetterSpacing(view View, subviewID string) SizeUnit + func GetLetterSpacing(view View, subviewID ...string) SizeUnit #### Свойство "word-spacing" @@ -1572,7 +1627,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetWordSpacing(view View, subviewID string) SizeUnit + func GetWordSpacing(view View, subviewID ...string) SizeUnit #### Свойство "line-height" @@ -1580,7 +1635,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetLineHeight(view View, subviewID string) SizeUnit + func GetLineHeight(view View, subviewID ...string) SizeUnit #### Свойство "text-transform" @@ -1595,7 +1650,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextTransform(view View, subviewID string) int + func GetTextTransform(view View, subviewID ...string) int #### Свойство "text-direction" @@ -1609,7 +1664,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetTextDirection(view View, subviewID string) int + func GetTextDirection(view View, subviewID ...string) int #### Свойство "writing-mode" @@ -1626,7 +1681,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetWritingMode(view View, subviewID string) int + func GetWritingMode(view View, subviewID ...string) int #### Свойство "vertical-text-orientation" @@ -1641,7 +1696,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetVerticalTextOrientation(view View, subviewID string) int + func GetVerticalTextOrientation(view View, subviewID ...string) int #### Свойство "user-select" @@ -1657,7 +1712,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func IsUserSelect(view View, subviewID string) bool + func IsUserSelect(view View, subviewID ...string) bool ### Свойства трансформации @@ -1676,7 +1731,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetPerspective(view View, subviewID string) SizeUnit + func GetPerspective(view View, subviewID ...string) SizeUnit #### Свойства "perspective-origin-x" и "perspective-origin-y" @@ -1687,7 +1742,7 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функции - func GetPerspectiveOrigin(view View, subviewID string) (SizeUnit, SizeUnit) + func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) #### Свойство "backface-visibility" @@ -1700,7 +1755,7 @@ radius необходимо передать nil Получить значение данного свойства можно с помощью функции - func GetBackfaceVisible(view View, subviewID string) bool + func GetBackfaceVisible(view View, subviewID ...string) bool #### Свойства "origin-x", "origin-y" и "origin-z" @@ -1713,7 +1768,7 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функции - func GetOrigin(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) + func GetOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) #### Свойства "translate-x", "translate-y" и "translate-z" @@ -1724,7 +1779,7 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функции - func GetTranslate(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) + func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) #### Свойства "scale-x", "scale-y" и "scale-z" @@ -1737,7 +1792,7 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функции - func GetScale(view View, subviewID string) (float64, float64, float64) + func GetScale(view View, subviewID ...string) (float64, float64, float64) #### Свойства "rotate" @@ -1754,7 +1809,7 @@ radius необходимо передать nil Получить значение данных свойств, а также свойства "rotate" можно с помощью функции - func GetRotate(view View, subviewID string) (float64, float64, float64, AngleUnit) + func GetRotate(view View, subviewID ...string) (float64, float64, float64, AngleUnit) #### Свойства "skew-x" и "skew-y" @@ -1764,7 +1819,7 @@ radius необходимо передать nil Получить значение данных свойств можно с помощью функции - func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit) + func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) ### Пользовательские данные @@ -1804,8 +1859,8 @@ radius необходимо передать nil Получить списки слушателей событий клавиатуры можно с помощью функций: - func GetKeyDownListeners(view View, subviewID string) []func(View, KeyEvent) - func GetKeyUpListeners(view View, subviewID string) []func(View, KeyEvent) + func GetKeyDownListeners(view View, subviewID ...string) []func(View, KeyEvent) + func GetKeyUpListeners(view View, subviewID ...string) []func(View, KeyEvent) ### События фокуса @@ -1826,8 +1881,8 @@ radius необходимо передать nil Получить списки слушателей событий фокуса можно с помощью функций: - func GetFocusListeners(view View, subviewID string) []func(View) - func GetLostFocusListeners(view View, subviewID string) []func(View) + func GetFocusListeners(view View, subviewID ...string) []func(View) + func GetLostFocusListeners(view View, subviewID ...string) []func(View) ### События мыши @@ -1895,14 +1950,14 @@ radius необходимо передать nil Получить списки слушателей событий мыши можно с помощью функций: - func GetMouseDownListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseUpListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseMoveListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseOverListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseOutListeners(view View, subviewID string) []func(View, MouseEvent) - func GetClickListeners(view View, subviewID string) []func(View, MouseEvent) - func GetDoubleClickListeners(view View, subviewID string) []func(View, MouseEvent) - func GetContextMenuListeners(view View, subviewID string) []func(View, MouseEvent) + func GetMouseDownListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseUpListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseMoveListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseOverListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseOutListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetClickListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetDoubleClickListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetContextMenuListeners(view View, subviewID ...string) []func(View, MouseEvent) ### События указателя @@ -1949,12 +2004,12 @@ radius необходимо передать nil Получить списки слушателей событий указателя можно с помощью функций: - func GetPointerDownListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerUpListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerMoveListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerCancelListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerOverListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerOutListeners(view View, subviewID string) []func(View, PointerEvent) + func GetPointerDownListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerUpListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerMoveListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerCancelListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerOverListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerOutListeners(view View, subviewID ...string) []func(View, PointerEvent) ### Touch события @@ -2007,10 +2062,10 @@ radius необходимо передать nil Получить списки слушателей событий касания можно с помощью функций: - func GetTouchStartListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchEndListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchMoveListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchCancelListeners(view View, subviewID string) []func(View, TouchEvent) + func GetTouchStartListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchEndListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchMoveListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchCancelListeners(view View, subviewID ...string) []func(View, TouchEvent) ### Событие "resize-event" @@ -2039,7 +2094,7 @@ radius необходимо передать nil Получить список слушателей данного события можно с помощью функции: - func GetResizeListeners(view View, subviewID string) []func(View, Frame) + func GetResizeListeners(view View, subviewID ...string) []func(View, Frame) Текущие положение и размеры видимой части View можно получить с помощью функции интерфейса View: @@ -2047,7 +2102,7 @@ radius необходимо передать nil или глобальной функции - func GetViewFrame(view View, subviewID string) Frame + func GetViewFrame(view View, subviewID ...string) Frame ### Событие прокрутки @@ -2078,13 +2133,13 @@ radius необходимо передать nil или глобальной функции - func GetViewScroll(view View, subviewID string) Frame + func GetViewScroll(view View, subviewID ...string) Frame Для программной прокрутки могут использоваться следующие глобальные функции func ScrollViewTo(view View, subviewID string, x, y float64) - func ScrollViewToStart(view View, subviewID string) - func ScrollViewToEnd(view View, subviewID string) + func ScrollViewToStart(view View, subviewID ...string) + func ScrollViewToEnd(view View, subviewID ...string) которые прокручивают view, соответственно, в заданную позицию, начало и конец @@ -2114,7 +2169,7 @@ radius необходимо передать nil * []string - каждый элемент массива преобразуется во View как описано в предыдущем пункте; -* []interface{} - данный массив должен содержать только View и string. Каждый string-элемент +* []any - данный массив должен содержать только View и string. Каждый string-элемент преобразуется во View, как описано выше. Если массив будет содержать недопустимае значения, то свойство "content" не будет установлено, а функция Set вернет false и в лог запишется сообщение об ошибке. @@ -2200,6 +2255,11 @@ ListLayout является контейнером, реализующим ин | 2 | CenterAlign | "center" | Выравнивание по центру | | 3 | StretchAlign | "stretch" | Выравнивание по ширине | +### "list-row-gap" и "list-column-gap" + +Свойства "list-row-gap" и "list-column-gap" (константы ListRowGap и ListColumnGap) типа SizeUnit позволяют +установить соответственно расстояния между строками и столбцами контейнера. Значение по умолчанию 0. + ## GridLayout GridLayout является контейнером, реализующим интерфейс ViewsContainer. Для его создания используется функция @@ -2285,7 +2345,7 @@ Cвойство "grid-auto-flow" может принимать следующи * []string. Каждый элемент должен быть текстовым представлением SizeUnit (или SizeUnit константой) -* []interface{}. Каждый элемент должен или иметь тип SizeUnit или быть текстовым +* []any. Каждый элемент должен или иметь тип SizeUnit или быть текстовым представлением SizeUnit (или SizeUnit константой) Если количество элементов в свойствах "cell-width" и "cell-height" меньше, чем используемое число @@ -2353,7 +2413,7 @@ ColumnLayout является контейнером, реализующим и Получить значение данного свойства можно с помощью функции - func GetColumnCount(view View, subviewID string) int + func GetColumnCount(view View, subviewID ...string) int ### Свойство "column-width" @@ -2365,7 +2425,7 @@ ColumnLayout является контейнером, реализующим и Получить значение данного свойства можно с помощью функции - func GetColumnWidth(view View, subviewID string) SizeUnit + func GetColumnWidth(view View, subviewID ...string) SizeUnit ### Свойство "column-gap" @@ -2373,7 +2433,7 @@ ColumnLayout является контейнером, реализующим и Получить значение данного свойства можно с помощью функции - func GetColumnGap(view View, subviewID string) SizeUnit + func GetColumnGap(view View, subviewID ...string) SizeUnit ### Свойство "column-separator" @@ -2421,7 +2481,7 @@ ViewBorder описана как а не структура ViewBorder. Получить структуру ViewBorders без дополнительных преобразований можно с помощью глобальной функции - func GetColumnSeparator(view View, subviewID string) ViewBorder + func GetColumnSeparator(view View, subviewID ...string) ViewBorder Вы также можете устанавливать отдельные атрибуты линии использую функцию Set интерфейса View. Для этого используются следующие свойства @@ -2459,7 +2519,7 @@ ViewBorder описана как Получить значение данного свойства можно с помощью функции - func GetAvoidBreak(view View, subviewID string) bool + func GetAvoidBreak(view View, subviewID ...string) bool ## StackLayout @@ -2505,7 +2565,7 @@ StackLayout является контейнером, реализующим ин func peek(layout rui.StackLayout) { views := layout.Views() - if index := rui.GetCurrent(layout, ""); index >= 0 && index < len(views) { + if index := rui.GetCurrent(layout); index >= 0 && index < len(views) { return views[index] } return nil @@ -2574,7 +2634,7 @@ Cвойства "tab-close-button" может быть задано как дл Для программного переключания вкладок присвойте данному свойству значение индекса новой текущего View. Прочитать значение свойства "current" можно с помощью функции - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int Также свойство "current" может быть использовано для отслеживания изменения текущего View: @@ -2650,11 +2710,11 @@ View, "false" - скрывает. Получить значение свойства "expanded" можно с помощью функции - func IsDetailsExpanded(view View, subviewID string) bool + func IsDetailsExpanded(view View, subviewID ...string) bool а значение свойства "summary" можно получить с помощью функции - func GetDetailsSummary(view View, subviewID string) View + func GetDetailsSummary(view View, subviewID ...string) View ## Resizable @@ -2698,7 +2758,7 @@ AllSides определено как Выводимый текст задается string свойством "text" (константа Text). Помимо метода Get значение свойства "text" может быть получено с помощью функции - func GetText(view View, subviewID string) string + func GetText(view View, subviewID ...string) string TextView наследует от View все свойства параметров текста ("font-name", "text-size", "text-color" и т.д.). Кроме них добавляется еще один "text-overflow" (константа TextOverflow). Он определяет как обрезается @@ -2781,11 +2841,11 @@ NaturalSize() возвращает исходную ширину и высоту Для получения значений свойств ImageView могут использоваться следующие функции: - func GetImageViewSource(view View, subviewID string) string - func GetImageViewAltText(view View, subviewID string) string - func GetImageViewFit(view View, subviewID string) int - func GetImageViewVerticalAlign(view View, subviewID string) int - func GetImageViewHorizontalAlign(view View, subviewID string) int + func GetImageViewSource(view View, subviewID ...string) string + func GetImageViewAltText(view View, subviewID ...string) string + func GetImageViewFit(view View, subviewID ...string) int + func GetImageViewVerticalAlign(view View, subviewID ...string) int + func GetImageViewHorizontalAlign(view View, subviewID ...string) int ## EditView @@ -2841,15 +2901,15 @@ Cвойство "caret-color" может быть задано не только Для получения значений свойств EditView могут использоваться следующие функции: - func GetText(view View, subviewID string) string - func GetHint(view View, subviewID string) string - func GetMaxLength(view View, subviewID string) int - func GetEditViewType(view View, subviewID string) int - func GetEditViewPattern(view View, subviewID string) string - func IsReadOnly(view View, subviewID string) bool - func IsEditViewWrap(view View, subviewID string) bool - func IsSpellcheck(view View, subviewID string) bool - func GetCaretColor(view View, subviewID string) Color + func GetText(view View, subviewID ...string) string + func GetHint(view View, subviewID ...string) string + func GetMaxLength(view View, subviewID ...string) int + func GetEditViewType(view View, subviewID ...string) int + func GetEditViewPattern(view View, subviewID ...string) string + func IsReadOnly(view View, subviewID ...string) bool + func IsEditViewWrap(view View, subviewID ...string) bool + func IsSpellcheck(view View, subviewID ...string) bool + func GetCaretColor(view View, subviewID ...string) Color Для отслеживания изменения текста используется событие "edit-text-changed" (константа EditTextChangedEvent). Основной слушатель события имеет следующий формат: @@ -2860,7 +2920,7 @@ EditTextChangedEvent). Основной слушатель события име Получить текущий список слушателей изменения текста можно с помощью функции - func GetTextChangedListeners(view View, subviewID string) []func(EditView, string) + func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string) ## NumberPicker @@ -2893,7 +2953,7 @@ NumberPicker может работать в двух режимах: редак Все эти типы приводятся к float64. Соответственно функция Get всегда возвращает float64 значение. Прочитано значение свойства "number-picker-value" может быть также с помощью функции: - func GetNumberPickerValue(view View, subviewID string) float64 + func GetNumberPickerValue(view View, subviewID ...string) float64 На вводимые значения могут быть наложены ограничения. Для этого используются следующие свойства: @@ -2911,8 +2971,8 @@ NumberPicker может работать в двух режимах: редак Прочитать значения данных свойств можно с помощью функций: - func GetNumberPickerMinMax(view View, subviewID string) (float64, float64) - func GetNumberPickerStep(view View, subviewID string) float64 + func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) + func GetNumberPickerStep(view View, subviewID ...string) float64 Для отслеживания изменения вводимого значения используется событие "number-changed" (константа NumberChangedEvent). Основной слушатель события имеет следующий формат: @@ -2923,7 +2983,7 @@ NumberChangedEvent). Основной слушатель события име Получить текущий список слушателей изменения значения можно с помощью функции - func GetNumberChangedListeners(view View, subviewID string) []func(NumberPicker, float64) + func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64) ## DatePicker @@ -2944,7 +3004,7 @@ NumberChangedEvent). Основной слушатель события име Текст преобразуется в time.Time. Соответственно функция Get всегда возвращает time.Time значение. Прочитано значение свойства "date-picker-value" может быть также с помощью функции: - func GetDatePickerValue(view View, subviewID string) time.Time + func GetDatePickerValue(view View, subviewID ...string) time.Time На вводимые даты могут быть наложены ограничения. Для этого используются следующие свойства: @@ -2956,9 +3016,9 @@ NumberChangedEvent). Основной слушатель события име Прочитать значения данных свойств можно с помощью функций: - func GetDatePickerMin(view View, subviewID string) (time.Time, bool) - func GetDatePickerMax(view View, subviewID string) (time.Time, bool) - func GetDatePickerStep(view View, subviewID string) int + func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) + func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) + func GetDatePickerStep(view View, subviewID ...string) int Для отслеживания изменения вводимого значения используется событие "date-changed" (константа DateChangedEvent). Основной слушатель события имеет следующий формат: @@ -2969,7 +3029,7 @@ DateChangedEvent). Основной слушатель события имее Получить текущий список слушателей изменения даты можно с помощью функции - func GetDateChangedListeners(view View, subviewID string) []func(DatePicker, time.Time) + func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time) ## TimePicker @@ -2990,7 +3050,7 @@ DateChangedEvent). Основной слушатель события имее Текст преобразуется в time.Time. Соответственно функция Get всегда возвращает time.Time значение. Прочитано значение свойства "time-picker-value" может быть также с помощью функции: - func GetTimePickerValue(view View, subviewID string) time.Time + func GetTimePickerValue(view View, subviewID ...string) time.Time На вводимое время могут быть наложены ограничения. Для этого используются следующие свойства: @@ -3002,9 +3062,9 @@ DateChangedEvent). Основной слушатель события имее Прочитать значения данных свойств можно с помощью функций: - func GetTimePickerMin(view View, subviewID string) (time.Time, bool) - func GetTimePickerMax(view View, subviewID string) (time.Time, bool) - func GetTimePickerStep(view View, subviewID string) int + func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) + func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) + func GetTimePickerStep(view View, subviewID ...string) int Для отслеживания изменения вводимого значения используется событие "time-changed" (константа TimeChangedEvent). Основной слушатель события имеет следующий формат: @@ -3015,7 +3075,7 @@ TimeChangedEvent). Основной слушатель события имее Получить текущий список слушателей изменения даты можно с помощью функции - func GetTimeChangedListeners(view View, subviewID string) []func(TimePicker, time.Time) + func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time) ## ColorPicker @@ -3034,7 +3094,7 @@ TimeChangedEvent). Основной слушатель события имее Прочитано значение свойства "color-picker-value" может быть также с помощью функции: - func GetColorPickerValue(view View, subviewID string) Color + func GetColorPickerValue(view View, subviewID ...string) Color Для отслеживания изменения выбранного цвета используется событие "color-changed" (константа ColorChangedEvent). Основной слушатель события имеет следующий формат: @@ -3045,7 +3105,7 @@ ColorChangedEvent). Основной слушатель события имее Получить текущий список слушателей изменения даты можно с помощью функции - func GetColorChangedListeners(view View, subviewID string) []func(ColorPicker, Color) + func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color) ## FilePicker @@ -3074,7 +3134,7 @@ ColorChangedEvent). Основной слушатель события имее а также соответствующие им глобальные функции - func GetFilePickerFiles(view View, subviewID string) []FileInfo + func GetFilePickerFiles(view View, subviewID ...string) []FileInfo func LoadFilePickerFile(view View, subviewID string, file FileInfo, result func(FileInfo, []byte)) Функции Files/GetFilePickerFiles возвращают список выбранных файлов в виде среза структур FileInfo. Структура FileInfo объявлена как @@ -3126,7 +3186,7 @@ FileInfo содержит только информацию о файле, но Получить текущий список слушателей изменения списка файлов можно с помощью функции - func GetFileSelectedListeners(view View, subviewID string) []func(FilePicker, []FileInfo) + func GetFileSelectedListeners(view View, subviewID ...string) []func(FilePicker, []FileInfo) ## DropDownList @@ -3141,13 +3201,13 @@ FileInfo содержит только информацию о файле, но * []string * []fmt.Stringer -* []interface{} содержащий в качестве элементов только: string, fmt.Stringer, bool, rune, +* []any содержащий в качестве элементов только: string, fmt.Stringer, bool, rune, float32, float64, int, int8…int64, uint, uint8…uint64. Все эти типы данных преопразуются в []string и присваиваются свойству "items". Прочитать значение свойства "items" можно с помощью функции - func GetDropDownItems(view View, subviewID string) []string + func GetDropDownItems(view View, subviewID ...string) []string Можно запретить выбор отдельных пунктов. Для этого используется свойство "disabled-items" (константа DisabledItems). Данному свойству присваивается массив индексов запрещенных пунктов. Индекс может задаваться или числом или в виде текста @@ -3157,17 +3217,17 @@ float32, float64, int, int8…int64, uint, uint8…uint64. * int * []string * string может содержать несколько индексов разделенных запятыми -* []interface{} содержащий в качестве элементов только: string, int, int8…int64, uint, uint8…uint64. +* []any содержащий в качестве элементов только: string, int, int8…int64, uint, uint8…uint64. -Все эти типы данных преопразуются в []interface{} и присваиваются свойству "disabled-items". +Все эти типы данных преопразуются в []any и присваиваются свойству "disabled-items". Прочитать значение свойства "disabled-items" можно с помощью функции - func GetDropDownDisabledItems(view View, subviewID string) []int + func GetDropDownDisabledItems(view View, subviewID ...string) []int Выбранное значение определяется int свойством "current" (константа Current). Значение по умолчанию 0. Прочитать значение данного свойства можно с помощью функции - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int Для отслеживания изменения свойства "current" используется событие "drop-down-event" (константа DropDownEvent). Основной слушатель события имеет следующий формат: @@ -3178,7 +3238,7 @@ DropDownEvent). Основной слушатель события имеет с Получить текущий список слушателей изменения даты можно с помощью функции - func GetDropDownListeners(view View, subviewID string) []func(DropDownList, int) + func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int) ## ProgressBar @@ -3199,8 +3259,8 @@ int8…int64, uint, uint8…uint64 Прочитать значение данных свойств можно с помощью функций - func GetProgressBarMax(view View, subviewID string) float64 - func GetProgressBarValue(view View, subviewID string) float64 + func GetProgressBarMax(view View, subviewID ...string) float64 + func GetProgressBarValue(view View, subviewID ...string) float64 ## Button @@ -3221,6 +3281,11 @@ int8…int64, uint, uint8…uint64 func NewListView(session Session, params Params) ListView +ListView реализован на основе ListLayout и поэтому он поддерживает все свойства ListLayout: +"orientation", "list-wrap", "vertical-align", "horizontal-align", "list-row-gap" и "list-column-gap". + +Помимо эти свойств ListView имеет ещё следующие: + ### Свойство "items" Элементы списка задаются с помощью свойства "items" (константа Items). Основным значением @@ -3248,13 +3313,13 @@ Cвойствy "items" могут быть присвоены следующие * ListAdapter; * []View при присваиваниии преобразуется в ListAdapter с помощью функции NewViewListAdapter; * []string при присваиваниии преобразуется в ListAdapter с помощью функции NewTextListAdapter; -* []interface{} который может содержать элементы типа View, string, fmt.Stringer, bool, rune, +* []any который может содержать элементы типа View, string, fmt.Stringer, bool, rune, float32, float64, int, int8…int64, uint, uint8…uint64. При присваиваниии все типы кроме View и string преобразуется в string, далее все string в TextView и из получившегося массива View с помощью функции NewViewListAdapter получается ListAdapter. Если элементы списка меняются в ходе работы, то после изменения необходимо вызывать или функцию -ReloadListViewData() интерфейса ListView или глобальную функцию ReloadListViewData(view View, subviewID string). +ReloadListViewData() интерфейса ListView или глобальную функцию ReloadListViewData(view View, subviewID ...string). Данные функции обновляют отображаемые элементы списка. ### Свойство "orientation" @@ -3276,7 +3341,7 @@ ReloadListViewData() интерфейса ListView или глобальную Получить значение данного свойства можно с помощью функции - func GetListOrientation(view View, subviewID string) int + func GetListOrientation(view View, subviewID ...string) int ### Свойство "wrap" @@ -3293,7 +3358,7 @@ ReloadListViewData() интерфейса ListView или глобальную Получить значение данного свойства можно с помощью функции - func GetListWrap(view View, subviewID string) int + func GetListWrap(view View, subviewID ...string) int ### Свойства "item-width" и "item-height" @@ -3306,8 +3371,8 @@ ReloadListViewData() интерфейса ListView или глобальную Получить значения данных свойств можно с помощью функций - func GetListItemWidth(view View, subviewID string) SizeUnit - func GetListItemHeight(view View, subviewID string) SizeUnit + func GetListItemWidth(view View, subviewID ...string) SizeUnit + func GetListItemHeight(view View, subviewID ...string) SizeUnit ### Свойство "item-vertical-align" @@ -3323,7 +3388,7 @@ ReloadListViewData() интерфейса ListView или глобальную Получить значение данного свойства можно с помощью функции - func GetListItemVerticalAlign(view View, subviewID string) int + func GetListItemVerticalAlign(view View, subviewID ...string) int ### Свойство "item-horizontal-align" @@ -3339,7 +3404,7 @@ ReloadListViewData() интерфейса ListView или глобальную Получить значение данного свойства можно с помощью функции - GetListItemHorizontalAlign(view View, subviewID string) int + GetListItemHorizontalAlign(view View, subviewID ...string) int ### Свойство "current" @@ -3350,7 +3415,7 @@ int свойство "current" (константа Current). Значение "c Получить значение данного свойства можно с помощью функции - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int ### Свойства "list-item-style", "current-style" и "current-inactive-style" @@ -3378,13 +3443,13 @@ int свойство "current" (константа Current). Значение "c Получить значение данного свойства можно с помощью функции - func GetListViewCheckbox(view View, subviewID string) int + func GetListViewCheckbox(view View, subviewID ...string) int Получить/установить список помеченных пунктов можно с помощью свойства "checked" (константа Checked). Данное свойство имеет тип []int и хранит индексы помеченных элементов. Получить значение данного свойства можно с помощью функции - func GetListViewCheckedItems(view View, subviewID string) []int + func GetListViewCheckedItems(view View, subviewID ...string) []int Проверить помечен ли конкретный элемент можно с помощью функции @@ -3415,8 +3480,8 @@ CheckboxHorizontalAlign и CheckboxVerticalAlign) Получить значения свойств можно "checkbox-horizontal-align" и "checkbox-vertical-align" с помощью функций - func GetListViewCheckboxHorizontalAlign(view View, subviewID string) int - func GetListViewCheckboxVerticalAlign(view View, subviewID string) int + func GetListViewCheckboxHorizontalAlign(view View, subviewID ...string) int + func GetListViewCheckboxVerticalAlign(view View, subviewID ...string) int ### События ListView @@ -3436,9 +3501,9 @@ CheckboxHorizontalAlign и CheckboxVerticalAlign) Получить списки слушателей данных событий можно с помощью функций: - func GetListItemClickedListeners(view View, subviewID string) []func(ListView, int) - func GetListItemSelectedListeners(view View, subviewID string) []func(ListView, int) - func GetListItemCheckedListeners(view View, subviewID string) []func(ListView, []int) + func GetListItemClickedListeners(view View, subviewID ...string) []func(ListView, int) + func GetListItemSelectedListeners(view View, subviewID ...string) []func(ListView, int) + func GetListItemCheckedListeners(view View, subviewID ...string) []func(ListView, []int) ## TableView @@ -3454,7 +3519,7 @@ CheckboxHorizontalAlign и CheckboxVerticalAlign) type TableAdapter interface { RowCount() int ColumnCount() int - Cell(row, column int) interface{} + Cell(row, column int) any } где функции RowCount() и ColumnCount() должны возврацать количество строк и столбцов в таблице; @@ -3474,10 +3539,10 @@ Cell(row, column int) возвращает содержимое ячейки т Свойству "content" можно также присваивать следующие типы данных * TableAdapter -* [][]interface{} +* [][]any * [][]string -[][]interface{} и [][]string при присвоении преобразуются к TableAdapter. +[][]any и [][]string при присвоении преобразуются к TableAdapter. ### Свойство "cell-style" @@ -3530,7 +3595,7 @@ Cell(row, column int) возвращает содержимое ячейки т | | | | |------|-------|--------| -Если в качестве значения свойства "content" используется [][]interface{}, то для объединения +Если в качестве значения свойства "content" используется [][]any, то для объединения ячеек используются пустые структуры type VerticalTableJoin struct { @@ -3541,7 +3606,7 @@ Cell(row, column int) возвращает содержимое ячейки т Данные структуры присоединяют ячейку, соответсвенно, к верхней/левой. Описание приведенной выше таблицы будет иметь следующий вид - content := [][]interface{} { + content := [][]any { {"", "", rui.HorizontalTableJoin{}}, {rui.VerticalTableJoin{}, "", ""}, } @@ -3635,7 +3700,7 @@ TableColumnStyle объявлена как Получить значение данного свойства можно с помощью функции - func GetTableVerticalAlign(view View, subviewID string) int + func GetTableVerticalAlign(view View, subviewID ...string) int ### Свойство "selection-mode" @@ -3657,7 +3722,7 @@ TableColumnStyle объявлена как Получить значение данного свойства можно с помощью функции - func GetSelectionMode(view View, subviewID string) int + func GetSelectionMode(view View, subviewID ...string) int ### Свойство "current" @@ -3675,7 +3740,7 @@ TableColumnStyle объявлена как Получить значение данного свойства можно с помощью функции - func GetTableCurrent(view View, subviewID string) CellIndex + func GetTableCurrent(view View, subviewID ...string) CellIndex ### Свойство "allow-selection" @@ -4349,6 +4414,42 @@ params - параметры всплавающего окна (может быт Заголовок также может иметь кнопку закрытия окна. Для ее добавления к заголовку используется свойство "close-button" типа bool. Установка этого свойства в "true" добавляет к заголовку кнопку закрытия окна (значение по умолчанию равно "false"). +### Стрелка Popup + +Всплывающее окно может иметь у одной из сторон стрелку. Стрелка задается с помощью свойства "arrow" (константа Arrow). +Cвойства "arrow" может принимать следующие значения + +| Значение | Константа | Расположение стрелки | +|:--------:|-------------|---------------------------------------------| +| 0 | NoneArrow | Нет стрелки (значение по умолчанию) | +| 1 | TopArrow | Стрелка у верхней стороны всплывающего окна | +| 2 | RightArrow | Стрелка у правой стороны всплывающего окна | +| 3 | BottomArrow | Стрелка у нижней стороны всплывающего окна | +| 4 | LeftArrow | Стрелка у левой стороны всплывающего окна | + +Размеры стрелки задаются с помощью свойств "arrow-size" (константа ArrowSize) и "arrow-width" (константа ArrowWidth) типа SizeUnit. +Они задают длину ("arrow-size") и ширину ("arrow-width") стрелки. Если данные свойтсва не заданы то используются константы +"@ruiArrowSize" (значение по умолчанию 16px) и "@ruiArrowWidth" (значение по умолчанию 16px). + +Выравнивание стрелки относительно всплывающего окна задается с помощью свойства "arrow-align" (константа ArrowAlign). +Cвойства "arrow-align" может принимать следующие значения + +| Значение | Константы | Выравнивание | +|:--------:|--------------------------|------------------------------------------------| +| 0 | TopAlign / LeftAlign | Выравнивание по верхнему / левому краю строны | +| 1 | BottomAlign / RightAlign | Выравнивание по нижнему / правому краю строны | +| 2 | CenterAlign | Выравнивание по центру (значение по умолчанию) | + +Также для стрелки можно задать дополнительное смещение. Для этого используется свойств "arrow-offset" (константа ArrowOffset) типа SizeUnit. + +Если значение свойства "arrow-align" равно TopAlign/LeftAlign, то смещение задается относительно верхней / левой стороны. +Если значение свойства "arrow-align" равно BottomAlign/RightAlign, то смещение задается относительно нижней / правой стороны. +Если значение свойства "arrow-align" равно CenterAlign, то смещение (может быть как положительным так и отрицательным) добавляется в виде отступа стрелки. +Т.е по центру выравнивается стрелка со смещением + +Если "arrow-offset" не задано, то значением по умолчанию для "arrow-align" равного CenterAlign является 0. +Для других значений "arrow-align" значением по умолчанию является соответствующий радиус скругления угла всплывающего окна. + ### Закрытие Popup Как было сказано выше, для закрытия всплавающего окна используется метод Dismiss() интерфейса Popup. @@ -4552,7 +4653,7 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете Однократная анимация запускается с помощью функции SetAnimated интерфейса View. Данная функция имеет следующее описание: - SetAnimated(tag string, value interface{}, animation Animation) bool + SetAnimated(tag string, value any, animation Animation) bool Она присваивает свойству новое значение, при этом изменение происходит с использованием заданной анимации. Например, @@ -4564,7 +4665,7 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете Есть также глобальная функция для анимированного однократного изменения значения свойства дочернего View - func SetAnimated(rootView View, viewID, tag string, value interface{}, animation Animation) bool + func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool Постоянная анимация запускается каждый раз когда изменяется значение свойства. Для задания постоянной анимации перехода используется свойство "transition" (константа Transition). В качества значения данному @@ -4587,7 +4688,7 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете Для получения текущего списка постоянных анимаций перехода используется функция - func GetTransition(view View, subviewID string) Params + func GetTransition(view View, subviewID ...string) Params Добавлять новые анимации перехода рекомендуется с помощью функции @@ -4624,10 +4725,10 @@ x1 и x2 должны быть в диапазоне [0, 1]. Вы можете Получить списки слушателей событий анимации перехода с помощью функций: - func GetTransitionRunListeners(view View, subviewID string) []func(View, string) - func GetTransitionStartListeners(view View, subviewID string) []func(View, string) - func GetTransitionEndListeners(view View, subviewID string) []func(View, string) - func GetTransitionCancelListeners(view View, subviewID string) []func(View, string) + func GetTransitionRunListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionStartListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionEndListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionCancelListeners(view View, subviewID ...string) []func(View, string) ### Cценарий анимации @@ -4641,8 +4742,8 @@ Cценарий анимации описывает более сложную а type AnimatedProperty struct { Tag string - From, To interface{} - KeyFrames map[int]interface{} + From, To any + KeyFrames map[int]any } где Tag - имя свойства, From - начальное значение свойства, To - конечное значение свойства, @@ -4756,10 +4857,10 @@ Safari и Firefox. Получить списки слушателей событий анимации с помощью функций: - func GetAnimationStartListeners(view View, subviewID string) []func(View, string) - func GetAnimationEndListeners(view View, subviewID string) []func(View, string) - func GetAnimationCancelListeners(view View, subviewID string) []func(View, string) - func GetAnimationIterationListeners(view View, subviewID string) []func(View, string) + func GetAnimationStartListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationEndListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationCancelListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationIterationListeners(view View, subviewID ...string) []func(View, string) ## Сессия @@ -4834,11 +4935,11 @@ Safari и Firefox. * SetTitleColor(color Color) устанавливает цвет панели навигации браузера. Поддерживается только в Safari и Chrome для Android. -* Get(viewID, tag string) interface{} - возвращает значение свойства View с именем tag. Эквивалентно +* Get(viewID, tag string) any - возвращает значение свойства View с именем tag. Эквивалентно rui.Get(session.RootView(), viewID, tag) -* Set(viewID, tag string, value interface{}) bool - устанавливает значение свойства View с именем tag. +* Set(viewID, tag string, value any) bool - устанавливает значение свойства View с именем tag. rui.Set(session.RootView(), viewID, tag, value) diff --git a/README.md b/README.md index dc0d816..3803ee2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[Russian](https://github.com/anoshenko/rui/blob/main/README-ru.md) + # RUI library The RUI (Remote User Interface) library is designed to create web applications in the go language. @@ -60,9 +62,10 @@ SizeUnit is declared as type SizeUnit struct { Type SizeUnitType Value float64 + Function SizeFunc } -where Type is the type of size; Value is the size. +where Type is the type of size; Value is the size; Function is function (used only if Type == SizeFunction, ignored otherwise) The Type can take the following values: @@ -79,12 +82,13 @@ The Type can take the following values: | 8 | SizeInMM | the Value field specifies the size in millimeters. | | 9 | SizeInCM | the Value field defines the size in centimeters. | | 10 | SizeInFraction | the Value field specifies the size in parts. Used only for sizing cells of the GridLayout. | +| 11 | SizeFunction | the Function field specifies a function for calculating the size. The Value field is ignored | For a more visual and simple setting of variables of the SizeUnit type, the functions below can be used. | Function | Equivalent definition | |----------------|----------------------------------------------------| -| rui.AutoSize() | rui.SizeUnit{ Type: rui.Auto, Value: 0 } | +| rui.AutoSize() | rui.SizeUnit{ Type: rui.Auto } | | rui.Px(n) | rui.SizeUnit{ Type: rui.SizeInPixel, Value: n } | | rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } | | rui.Ex(n) | rui.SizeUnit{ Type: rui.SizeInEX, Value: n } | @@ -98,8 +102,8 @@ For a more visual and simple setting of variables of the SizeUnit type, the func Variables of the SizeUnit type have a textual representation (why you need it will be described below). The textual representation consists of a number (equal to the value of the Value field) followed by -a suffix defining the type. An exception is a value of type Auto, which has the representation “auto”. -The suffixes are listed in the following table: +a suffix defining the type. +The exceptions are a value of type Auto, which has the representation "auto", and a value of type SizeFunction, which has a special representation. The suffixes are listed in the following table: | Suffix | Type | |:------:|----------------| @@ -122,6 +126,52 @@ To convert the textual representation to the SizeUnit structure, is used the fun You can get a textual representation of the structure using the String() function of SizeUnit structure +#### SizeFunc + +The SizeFunc interface is used to define a function that calculates SizeUnit. Let's consider functions using the min function as an example. + +The min function finds the minimum value among the given arguments. This function is specified using the MinSize function, declared as: + + func MinSize(arg0, arg1 any, args ...any) SizeFunc + +The function has 2 or more arguments, each of which can be either SizeUnit or SizeFunc or string which is a constant or +text representation of SizeUnit or SizeFunc. + +Examples + + rui.MizSize(rui.Percent(50), rui.Px(250)) + rui.MizSize("50%", rui.Px(250), "40em") + rui.MizSize(rui.Percent(50), "@a1") + +The min function has the following text representation + + "min(, , ...)" + +where arg1, arg2, ... must be a text representation of SizeUnit, or SizeFunc, or a constant. For example + + "min(50%, 250px)" + "min(75%, @a1)" + +The SizeFunc interface implements the fmt.Stringer interface. +The String() function of this interface returns the textual representation of SizeFunc. + +In addition to "min", there are the following functions + +| Text representation | Function to create | Description | +|------------------------------|--------------------------------------|----------------------------------------------------------| +| "min(, , ...)" | MaxSize(arg0, arg1 any, args ...any) | finds the minimum value among the arguments | +| "sum(, , ...)" | SumSize(arg0, arg1 any, args ...any) | calculates the sum of the argument values | +| "sub(, )" | SubSize(arg0, arg1 any) | calculates the subtraction of argument values | +| "mul(, )" | MulSize(arg0, arg1 any) | calculates the result of multiplying the argument values | +| "div(, )" | DivSize(arg0, arg1 any) | calculates the result of dividing the argument values | +| "clamp(, , )" | ClampSize(min, val, max any) | limits value to specified range | + +Additional explanations for the function "clamp(, , )": the result is calculated as follows: + +* if min ≤ val ≤ max then val; +* if val < min then min; +* if max < val then max; + ### Color The Color type describes a 32-bit ARGB color: @@ -362,8 +412,8 @@ 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): type Properties interface { - Get(tag string) interface{} - Set(tag string, value interface{}) bool + Get(tag string) any + Set(tag string, value any) bool Remove(tag string) Clear() AllTags() []string @@ -378,8 +428,8 @@ The Remove function removes property value, equivalent to Set(nil) To simplify setting / reading properties, there are also two global functions Get and Set: - func Get(rootView View, viewID, tag string) interface{} - func Set(rootView View, viewID, tag string, value interface{}) bool + func Get(rootView View, viewID, tag string) any + func Set(rootView View, viewID, tag string, value any) bool These functions get/set the value of the child View @@ -441,7 +491,7 @@ Each event can have multiple listeners. In this regard, five data types can be u * func([< parameters>]) * []func(< View >[, < parameters >]) * []func([< parameters >]) -* []interface{} which only contains func(< View >[, < parameters >]) and func([< parameters >]) +* []any which only contains func(< View >[, < parameters >]) and func([< parameters >]) After being assigned to a property, all these types are converted to an array of []func(< View >, [< parameters >]). Accordingly, the Get function always returns an array of []func(< View >, [< parameters >]). @@ -453,7 +503,7 @@ For the "edit-text-changed" event, this * func(newText string) * []func(editor EditView, newText string) * []func(newText string) -* []interface{} содержащий только func(editor EditView, newText string) и func(newText string) +* []any содержащий только func(editor EditView, newText string) и func(newText string) And the "edit-text-changed" property always stores and returns []func(EditView, string). @@ -538,17 +588,17 @@ After getting the value with the Get function, you must typecast: This is quite cumbersome, therefore for each property there is a global function of the same name with the Get prefix, which performs the given cast, gets the value of the constant, if necessary, and returns it. -All functions of this type have two arguments: View and subviewID string. +All functions of this type have two arguments: View and subviewID ...string. The first argument is the root View, the second is the ID of the child View. -If the ID of the child View is passed as "", then the value of the root View is returned. +If the ID of the child View is not specified or is passed as "", then the value of the root View is returned. For the properties "width", "height", "min-width", "min-height", "max-width", "max-height" these are functions: - func GetWidth(view View, subviewID string) SizeUnit - func GetHeight(view View, subviewID string) SizeUnit - func GetMinWidth(view View, subviewID string) SizeUnit - func GetMinHeight(view View, subviewID string) SizeUnit - func GetMaxWidth(view View, subviewID string) SizeUnit - func GetMaxHeight(view View, subviewID string) SizeUnit + func GetWidth(view View, subviewID ...string) SizeUnit + func GetHeight(view View, subviewID ...string) SizeUnit + func GetMinWidth(view View, subviewID ...string) SizeUnit + func GetMinHeight(view View, subviewID ...string) SizeUnit + func GetMaxWidth(view View, subviewID ...string) SizeUnit + func GetMaxHeight(view View, subviewID ...string) SizeUnit ### "resize" property @@ -567,7 +617,7 @@ The default value for a multiline text editor is BothResize(1). You can get the value of this property using the function - func GetResize(view View, subviewID string) int + func GetResize(view View, subviewID ...string) int ### "margin" and "padding" properties @@ -607,8 +657,8 @@ can be converted to a more convenient Bounds structure: Global functions can also be used for this: - func GetMargin(view View, subviewID string) Bounds - func GetPadding(view View, subviewID string) Bounds + func GetMargin(view View, subviewID ...string) Bounds + func GetPadding(view View, subviewID ...string) Bounds The textual representation of the BoundsProperty is as follows: @@ -742,7 +792,7 @@ This converts the ViewBorders to BorderProperty. Therefore, when the property is the Get function will return the BorderProperty interface, not the ViewBorders structure. You can get the ViewBorders structure without additional transformations using the global function - func GetBorder(view View, subviewID string) ViewBorders + func GetBorder(view View, subviewID ...string) ViewBorders Besides the auxiliary properties "style", "width" and "color" there are 4 more: "left", "right", "top" and "bottom". As a value, these properties can only take the ViewBorder structure and allow you to set all the attributes of the line of the side of the same name. @@ -878,7 +928,7 @@ This converts BoxRadius to RadiusProperty. Therefore, when the property is read, the Get function will return the RadiusProperty interface, not the BoxRadius structure. You can get the BoxRadius structure without additional transformations using the global function - func GetRadius(view View, subviewID string) BoxRadius + func GetRadius(view View, subviewID ...string) BoxRadius You can also set individual radii using the Set function of the View interface. For this, the following properties are used @@ -953,7 +1003,7 @@ The ViewShadow text representation has the following format: You can get the value of "shadow" property using the function - func GetViewShadows(view View, subviewID string) []ViewShadow + func GetViewShadows(view View, subviewID ...string) []ViewShadow If no shadow is specified, then this function will return an empty array @@ -1016,7 +1066,7 @@ Optional parameter. The default direction is from bottom to top. It can be eithe Each point is described by a BackgroundGradientPoint structure, which has two fields: Pos of type SizeUnit and Color. Pos defines the position of the point relative to the start of the gradient line. The array must have at least 2 points. You can also pass a Color array as the gradient value. In this case, the points are evenly distributed along the gradient line. -You can also use an array of []interface{} as an array of cue points. +You can also use an array of []any as an array of cue points. The elements of this array can be BackgroundGradientPoint, Color, BackgroundGradientPoint or Color text representation, and the name of the constant * Repeating ("repeating") - a boolean value that determines whether the gradient will repeat after the last key point. @@ -1081,8 +1131,8 @@ The radial gradient has the following options: Each key angle is described by a BackgroundGradientAngle structure: type BackgroundGradientAngle struct { - Color interface{} - Angle interface{} + Color any + Angle any } where Color specifies the color of the key corner and can take a value of Color type or @@ -1194,7 +1244,7 @@ The textual description of the elliptical clipping region is in the following fo Polygonal cropping area. Created using functions: - func PolygonClip(points []interface{}) ClipShape + func PolygonClip(points []any) ClipShape func PolygonPointsClip(points []SizeUnit) ClipShape an array of corner points of the polygon is passed as an argument in the following order: x1, y1, x2, y2, … @@ -1212,7 +1262,7 @@ Where 1 - View is fully opaque, 0 - fully transparent. You can get the value of this property using the function - func GetOpacity(view View, subviewID string) float64 + func GetOpacity(view View, subviewID ...string) float64 ### "z-index" property @@ -1222,7 +1272,7 @@ higher z-indexes overlap elements with lower. You can get the value of this property using the function - func GetZIndex(view View, subviewID string) int + func GetZIndex(view View, subviewID ...string) int ### "visibility" property @@ -1236,7 +1286,7 @@ The "visibility" int property (constant Visibility) specifies the visibility of You can get the value of this property using the function - func GetVisibility(view View, subviewID string) int + func GetVisibility(view View, subviewID ...string) int ### "filter" and "backdrop-filter" properties @@ -1272,8 +1322,8 @@ Example You can get the value of the current filter using functions - func GetFilter(view View, subviewID string) ViewFilter - func GetBackdropFilter(view View, subviewID string) ViewFilter + func GetFilter(view View, subviewID ...string) ViewFilter + func GetBackdropFilter(view View, subviewID ...string) ViewFilter ### "semantics" property @@ -1320,7 +1370,7 @@ if it is not available, then the second, third, etc. You can get the value of this property using the function - func GetFontName(view View, subviewID string) string + func GetFontName(view View, subviewID ...string) string #### "text-color" property @@ -1328,7 +1378,7 @@ Property "text-color" (constant TextColor) - the Color property determines the c You can get the value of this property using the function - func GetTextColor(view View, subviewID string) Color + func GetTextColor(view View, subviewID ...string) Color #### "text-size" property @@ -1336,7 +1386,7 @@ Property "text-size" (constant TextSize) - the SizeUnit property determines the You can get the value of this property using the function - func GetTextSize(view View, subviewID string) SizeUnit + func GetTextSize(view View, subviewID ...string) SizeUnit #### "italic" property @@ -1344,7 +1394,7 @@ The "italic" property (constant Italic) is the bool property. If the value is tr You can get the value of this property using the function - func IsItalic(view View, subviewID string) bool + func IsItalic(view View, subviewID ...string) bool #### "small-caps" property @@ -1352,7 +1402,7 @@ The "small-caps" property (SmallCaps constant) is the bool property. If the valu You can get the value of this property using the function - func IsSmallCaps(view View, subviewID string) bool + func IsSmallCaps(view View, subviewID ...string) bool #### "white-space" property @@ -1391,20 +1441,25 @@ The table below shows the behavior of various values of the "white-space" proper | WhiteSpacePreLine | Preserve | Collapse | Wrap | Remove | Hang | | WhiteSpaceBreakSpaces | Preserve | Preserve | Wrap | Wrap | Wrap | +#### "tab-size" property + +The "tab-size" int property (TabSize constant) specifies the size of the tab character (U+0009) in spaces. +The value of the "tab-size" property must be greater than 0. The default value is 8 + #### "word-break" property The "word-break" int property (WordBreak constant) determines where the newline will be set if the text exceeds the block boundaries. The "white-space" property can take the following values: -0 (constant WordBreak, name "normal) - default behavior for linefeed placement. +0 (WordBreak constant, "normal" name) - default behavior for linefeed placement. -1 (constant WordBreakAll, name "break-all) - if the block boundaries are exceeded, +1 (WordBreakAll constant, "break-all" name) - if the block boundaries are exceeded, a line break will be inserted between any two characters (except for Chinese/Japanese/Korean text). -2 (constant WordBreakKeepAll, name "keep-all) - Line break will not be used in Chinese/Japanese/ Korean text. +2 (WordBreakKeepAll constant, "keep-all" name) - Line break will not be used in Chinese/Japanese/ Korean text. For text in other languages, the default behavior (normal) will be applied. -3 (constant WordBreakWord, name "break-word) - when the block boundaries are exceeded, +3 (WordBreakWord constant, "break-word" name) - when the block boundaries are exceeded, the remaining whole words can be broken in an arbitrary place, if a more suitable place for line break is not found. #### "strikethrough", "overline", "underline" properties @@ -1419,9 +1474,9 @@ These bool properties set decorative lines on the text: You can get the value of these properties using the functions - func IsStrikethrough(view View, subviewID string) bool - func IsOverline(view View, subviewID string) bool - func IsUnderline(view View, subviewID string) bool + func IsStrikethrough(view View, subviewID ...string) bool + func IsOverline(view View, subviewID ...string) bool + func IsUnderline(view View, subviewID ...string) bool #### "text-line-thickness" property @@ -1430,7 +1485,7 @@ of decorative lines on the text set using the "strikethrough", "overline" and "u You can get the value of this property using the function - GetTextLineThickness(view View, subviewID string) SizeUnit + GetTextLineThickness(view View, subviewID ...string) SizeUnit #### "text-line-style" property @@ -1449,7 +1504,7 @@ Possible values are: You can get the value of this property using the function - func GetTextLineStyle(view View, subviewID string) int + func GetTextLineStyle(view View, subviewID ...string) int #### "text-line-color" property @@ -1459,7 +1514,7 @@ If the property is not defined, then the text color specified by the "text-color You can get the value of this property using the function - func GetTextLineColor(view View, subviewID string) Color + func GetTextLineColor(view View, subviewID ...string) Color #### "text-weight" property @@ -1481,7 +1536,7 @@ Some fonts are only available in normal or bold style. In this case, the value o You can get the value of this property using the function - func GetTextWeight(view View, subviewID string) int + func GetTextWeight(view View, subviewID ...string) int #### "text-shadow" property @@ -1506,7 +1561,7 @@ ViewShadow, ViewShadow array, ViewShadow textual representation can be assigned You can get the value of this property using the function - func GetTextShadows(view View, subviewID string) []ViewShadow + func GetTextShadows(view View, subviewID ...string) []ViewShadow If no shadow is specified, then this function will return an empty array @@ -1523,7 +1578,7 @@ The "text-align" int property (constant TextAlign) sets the alignment of the tex You can get the value of this property using the function - func GetTextAlign(view View, subviewID string) int + func GetTextAlign(view View, subviewID ...string) int #### "text-indent" property @@ -1532,7 +1587,7 @@ before the first line of text. You can get the value of this property using the function - func GetTextIndent(view View, subviewID string) SizeUnit + func GetTextIndent(view View, subviewID ...string) SizeUnit #### "letter-spacing" property @@ -1542,7 +1597,7 @@ The user agent can choose not to increase or decrease the letter spacing to alig You can get the value of this property using the function - func GetLetterSpacing(view View, subviewID string) SizeUnit + func GetLetterSpacing(view View, subviewID ...string) SizeUnit #### "word-spacing" property @@ -1552,7 +1607,7 @@ Otherwise, it specifies additional spacing in addition to the inner word spacing You can get the value of this property using the function - func GetWordSpacing(view View, subviewID string) SizeUnit + func GetWordSpacing(view View, subviewID ...string) SizeUnit #### "line-height" property @@ -1560,7 +1615,7 @@ The "line-height" (LineHeight constant) SizeUnit property sets the amount of spa You can get the value of this property using the function - func GetLineHeight(view View, subviewID string) SizeUnit + func GetLineHeight(view View, subviewID ...string) SizeUnit #### "text-transform" property @@ -1575,7 +1630,7 @@ The "text-transform" (TextTransform constant) int property defines the case of c You can get the value of this property using the function - func GetTextTransform(view View, subviewID string) int + func GetTextTransform(view View, subviewID ...string) int #### "text-direction" property @@ -1589,7 +1644,7 @@ The "text-direction" (TextDirection constant) int property determines the direct You can get the value of this property using the function - func GetTextDirection(view View, subviewID string) int + func GetTextDirection(view View, subviewID ...string) int #### "writing-mode" property The "writing-mode" (WritingMode constant) int property defines how the lines of text are arranged @@ -1605,7 +1660,7 @@ Possible values are: You can get the value of this property using the function - func GetWritingMode(view View, subviewID string) int + func GetWritingMode(view View, subviewID ...string) int #### "vertical-text-orientation" property @@ -1620,7 +1675,7 @@ Possible values are: You can get the value of this property using the function - func GetVerticalTextOrientation(view View, subviewID string) int + func GetVerticalTextOrientation(view View, subviewID ...string) int #### "user-select" property @@ -1636,7 +1691,7 @@ it will also apply to all child elements You can get the value of this property using the function - func IsUserSelect(view View, subviewID string) bool + func IsUserSelect(view View, subviewID ...string) bool ### Transformation properties @@ -1654,7 +1709,7 @@ The vanishing point is by default located in the center of the element, but it c You can get the value of this property using the function - func GetPerspective(view View, subviewID string) SizeUnit + func GetPerspective(view View, subviewID ...string) SizeUnit #### "perspective-origin-x" and "perspective-origin-y" properties @@ -1665,7 +1720,7 @@ By default, the "perspective-origin-x" and "perspective-origin-y" properties are You can get the value of these properties using the function - func GetPerspectiveOrigin(view View, subviewID string) (SizeUnit, SizeUnit) + func GetPerspectiveOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit) #### "backface-visibility" property @@ -1678,7 +1733,7 @@ the back face can be visible when the transformation causes the element to rotat You can get the value of this property using the function - func GetBackfaceVisible(view View, subviewID string) bool + func GetBackfaceVisible(view View, subviewID ...string) bool #### "origin-x", "origin-y", and "origin-z" properties @@ -1690,7 +1745,7 @@ The "origin-z" property is ignored if the perspective property is not set. You can get the value of these properties using the function - func GetOrigin(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) + func GetOrigin(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) #### "translate-x", "translate-y", and "translate-z" properties @@ -1701,7 +1756,7 @@ The translate-z property is ignored if the perspective property is not set. You can get the value of these properties using the function - func GetTranslate(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) + func GetTranslate(view View, subviewID ...string) (SizeUnit, SizeUnit, SizeUnit) #### "scale-x", "scale-y" and "scale-z" properties @@ -1714,7 +1769,7 @@ The "scale-z" property is ignored if the "perspective" property is not set. You can get the value of these properties using the function - func GetScale(view View, subviewID string) (float64, float64, float64) + func GetScale(view View, subviewID ...string) (float64, float64, float64) #### "rotate" property @@ -1731,7 +1786,7 @@ The "rotate-z" property is ignored if the "perspective" property is not set. You can get the value of these properties, as well as the "rotate" property, using the function - func GetRotate(view View, subviewID string) (float64, float64, float64, AngleUnit) + func GetRotate(view View, subviewID ...string) (float64, float64, float64, AngleUnit) #### "skew-x" and "skew-y" properties @@ -1741,7 +1796,7 @@ specified by the transform-origin-x and transform-origin-y properties. You can get the value of these properties using the function - func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit) + func GetSkew(view View, subviewID ...string) (AngleUnit, AngleUnit) ### User data @@ -1781,8 +1836,8 @@ You can also use listeners in the following formats: You can get lists of listeners for keyboard events using the functions: - func GetKeyDownListeners(view View, subviewID string) []func(View, KeyEvent) - func GetKeyUpListeners(view View, subviewID string) []func(View, KeyEvent) + func GetKeyDownListeners(view View, subviewID ...string) []func(View, KeyEvent) + func GetKeyUpListeners(view View, subviewID ...string) []func(View, KeyEvent) ### Focus events @@ -1803,8 +1858,8 @@ You can also use a listener in the following format: You can get lists of listeners for focus events using the functions: - func GetFocusListeners(view View, subviewID string) []func(View) - func GetLostFocusListeners(view View, subviewID string) []func(View) + func GetFocusListeners(view View, subviewID ...string) []func(View) + func GetLostFocusListeners(view View, subviewID ...string) []func(View) ### Mouse events @@ -1872,14 +1927,14 @@ You can also use listeners in the following formats: You can get lists of listeners for mouse events using the functions: - func GetMouseDownListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseUpListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseMoveListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseOverListeners(view View, subviewID string) []func(View, MouseEvent) - func GetMouseOutListeners(view View, subviewID string) []func(View, MouseEvent) - func GetClickListeners(view View, subviewID string) []func(View, MouseEvent) - func GetDoubleClickListeners(view View, subviewID string) []func(View, MouseEvent) - func GetContextMenuListeners(view View, subviewID string) []func(View, MouseEvent) + func GetMouseDownListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseUpListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseMoveListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseOverListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetMouseOutListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetClickListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetDoubleClickListeners(view View, subviewID ...string) []func(View, MouseEvent) + func GetContextMenuListeners(view View, subviewID ...string) []func(View, MouseEvent) ### Pointer Events @@ -1926,12 +1981,12 @@ You can also use listeners in the following formats: You can get lists of pointer event listeners using the functions: - func GetPointerDownListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerUpListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerMoveListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerCancelListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerOverListeners(view View, subviewID string) []func(View, PointerEvent) - func GetPointerOutListeners(view View, subviewID string) []func(View, PointerEvent) + func GetPointerDownListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerUpListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerMoveListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerCancelListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerOverListeners(view View, subviewID ...string) []func(View, PointerEvent) + func GetPointerOutListeners(view View, subviewID ...string) []func(View, PointerEvent) ### Touch events @@ -1984,10 +2039,10 @@ You can also use listeners in the following formats: You can get lists of listeners for touch events using the functions: - func GetTouchStartListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchEndListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchMoveListeners(view View, subviewID string) []func(View, TouchEvent) - func GetTouchCancelListeners(view View, subviewID string) []func(View, TouchEvent) + func GetTouchStartListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchEndListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchMoveListeners(view View, subviewID ...string) []func(View, TouchEvent) + func GetTouchCancelListeners(view View, subviewID ...string) []func(View, TouchEvent) ### Resize-event @@ -2016,7 +2071,7 @@ You can also use listeners in the following formats: You can get a list of listeners for this event using the function: - func GetResizeListeners(view View, subviewID string) []func(View, Frame) + func GetResizeListeners(view View, subviewID ...string) []func(View, Frame) The current position and dimensions of the visible part of the View can be obtained using the View interface function: @@ -2024,7 +2079,7 @@ The current position and dimensions of the visible part of the View can be obtai or global function - func GetViewFrame(view View, subviewID string) Frame + func GetViewFrame(view View, subviewID ...string) Frame ### Scroll event @@ -2055,13 +2110,13 @@ The current position of the viewable area and the overall dimensions of the View or global function - func GetViewScroll(view View, subviewID string) Frame + func GetViewScroll(view View, subviewID ...string) Frame The following global functions can be used for manual scrolling func ScrollViewTo(view View, subviewID string, x, y float64) - func ScrollViewToStart(view View, subviewID string) - func ScrollViewToEnd(view View, subviewID string) + func ScrollViewToStart(view View, subviewID ...string) + func ScrollViewToEnd(view View, subviewID ...string) which scroll the view, respectively, to the given position, start and end @@ -2091,7 +2146,7 @@ Next, a []View is created containing the resulting View; * []string - each element of the array is converted to View as described in the previous paragraph; -* []interface{} - this array must contain only View and string. Each string element is converted to +* []any - this array must contain only View and string. Each string element is converted to a View as described above. If the array contains invalid values, the "content" property will not be set, and the Set function will return false and an error message will be written to the log. @@ -2178,6 +2233,11 @@ alignment of items in the list. Valid values: | 2 | CenterAlign | "center" | Center alignment | | 3 | StretchAlign | "stretch" | Width alignment | +### "list-row-gap" and "list-column-gap" properties + +The "list-row-gap" and "list-column-gap" SizeUnit properties (ListRowGap and ListColumnGap constants) +allow you to set the distance between the rows and columns of the container, respectively. The default is 0px. + ## GridLayout GridLayout is a container that implements the ViewsContainer interface. To create it, use the function @@ -2330,7 +2390,7 @@ on the "text-direction" property to the right or left of the previous one, and t You can get the value of this property using the function - func GetColumnCount(view View, subviewID string) int + func GetColumnCount(view View, subviewID ...string) int ### "column-width" property @@ -2341,7 +2401,7 @@ IMPORTANT! Percentages cannot be used as the "column-width" value (i.e. if you s You can get the value of this property using the function - func GetColumnWidth(view View, subviewID string) SizeUnit + func GetColumnWidth(view View, subviewID ...string) SizeUnit ### "column-gap" property @@ -2349,7 +2409,7 @@ The "column-gap" SizeUnit property (ColumnGap constant) sets the width of the ga You can get the value of this property using the function - func GetColumnGap(view View, subviewID string) SizeUnit + func GetColumnGap(view View, subviewID ...string) SizeUnit ### "column-separator" property @@ -2397,7 +2457,7 @@ not the ViewBorder structure. You can get the ViewBorders structure without additional transformations using the global function - func GetColumnSeparator(view View, subviewID string) ViewBorder + func GetColumnSeparator(view View, subviewID ...string) ViewBorder You can also set individual line attributes using the Set function of the View interface. For this, the following properties are used @@ -2435,7 +2495,7 @@ The default is "false". You can get the value of this property using the function - func GetAvoidBreak(view View, subviewID string) bool + func GetAvoidBreak(view View, subviewID ...string) bool ## StackLayout @@ -2481,7 +2541,7 @@ Example func peek(layout rui.StackLayout) { views := layout.Views() - if index := rui.GetCurrent(layout, ""); index >= 0 && index < len(views) { + if index := rui.GetCurrent(layout); index >= 0 && index < len(views) { return views[index] } return nil @@ -2549,7 +2609,7 @@ You can control the current View using the "current" integer property (constant To programmatically switch tabs, set this property to the index of the new current View. You can read the value of the "current" property using the function - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int Also, the "current" property can be used to track changes to the current View: @@ -2622,11 +2682,11 @@ Accordingly, the value "true" shows child Views, "false" - hides. You can get the value of the "expanded" property using the function - func IsDetailsExpanded(view View, subviewID string) bool + func IsDetailsExpanded(view View, subviewID ...string) bool 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 ## Resizable @@ -2671,7 +2731,7 @@ To create a TextView, the function is used: The displayed text is set by the string property "text" (Text constant). In addition to the Get method, the value of the "text" property can be obtained using the function - func GetText (view View, subviewID string) string + func GetText (view View, subviewID ...string) string TextView inherits from View all properties of text parameters ("font-name", "text-size", "text-color", etc.). In addition to them, the "text-overflow" int property (TextOverflow constant) is added. @@ -2752,11 +2812,11 @@ relative to the bounds of the ImageView. Valid values: The following functions can be used to retrieve ImageView property values: - func GetImageViewSource(view View, subviewID string) string - func GetImageViewAltText(view View, subviewID string) string - func GetImageViewFit(view View, subviewID string) int - func GetImageViewVerticalAlign(view View, subviewID string) int - func GetImageViewHorizontalAlign(view View, subviewID string) int + func GetImageViewSource(view View, subviewID ...string) string + func GetImageViewAltText(view View, subviewID ...string) string + func GetImageViewFit(view View, subviewID ...string) int + func GetImageViewVerticalAlign(view View, subviewID ...string) int + func GetImageViewHorizontalAlign(view View, subviewID ...string) int ## EditView @@ -2809,15 +2869,15 @@ In this case, the color of the caret changes for all child EditViews placed in t The following functions can be used to get the values of the properties of an EditView: - func GetText(view View, subviewID string) string - func GetHint(view View, subviewID string) string - func GetMaxLength(view View, subviewID string) int - func GetEditViewType(view View, subviewID string) int - func GetEditViewPattern(view View, subviewID string) string - func IsReadOnly(view View, subviewID string) bool - func IsEditViewWrap(view View, subviewID string) bool - func IsSpellcheck(view View, subviewID string) bool - func GetCaretColor(view View, subviewID string) Color + func GetText(view View, subviewID ...string) string + func GetHint(view View, subviewID ...string) string + func GetMaxLength(view View, subviewID ...string) int + func GetEditViewType(view View, subviewID ...string) int + func GetEditViewPattern(view View, subviewID ...string) string + func IsReadOnly(view View, subviewID ...string) bool + func IsEditViewWrap(view View, subviewID ...string) bool + func IsSpellcheck(view View, subviewID ...string) bool + func GetCaretColor(view View, subviewID ...string) Color The "edit-text-changed" event (EditTextChangedEvent constant) is used to track changes to the text. The main event listener has the following format: @@ -2828,7 +2888,7 @@ where the second argument is the new text value You can get the current list of text change listeners using the function - func GetTextChangedListeners(view View, subviewID string) []func(EditView, string) + func GetTextChangedListeners(view View, subviewID ...string) []func(EditView, string) ## NumberPicker @@ -2861,7 +2921,7 @@ The following can be passed as a value to the "number-picker-value" property: All of these types are cast to float64. Accordingly, the Get function always returns a float64 value. The value of the "number-picker-value" property can also be read using the function: - func GetNumberPickerValue(view View, subviewID string) float64 + func GetNumberPickerValue(view View, subviewID ...string) float64 The entered values may be subject to restrictions. For this, the following properties are used: @@ -2878,8 +2938,8 @@ If "number-picker-type" is equal to NumberEditor, then the entered numbers, by d You can read the values of these properties using the functions: - func GetNumberPickerMinMax(view View, subviewID string) (float64, float64) - func GetNumberPickerStep(view View, subviewID string) float64 + func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) + func GetNumberPickerStep(view View, subviewID ...string) float64 The "number-changed" event (NumberChangedEvent constant) is used to track the change in the entered value. The main event listener has the following format: @@ -2890,7 +2950,7 @@ where the second argument is the new value You can get the current list of value change listeners using the function - func GetNumberChangedListeners(view View, subviewID string) []func(NumberPicker, float64) + func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64) ## DatePicker @@ -2912,7 +2972,7 @@ The following can be passed as a value to the "date-picker-value" property: The text is converted to time.Time. Accordingly, the Get function always returns a time.Time value. The value of the "date-picker-value" property can also be read using the function: - func GetDatePickerValue(view View, subviewID string) time.Time + func GetDatePickerValue(view View, subviewID ...string) time.Time The dates you enter may be subject to restrictions. For this, the following properties are used: @@ -2924,9 +2984,9 @@ The dates you enter may be subject to restrictions. For this, the following prop You can read the values of these properties using the functions: - func GetDatePickerMin(view View, subviewID string) (time.Time, bool) - func GetDatePickerMax(view View, subviewID string) (time.Time, bool) - func GetDatePickerStep(view View, subviewID string) int + func GetDatePickerMin(view View, subviewID ...string) (time.Time, bool) + func GetDatePickerMax(view View, subviewID ...string) (time.Time, bool) + func GetDatePickerStep(view View, subviewID ...string) int The "date-changed" event (DateChangedEvent constant) is used to track the change in the entered value. The main event listener has the following format: @@ -2937,7 +2997,7 @@ where the second argument is the new date value You can get the current list of date change listeners using the function - func GetDateChangedListeners(view View, subviewID string) []func(DatePicker, time.Time) + func GetDateChangedListeners(view View, subviewID ...string) []func(DatePicker, time.Time) ## TimePicker @@ -2959,7 +3019,7 @@ The following can be passed as a value to the "time-picker-value" property: The text is converted to time.Time. Accordingly, the Get function always returns a time.Time value. The value of the "time-picker-value" property can also be read using the function: - func GetTimePickerValue(view View, subviewID string) time.Time + func GetTimePickerValue(view View, subviewID ...string) time.Time The time entered may be subject to restrictions. For this, the following properties are used: @@ -2971,9 +3031,9 @@ The time entered may be subject to restrictions. For this, the following propert You can read the values of these properties using the functions: - func GetTimePickerMin(view View, subviewID string) (time.Time, bool) - func GetTimePickerMax(view View, subviewID string) (time.Time, bool) - func GetTimePickerStep(view View, subviewID string) int + func GetTimePickerMin(view View, subviewID ...string) (time.Time, bool) + func GetTimePickerMax(view View, subviewID ...string) (time.Time, bool) + func GetTimePickerStep(view View, subviewID ...string) int The "time-changed" event (TimeChangedEvent constant) is used to track the change in the entered value. The main event listener has the following format: @@ -2984,7 +3044,7 @@ where the second argument is the new time value You can get the current list of date change listeners using the function - func GetTimeChangedListeners(view View, subviewID string) []func(TimePicker, time.Time) + func GetTimeChangedListeners(view View, subviewID ...string) []func(TimePicker, time.Time) ## ColorPicker @@ -3003,7 +3063,7 @@ The following can be passed as a value to the "color-picker-value" property: The value of the property "color-picker-value" can also be read using the function: - func GetColorPickerValue(view View, subviewID string) Color + func GetColorPickerValue(view View, subviewID ...string) Color The "color-changed" event (ColorChangedEvent constant) is used to track the change in the selected color. The main event listener has the following format: @@ -3014,7 +3074,7 @@ where the second argument is the new color value You can get the current list of date change listeners using the function - func GetColorChangedListeners(view View, subviewID string) []func(ColorPicker, Color) + func GetColorChangedListeners(view View, subviewID ...string) []func(ColorPicker, Color) ## FilePicker @@ -3043,7 +3103,7 @@ Two functions of the FilePicker interface are used to access the selected files: as well as the corresponding global functions - func GetFilePickerFiles(view View, subviewID string) []FileInfo + func GetFilePickerFiles(view View, subviewID ...string) []FileInfo func LoadFilePickerFile(view View, subviewID string, file FileInfo, result func(FileInfo, []byte)) The Files/GetFilePickerFiles functions return a list of the selected files as a slice of FileInfo structures. @@ -3097,7 +3157,7 @@ where the second argument is the new value of the list of selected files. You can get the current list of listeners of the list of files changing using the function - func GetFileSelectedListeners(view View, subviewID string) []func(FilePicker, []FileInfo) + func GetFileSelectedListeners(view View, subviewID ...string) []func(FilePicker, []FileInfo) ## DropDownList @@ -3112,13 +3172,13 @@ The following data types can be passed as a value to the "items" property * []string * []fmt.Stringer -* []interface{} containing as elements only: string, fmt.Stringer, bool, rune, +* []any containing as elements only: string, fmt.Stringer, bool, rune, float32, float64, int, int8 … int64, uint, uint8 … uint64. All of these data types are converted to []string and assigned to the "items" property. You can read the value of the "items" property using the function - func GetDropDownItems(view View, subviewID string) []string + func GetDropDownItems(view View, subviewID ...string) []string You can disable the selection of individual items. For this, the "disabled-items" property (constant DisabledItems) is used. This property is assigned an array of disabled item indices. The index can be specified either as a number, as text, or as a constant. Therefore, the following data types can be assigned to the "disabled-items" property: @@ -3127,17 +3187,17 @@ This property is assigned an array of disabled item indices. The index can be sp * int * []string * string - can contain multiple indexes separated by commas -* []interface{} - containing as elements only: string, int, int8…int64, uint, uint8…uint64. +* []any - containing as elements only: string, int, int8…int64, uint, uint8…uint64. -All of these data types are converted to []interface{} and assigned to the "disabled-items" property. +All of these data types are converted to []any and assigned to the "disabled-items" property. You can read the value of the "disabled-items" property using the function - func GetDropDownDisabledItems(view View, subviewID string) []int + func GetDropDownDisabledItems(view View, subviewID ...string) []int The selected value is determined by the int property "current" (Current constant). The default is 0. You can read the value of this property using the function - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int To track the change of the "current" property, the "drop-down-event" event (DropDownEvent constant) is used. The main event listener has the following format: @@ -3148,7 +3208,7 @@ where the second argument is the index of the selected item You can get the current list of date change listeners using the function - func GetDropDownListeners(view View, subviewID string) []func(DropDownList, int) + func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int) ## ProgressBar @@ -3167,8 +3227,8 @@ In addition to float64, float32, int, int8 … int64, uint, uint8 … uint64 You can read the value of these properties using the functions - func GetProgressBarMax(view View, subviewID string) float64 - func GetProgressBarValue(view View, subviewID string) float64 + func GetProgressBarMax(view View, subviewID ...string) float64 + func GetProgressBarValue(view View, subviewID ...string) float64 ## Button @@ -3188,6 +3248,11 @@ The ListView is created using the function: func NewListView(session Session, params Params) ListView +ListView is implemented on top of ListLayout and therefore supports all ListLayout properties: +"orientation", "list-wrap", "vertical-align", "horizontal-align", "list-row-gap", and "list-column-gap". + +In addition to these properties ListView has the following: + ### The "items" property List items are set using the "items" property (Items constant). @@ -3222,7 +3287,7 @@ When assigning, all types except View and string are converted to string, then a and from the resulting View array using the NewViewListAdapter function, a ListAdapter is obtained. If the list items change during operation, then after the change, either the ReloadListViewData() -function of the ListView interface or the global ReloadListViewData(view View, subviewID string) function must be called. +function of the ListView interface or the global ReloadListViewData(view View, subviewID ...string) function must be called. These functions update the displayed list items. ### "Orientation" property @@ -3244,7 +3309,7 @@ on the value of the "text-direction" property. For languages written from right You can get the value of this property using the function - func GetListOrientation(view View, subviewID string) int + func GetListOrientation(view View, subviewID ...string) int ### "wrap" property @@ -3261,7 +3326,7 @@ the beginning (for the position of the beginning and end, see above), the new li You can get the value of this property using the function - func GetListWrap(view View, subviewID string) int + func GetListWrap(view View, subviewID ...string) int ### "item-width" and "item-height" properties @@ -3274,8 +3339,8 @@ properties "item-width" and "item-height" You can get the values of these properties using the functions - func GetListItemWidth(view View, subviewID string) SizeUnit - func GetListItemHeight(view View, subviewID string) SizeUnit + func GetListItemWidth(view View, subviewID ...string) SizeUnit + func GetListItemHeight(view View, subviewID ...string) SizeUnit ### "item-vertical-align" property @@ -3291,7 +3356,7 @@ of the contents of the list items. Valid values: You can get the value of this property using the function - func GetListItemVerticalAlign(view View, subviewID string) int + func GetListItemVerticalAlign(view View, subviewID ...string) int ### "item-horizontal-align" property @@ -3307,7 +3372,7 @@ horizontal alignment of the contents of the list items. Valid values: You can get the value of this property using the function - GetListItemHorizontalAlign(view View, subviewID string) int + GetListItemHorizontalAlign(view View, subviewID ...string) int ### "current" property @@ -3318,7 +3383,7 @@ The value "current" is less than 0 means that no item is selected You can get the value of this property using the function - func GetCurrent(view View, subviewID string) int + func GetCurrent(view View, subviewID ...string) int ### "list-item-style", "current-style", and "current-inactive-style" properties @@ -3346,13 +3411,13 @@ can take the following values You can get the value of this property using the function - func GetListViewCheckbox(view View, subviewID string) int + func GetListViewCheckbox(view View, subviewID ...string) int You can get/set the list of checked items using the "checked" property (Checked constant). This property is of type []int and stores the indexes of the marked elements. You can get the value of this property using the function - func GetListViewCheckedItems(view View, subviewID string) []int + func GetListViewCheckedItems(view View, subviewID ...string) []int You can check if a specific element is marked using the function @@ -3383,8 +3448,8 @@ In this case, the checkbox is centered horizontally, the content is below You can get property values for "checkbox-horizontal-align" and "checkbox-vertical-align" using the functions - func GetListViewCheckboxHorizontalAlign(view View, subviewID string) int - func GetListViewCheckboxVerticalAlign(view View, subviewID string) int + func GetListViewCheckboxHorizontalAlign(view View, subviewID ...string) int + func GetListViewCheckboxVerticalAlign(view View, subviewID ...string) int ### ListView events @@ -3404,9 +3469,9 @@ Where the second argument is an array of indexes of the tagged items. You can get lists of listeners for these events using the functions: - func GetListItemClickedListeners(view View, subviewID string) []func(ListView, int) - func GetListItemSelectedListeners(view View, subviewID string) []func(ListView, int) - func GetListItemCheckedListeners(view View, subviewID string) []func(ListView, []int) + func GetListItemClickedListeners(view View, subviewID ...string) []func(ListView, int) + func GetListItemSelectedListeners(view View, subviewID ...string) []func(ListView, int) + func GetListItemCheckedListeners(view View, subviewID ...string) []func(ListView, []int) ## TableView @@ -3422,7 +3487,7 @@ To describe the content, you need to implement the TableAdapter interface declar type TableAdapter interface { RowCount() int ColumnCount() int - Cell(row, column int) interface{} + Cell(row, column int) any } where RowCount() and ColumnCount() functions must return the number of rows and columns in the table; @@ -3441,10 +3506,10 @@ Cell(row, column int) returns the contents of a table cell. The Cell() function The "content" property can also be assigned the following data types * TableAdapter -* [][]interface{} +* [][]any * [][]string -[][]interface{} and [][]string are converted to a TableAdapter when assigned. +[][]any and [][]string are converted to a TableAdapter when assigned. ### "cell-style" property @@ -3496,7 +3561,7 @@ In this case, the table will look like this | | | | |------+-------+--------| -If [][]interface{} is used as the value of the "content" property, then empty structures are used to merge cells +If [][]any is used as the value of the "content" property, then empty structures are used to merge cells type VerticalTableJoin struct { } @@ -3506,7 +3571,7 @@ If [][]interface{} is used as the value of the "content" property, then empty st These structures attach the cell to the top/left, respectively. The description of the above table will be as follows - content := [][]interface{} { + content := [][]any { {"", "", rui.HorizontalTableJoin{}}, {rui.VerticalTableJoin{}, "", ""}, } @@ -3600,7 +3665,7 @@ For horizontal alignment, use the "text-align" property You can get the value of this property using the function - func GetTableVerticalAlign(view View, subviewID string) int + func GetTableVerticalAlign(view View, subviewID ...string) int ### "selection-mode" property @@ -3619,7 +3684,7 @@ In this mode, the table generates two types of events: "table-row-selected" and You can get the value of this property using the function - func GetSelectionMode(view View, subviewID string) int + func GetSelectionMode(view View, subviewID ...string) int ### "current" property @@ -3636,7 +3701,7 @@ the "current" property can be assigned a value of type int (row index). You can get the value of this property using the function - func GetTableCurrent(view View, subviewID string) CellIndex + func GetTableCurrent(view View, subviewID ...string) CellIndex ### "allow-selection" property @@ -4312,6 +4377,43 @@ not to use the "title-style" property, but to override the "ruiPopupTitle" style The header can also have a window close button. To add it to the header, use the "close-button" bool property. Setting this property to "true" adds a window close button to the title bar (the default value is "false"). +### Arrow Popup + +A pop-up window can have an arrow on one side. An arrow is specified using the "arrow" int property (Arrow constant). +The "arrow" property can take the following values + +| Value | Constant | Arrow location | +|:-----:|-------------|----------------------------------------------| +| 0 | NoneArrow | No arrow (default value) | +| 1 | Top Arrow | Arrow at the top side of the pop-up window | +| 2 | RightArrow | Arrow on the right side of the pop-up window | +| 3 | bottomarrow | Arrow at the bottom of the pop-up window | +| 4 | LeftArrow | Arrow on the left side of the pop-up window | + +The size of the arrow is specified using the "arrow-size" (ArrowSize constant) and "arrow-width" (ArrowWidth constant) SizeUnit properties. +They specify the length ("arrow-size") and width ("arrow-width") of the arrow. +If these properties are not set, then the constants "@ruiArrowSize" (the default value is 16px) +and "@ruiArrowWidth" (the default value is 16px) are used. + +The alignment of the arrow relative to the popup is set using the "arrow-align" int property (ArrowAlign constant). +The "arrow-align" property can take the following values + +| Value | Constants | Alignment | +|:-----:|--------------------------|----------------------------------| +| 0 | TopAlign / LeftAlign | Top/left alignment | +| 1 | BottomAlign / RightAlign | Bottom/Right Alignment | +| 2 | CenterAlign | Center alignment (default value) | + +You can also set an additional offset for the arrow. For this, the "arrow-offset" SizeUnit property (ArrowOffset constant) is used. + +If the value of the "arrow-align" property is TopAlign/LeftAlign, then the offset is relative to the top/left side. +If the value of the "arrow-align" property is BottomAlign/RightAlign, then the offset is relative to the bottom/right side. +If the value of the "arrow-align" property is CenterAlign, then an offset (can be either positive or negative) is added as an arrow padding. +That is, the arrow is aligned in the center with an offset + +If "arrow-offset" is not set, then the default value for "arrow-align" equal to CenterAlign is 0. +For other "arrow-align" values, the default value is the appropriate corner radius of the popup. + ### Close Popup As it was said above, the Dismiss() method of the Popup interface is used to close the popup window. @@ -4513,7 +4615,7 @@ There are two types of transition animations: A one-time animation is triggered using the SetAnimated function of the View interface. This function has the following description: - SetAnimated(tag string, value interface{}, animation Animation) bool + SetAnimated(tag string, value any, animation Animation) bool It assigns a new value to the property, and the change occurs using the specified animation. For example, @@ -4525,7 +4627,7 @@ For example, 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 interface{}, animation Animation) bool + func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool 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). @@ -4549,7 +4651,7 @@ Calling the SetAnimated function does not change the value of the "transition" p To get the current list of permanent transition animations, use the function - func GetTransition(view View, subviewID string) Params + func GetTransition(view View, subviewID ...string) Params It is recommended to add new transition animations using the function @@ -4586,10 +4688,10 @@ You can also use a listener in the following format: Get lists of listeners for transition animation events using functions: - func GetTransitionRunListeners(view View, subviewID string) []func(View, string) - func GetTransitionStartListeners(view View, subviewID string) []func(View, string) - func GetTransitionEndListeners(view View, subviewID string) []func(View, string) - func GetTransitionCancelListeners(view View, subviewID string) []func(View, string) + func GetTransitionRunListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionStartListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionEndListeners(view View, subviewID ...string) []func(View, string) + func GetTransitionCancelListeners(view View, subviewID ...string) []func(View, string) ### Animation script @@ -4603,8 +4705,8 @@ the change script of one property. She is described as type AnimatedProperty struct { Tag string - From, To interface{} - KeyFrames map[int]interface{} + From, To any + KeyFrames map[int]any } where Tag is the name of the property, From is the initial value of the property, @@ -4718,10 +4820,10 @@ You can also use a listener in the following format: Get lists of animation event listeners using functions: - func GetAnimationStartListeners(view View, subviewID string) []func(View, string) - func GetAnimationEndListeners(view View, subviewID string) []func(View, string) - func GetAnimationCancelListeners(view View, subviewID string) []func(View, string) - func GetAnimationIterationListeners(view View, subviewID string) []func(View, string) + func GetAnimationStartListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationEndListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationCancelListeners(view View, subviewID ...string) []func(View, string) + func GetAnimationIterationListeners(view View, subviewID ...string) []func(View, string) ## Session @@ -4791,7 +4893,7 @@ Returns false if no topic with this name was found. Themes named "" are the defa * SetTitleColor(color Color) sets the color of the browser navigation bar. Supported only in Safari and Chrome for android -* Get(viewID, tag string) interface{} returns the value of the View property named tag. Equivalent to +* Get(viewID, tag string) any returns the value of the View property named tag. Equivalent to rui.Get(session.RootView(), viewID, tag) diff --git a/absoluteLayout.go b/absoluteLayout.go index 704e28c..d55cdac 100644 --- a/absoluteLayout.go +++ b/absoluteLayout.go @@ -14,7 +14,7 @@ type absoluteLayoutData struct { // NewAbsoluteLayout create new AbsoluteLayout object and return it func NewAbsoluteLayout(session Session, params Params) AbsoluteLayout { view := new(absoluteLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -24,8 +24,8 @@ func newAbsoluteLayout(session Session) View { } // Init initialize fields of ViewsContainer by default values -func (layout *absoluteLayoutData) Init(session Session) { - layout.viewsContainerData.Init(session) +func (layout *absoluteLayoutData) init(session Session) { + layout.viewsContainerData.init(session) layout.tag = "AbsoluteLayout" layout.systemClass = "ruiAbsoluteLayout" } diff --git a/animation.go b/animation.go index 706cab4..21287ab 100644 --- a/animation.go +++ b/animation.go @@ -55,16 +55,19 @@ const ( // 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. NormalAnimation = 0 + // ReverseAnimation is value of the "animation-direction" property. // 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 // backwards, and timing functions are also reversed. // For example, an "ease-in" timing function becomes "ease-out". ReverseAnimation = 1 + // AlternateAnimation is value of the "animation-direction" property. // 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. AlternateAnimation = 2 + // AlternateReverseAnimation is value of the "animation-direction" property. // 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. @@ -107,11 +110,11 @@ type AnimatedProperty struct { // Tag is the name of the property Tag string // From is the initial value of the property - From interface{} + From any // To is the final value of the property - To interface{} + To any // KeyFrames is intermediate property values - KeyFrames map[int]interface{} + KeyFrames map[int]any } type animationData struct { @@ -184,7 +187,7 @@ func (animation *animationData) normalizeTag(tag string) string { return tag } -func (animation *animationData) Set(tag string, value interface{}) bool { +func (animation *animationData) Set(tag string, value any) bool { if value == nil { animation.Remove(tag) return true @@ -285,7 +288,7 @@ func (animation *animationData) Set(tag string, value interface{}) bool { ErrorLogF(`key-frame "%d" is out of range`, n) } else { if result.KeyFrames == nil { - result.KeyFrames = map[int]interface{}{n: node.Text()} + result.KeyFrames = map[int]any{n: node.Text()} } else { result.KeyFrames[n] = node.Text() } @@ -359,7 +362,7 @@ func (animation *animationData) Remove(tag string) { delete(animation.properties, animation.normalizeTag(tag)) } -func (animation *animationData) Get(tag string) interface{} { +func (animation *animationData) Get(tag string) any { return animation.getRaw(animation.normalizeTag(tag)) } @@ -397,7 +400,7 @@ func (animation *animationData) animationCSS(session Session) string { buffer.WriteString(animation.keyFramesName) - if duration, _ := floatProperty(animation, Duration, session, 1); duration > 0 { + if duration, ok := floatProperty(animation, Duration, session, 1); ok && duration > 0 { buffer.WriteString(fmt.Sprintf(" %gs ", duration)) } else { buffer.WriteString(" 1s ") @@ -405,7 +408,7 @@ func (animation *animationData) animationCSS(session Session) string { buffer.WriteString(animation.timingFunctionCSS(session)) - if delay, _ := floatProperty(animation, Delay, session, 0); delay > 0 { + if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 { buffer.WriteString(fmt.Sprintf(" %gs", delay)) } else { buffer.WriteString(" 0s") @@ -435,7 +438,7 @@ func (animation *animationData) animationCSS(session Session) string { func (animation *animationData) transitionCSS(buffer *strings.Builder, session Session) { - if duration, _ := floatProperty(animation, Duration, session, 1); duration > 0 { + if duration, ok := floatProperty(animation, Duration, session, 1); ok && duration > 0 { buffer.WriteString(fmt.Sprintf(" %gs ", duration)) } else { buffer.WriteString(" 1s ") @@ -443,7 +446,7 @@ func (animation *animationData) transitionCSS(buffer *strings.Builder, session S buffer.WriteString(animation.timingFunctionCSS(session)) - if delay, _ := floatProperty(animation, Delay, session, 0); delay > 0 { + if delay, ok := floatProperty(animation, Delay, session, 0); ok && delay > 0 { buffer.WriteString(fmt.Sprintf(" %gs", delay)) } } @@ -481,6 +484,8 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string buffer.WriteRune('"') buffer.WriteString(timingFunction) buffer.WriteRune('"') + } else { + buffer.WriteString(timingFunction) } } } @@ -490,14 +495,14 @@ func (animation *animationData) writeTransitionString(tag string, buffer *string func (animation *animationData) timingFunctionCSS(session Session) string { if timingFunction, ok := stringProperty(animation, TimingFunction, session); ok { - if timingFunction, ok = session.resolveConstants(timingFunction); ok && validateTimingFunction(timingFunction) { + if timingFunction, ok = session.resolveConstants(timingFunction); ok && isTimingFunctionValid(timingFunction) { return timingFunction } } return ("ease") } -func validateTimingFunction(timingFunction string) bool { +func isTimingFunctionValid(timingFunction string) bool { switch timingFunction { case "", EaseTiming, EaseInTiming, EaseOutTiming, EaseInOutTiming, LinearTiming: return true @@ -529,6 +534,14 @@ func validateTimingFunction(timingFunction string) bool { return false } +// IsTimingFunctionValid returns "true" if the "timingFunction" argument is the valid timing function. +func IsTimingFunctionValid(timingFunction string, session Session) bool { + if timingFunc, ok := session.resolveConstants(strings.Trim(timingFunction, " \t\n")); ok { + return isTimingFunctionValid(timingFunc) + } + return false +} + func (session *sessionData) registerAnimation(props []AnimatedProperty) string { session.animationCounter++ @@ -602,7 +615,7 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string { return name } -func (view *viewData) SetAnimated(tag string, value interface{}, animation Animation) bool { +func (view *viewData) SetAnimated(tag string, value any, animation Animation) bool { if animation == nil { return view.Set(tag, value) } @@ -659,11 +672,29 @@ func (style *viewStyle) transitionCSS(session Session) string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) + convert := map[string]string{ + CellHeight: "grid-template-rows", + CellWidth: "grid-template-columns", + Row: "grid-row", + Column: "grid-column", + Clip: "clip-path", + Shadow: "box-shadow", + ColumnSeparator: "column-rule", + FontName: "font", + TextSize: "font-size", + TextLineThickness: "text-decoration-thickness", + } + for tag, animation := range style.transitions { if buffer.Len() > 0 { buffer.WriteString(", ") } - buffer.WriteString(tag) + + if cssTag, ok := convert[tag]; ok { + buffer.WriteString(cssTag) + } else { + buffer.WriteString(tag) + } animation.transitionCSS(buffer, session) } return buffer.String() @@ -673,18 +704,42 @@ func (view *viewData) updateTransitionCSS() { updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.Session()), view.Session()) } -func (view *viewData) getTransitions() Params { - result := Params{} - for tag, animation := range view.transitions { +func (style *viewStyle) Transition(tag string) Animation { + if style.transitions != nil { + if anim, ok := style.transitions[tag]; ok { + return anim + } + } + return nil +} + +func (style *viewStyle) Transitions() map[string]Animation { + result := map[string]Animation{} + for tag, animation := range style.transitions { result[tag] = animation } return result } +func (style *viewStyle) SetTransition(tag string, animation Animation) { + if animation == nil { + delete(style.transitions, tag) + } else { + style.transitions[tag] = animation + } +} + +func (view *viewData) SetTransition(tag string, animation Animation) { + view.viewStyle.SetTransition(tag, animation) + if view.created { + updateCSSProperty(view.htmlID(), "transition", view.transitionCSS(view.Session()), view.Session()) + } +} + // SetAnimated sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result: // true - success, // false - error (incompatible type or invalid format of a string value, see AppLog). -func SetAnimated(rootView View, viewID, tag string, value interface{}, animation Animation) bool { +func SetAnimated(rootView View, viewID, tag string, value any, animation Animation) bool { if view := ViewByID(rootView, viewID); view != nil { return view.SetAnimated(tag, value, animation) } @@ -692,58 +747,60 @@ func SetAnimated(rootView View, viewID, tag string, value interface{}, animation } // IsAnimationPaused returns "true" if an animation of the subview is paused, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsAnimationPaused(view View, subviewID string) bool { +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsAnimationPaused(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, AnimationPaused, false) +} + +// 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 +func GetTransitions(view View, subviewID ...string) map[string]Animation { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) + } + + if view != nil { + return view.Transitions() + } + + return map[string]Animation{} +} + +// 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 +func GetTransition(view View, subviewID, tag string) Animation { if subviewID != "" { view = ViewByID(view, subviewID) } + if view != nil { - if result, ok := boolStyledProperty(view, AnimationPaused); ok { - return result + return view.Transition(tag) + } + + return nil +} + +// 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) +func AddTransition(view View, subviewID, tag string, animation Animation) bool { + if tag != "" { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + + if view != nil { + view.SetTransition(tag, animation) + return true } } return false } -// GetTransition returns the subview transitions. The result is always non-nil. -// If the second argument (subviewID) is "" then transitions of the first argument (view) is returned -func GetTransition(view View, subviewID string) Params { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - - if view != nil { - return view.getTransitions() - } - - return Params{} -} - -// AddTransition adds the transition for the subview property. -// If the second argument (subviewID) is "" then the transition is added to the first argument (view) -func AddTransition(view View, subviewID, tag string, animation Animation) bool { - if tag == "" { - return false - } - - if subviewID != "" { - view = ViewByID(view, subviewID) - } - - if view == nil { - return false - } - - transitions := view.getTransitions() - transitions[tag] = animation - return view.Set(Transition, transitions) -} - // GetAnimation returns the subview animations. The result is always non-nil. -// If the second argument (subviewID) is "" then transitions of the first argument (view) is returned -func GetAnimation(view View, subviewID string) []Animation { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { diff --git a/animationEvents.go b/animationEvents.go index 0b0a080..bdebde9 100644 --- a/animationEvents.go +++ b/animationEvents.go @@ -51,131 +51,6 @@ const ( AnimationIterationEvent = "animation-iteration-event" ) -func valueToAnimationListeners(value interface{}) ([]func(View, string), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(View, string): - return []func(View, string){value}, true - - case func(string): - fn := func(_ View, event string) { - value(event) - } - return []func(View, string){fn}, true - - case func(View): - fn := func(view View, _ string) { - value(view) - } - return []func(View, string){fn}, true - - case func(): - fn := func(View, string) { - value() - } - return []func(View, string){fn}, true - - case []func(View, string): - if len(value) == 0 { - return nil, true - } - for _, fn := range value { - if fn == nil { - return nil, false - } - } - return value, true - - case []func(string): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, string), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(_ View, event string) { - v(event) - } - } - return listeners, true - - case []func(View): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, string), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(view View, _ string) { - v(view) - } - } - return listeners, true - - case []func(): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, string), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(View, string) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, string), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(View, string): - listeners[i] = v - - case func(string): - listeners[i] = func(_ View, event string) { - v(event) - } - - case func(View): - listeners[i] = func(view View, _ string) { - v(view) - } - - case func(): - listeners[i] = func(View, string) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - var transitionEvents = map[string]struct{ jsEvent, jsFunc string }{ TransitionRunEvent: {jsEvent: "ontransitionrun", jsFunc: "transitionRunEvent"}, TransitionStartEvent: {jsEvent: "ontransitionstart", jsFunc: "transitionStartEvent"}, @@ -183,8 +58,8 @@ var transitionEvents = map[string]struct{ jsEvent, jsFunc string }{ TransitionCancelEvent: {jsEvent: "ontransitioncancel", jsFunc: "transitionCancelEvent"}, } -func (view *viewData) setTransitionListener(tag string, value interface{}) bool { - listeners, ok := valueToAnimationListeners(value) +func (view *viewData) setTransitionListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, string](value) if !ok { notCompatibleType(tag, value) return false @@ -212,20 +87,6 @@ func (view *viewData) removeTransitionListener(tag string) { } } -func getAnimationListeners(view View, subviewID string, tag string) []func(View, string) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(tag); value != nil { - if result, ok := value.([]func(View, string)); ok { - return result - } - } - } - return []func(View, string){} -} - func transitionEventsHtml(view View, buffer *strings.Builder) { for tag, js := range transitionEvents { if value := view.getRaw(tag); value != nil { @@ -250,7 +111,7 @@ func (view *viewData) handleTransitionEvents(tag string, data DataObject) { } } - for _, listener := range getAnimationListeners(view, "", tag) { + for _, listener := range getEventListeners[View, string](view, nil, tag) { listener(view, property) } } @@ -263,8 +124,8 @@ var animationEvents = map[string]struct{ jsEvent, jsFunc string }{ AnimationCancelEvent: {jsEvent: "onanimationcancel", jsFunc: "animationCancelEvent"}, } -func (view *viewData) setAnimationListener(tag string, value interface{}) bool { - listeners, ok := valueToAnimationListeners(value) +func (view *viewData) setAnimationListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, string](value) if !ok { notCompatibleType(tag, value) return false @@ -303,10 +164,10 @@ func animationEventsHtml(view View, buffer *strings.Builder) { } func (view *viewData) handleAnimationEvents(tag string, data DataObject) { - if listeners := getAnimationListeners(view, "", tag); len(listeners) > 0 { + if listeners := getEventListeners[View, string](view, nil, tag); len(listeners) > 0 { id := "" if name, ok := data.PropertyValue("name"); ok { - for _, animation := range GetAnimation(view, "") { + for _, animation := range GetAnimation(view) { if name == animation.animationName() { id, _ = stringProperty(animation, ID, view.Session()) } @@ -320,56 +181,56 @@ func (view *viewData) handleAnimationEvents(tag string, data DataObject) { // GetTransitionRunListeners returns the "transition-run-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTransitionRunListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, TransitionRunEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTransitionRunListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, TransitionRunEvent) } // GetTransitionStartListeners returns the "transition-start-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTransitionStartListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, TransitionStartEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTransitionStartListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, TransitionStartEvent) } // GetTransitionEndListeners returns the "transition-end-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTransitionEndListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, TransitionEndEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTransitionEndListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, TransitionEndEvent) } // GetTransitionCancelListeners returns the "transition-cancel-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTransitionCancelListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, TransitionCancelEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTransitionCancelListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, TransitionCancelEvent) } // GetAnimationStartListeners returns the "animation-start-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetAnimationStartListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, AnimationStartEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetAnimationStartListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, AnimationStartEvent) } // GetAnimationEndListeners returns the "animation-end-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetAnimationEndListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, AnimationEndEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetAnimationEndListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, AnimationEndEvent) } // GetAnimationCancelListeners returns the "animation-cancel-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetAnimationCancelListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, AnimationCancelEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetAnimationCancelListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, AnimationCancelEvent) } // GetAnimationIterationListeners returns the "animation-iteration-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetAnimationIterationListeners(view View, subviewID string) []func(View, string) { - return getAnimationListeners(view, subviewID, AnimationIterationEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetAnimationIterationListeners(view View, subviewID ...string) []func(View, string) { + return getEventListeners[View, string](view, subviewID, AnimationIterationEvent) } diff --git a/appLog.go b/appLog.go index 60919bd..3d23c27 100644 --- a/appLog.go +++ b/appLog.go @@ -39,7 +39,7 @@ func DebugLog(text string) { } // DebugLogF print the text to the debug log -func DebugLogF(format string, a ...interface{}) { +func DebugLogF(format string, a ...any) { if debugLogFunc != nil { debugLogFunc(fmt.Sprintf(format, a...)) } @@ -57,7 +57,7 @@ func ErrorLog(text string) { } // ErrorLogF print the text to the error log -func ErrorLogF(format string, a ...interface{}) { +func ErrorLogF(format string, a ...any) { lastError = fmt.Sprintf(format, a...) if errorLogFunc != nil { errorLogFunc(lastError) diff --git a/app_scripts.js b/app_scripts.js index 8499f71..9894e88 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1793,6 +1793,7 @@ function imageLoaded(element, event) { ",natural-height=" + element.naturalHeight + ",current-src=\"" + element.currentSrc + "\"}"; sendMessage(message); + scanElementsSize() } function imageError(element, event) { diff --git a/app_styles.css b/app_styles.css index b85cfb8..cf66269 100644 --- a/app_styles.css +++ b/app_styles.css @@ -59,7 +59,6 @@ ul:focus { outline: none; } - .ruiRoot { position: absolute; top: 0px; diff --git a/audioPlayer.go b/audioPlayer.go index ab27a64..a7cecc3 100644 --- a/audioPlayer.go +++ b/audioPlayer.go @@ -11,7 +11,7 @@ type audioPlayerData struct { // NewAudioPlayer create new MediaPlayer object and return it func NewAudioPlayer(session Session, params Params) AudioPlayer { view := new(audioPlayerData) - view.Init(session) + view.init(session) view.tag = "AudioPlayer" setInitParams(view, params) return view @@ -21,8 +21,8 @@ func newAudioPlayer(session Session) View { return NewAudioPlayer(session, nil) } -func (player *audioPlayerData) Init(session Session) { - player.mediaPlayerData.Init(session) +func (player *audioPlayerData) init(session Session) { + player.mediaPlayerData.init(session) player.tag = "AudioPlayer" } diff --git a/background.go b/background.go index 9aed11d..692c73d 100644 --- a/background.go +++ b/background.go @@ -81,22 +81,22 @@ func createBackground(obj DataObject) BackgroundElement { switch obj.Tag() { case "image": image := new(backgroundImage) - image.properties = map[string]interface{}{} + image.properties = map[string]any{} result = image case "linear-gradient": gradient := new(backgroundLinearGradient) - gradient.properties = map[string]interface{}{} + gradient.properties = map[string]any{} result = gradient case "radial-gradient": gradient := new(backgroundRadialGradient) - gradient.properties = map[string]interface{}{} + gradient.properties = map[string]any{} result = gradient case "conic-gradient": gradient := new(backgroundConicGradient) - gradient.properties = map[string]interface{}{} + gradient.properties = map[string]any{} result = gradient default: @@ -118,7 +118,7 @@ func createBackground(obj DataObject) BackgroundElement { // NewBackgroundImage creates the new background image func NewBackgroundImage(params Params) BackgroundElement { result := new(backgroundImage) - result.properties = map[string]interface{}{} + result.properties = map[string]any{} for tag, value := range params { result.Set(tag, value) } @@ -156,7 +156,7 @@ func (image *backgroundImage) normalizeTag(tag string) string { return tag } -func (image *backgroundImage) Set(tag string, value interface{}) bool { +func (image *backgroundImage) Set(tag string, value any) bool { tag = image.normalizeTag(tag) switch tag { case Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign, @@ -167,7 +167,7 @@ func (image *backgroundImage) Set(tag string, value interface{}) bool { return false } -func (image *backgroundImage) Get(tag string) interface{} { +func (image *backgroundImage) Get(tag string) any { return image.backgroundElement.Get(image.normalizeTag(tag)) } @@ -219,9 +219,9 @@ func (image *backgroundImage) cssStyle(session Session) string { if width.Type != Auto || height.Type != Auto { buffer.WriteString(` / `) - buffer.WriteString(width.cssString("auto")) + buffer.WriteString(width.cssString("auto", session)) buffer.WriteRune(' ') - buffer.WriteString(height.cssString("auto")) + buffer.WriteString(height.cssString("auto", session)) } } diff --git a/backgroundConicGradient.go b/backgroundConicGradient.go index 31bbfaf..00ef8a1 100644 --- a/backgroundConicGradient.go +++ b/backgroundConicGradient.go @@ -12,16 +12,16 @@ type backgroundConicGradient struct { type BackgroundGradientAngle struct { // Color - the color of the key angle. Must not be nil. // Can take a value of Color type or string (color constant or textual description of the color) - Color interface{} + Color any // Angle - the key angle. Optional (may be nil). // Can take a value of AngleUnit type or string (angle constant or textual description of the angle) - Angle interface{} + Angle any } // NewBackgroundConicGradient creates the new background conic gradient func NewBackgroundConicGradient(params Params) BackgroundElement { result := new(backgroundConicGradient) - result.properties = map[string]interface{}{} + result.properties = map[string]any{} for tag, value := range params { result.Set(tag, value) } @@ -139,7 +139,7 @@ func (gradient *backgroundConicGradient) normalizeTag(tag string) string { return tag } -func (gradient *backgroundConicGradient) Set(tag string, value interface{}) bool { +func (gradient *backgroundConicGradient) Set(tag string, value any) bool { tag = gradient.normalizeTag(tag) switch tag { case CenterX, CenterY, Repeating, From: @@ -153,7 +153,7 @@ func (gradient *backgroundConicGradient) Set(tag string, value interface{}) bool return false } -func (gradient *backgroundConicGradient) stringToAngle(text string) (interface{}, bool) { +func (gradient *backgroundConicGradient) stringToAngle(text string) (any, bool) { if text == "" { return nil, false } else if text[0] == '@' { @@ -216,7 +216,7 @@ func (gradient *backgroundConicGradient) parseGradientText(value string) []Backg return vector } -func (gradient *backgroundConicGradient) setGradient(value interface{}) bool { +func (gradient *backgroundConicGradient) setGradient(value any) bool { if value == nil { delete(gradient.properties, Gradient) return true @@ -262,7 +262,7 @@ func (gradient *backgroundConicGradient) setGradient(value interface{}) bool { return false } -func (gradient *backgroundConicGradient) Get(tag string) interface{} { +func (gradient *backgroundConicGradient) Get(tag string) any { return gradient.backgroundElement.Get(gradient.normalizeTag(tag)) } @@ -316,9 +316,9 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string { buffer.WriteRune(' ') } buffer.WriteString("at ") - buffer.WriteString(x.cssString("50%")) + buffer.WriteString(x.cssString("50%", session)) buffer.WriteString(" ") - buffer.WriteString(y.cssString("50%")) + buffer.WriteString(y.cssString("50%", session)) comma = true } diff --git a/backgroundGradient.go b/backgroundGradient.go index 08a8328..fcb0e61 100644 --- a/backgroundGradient.go +++ b/backgroundGradient.go @@ -50,10 +50,10 @@ const ( 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 interface{} + 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 interface{} + Pos any } type backgroundGradient struct { @@ -71,7 +71,7 @@ type backgroundRadialGradient struct { // NewBackgroundLinearGradient creates the new background linear gradient func NewBackgroundLinearGradient(params Params) BackgroundElement { result := new(backgroundLinearGradient) - result.properties = map[string]interface{}{} + result.properties = map[string]any{} for tag, value := range params { result.Set(tag, value) } @@ -81,7 +81,7 @@ func NewBackgroundLinearGradient(params Params) BackgroundElement { // NewBackgroundRadialGradient creates the new background radial gradient func NewBackgroundRadialGradient(params Params) BackgroundElement { result := new(backgroundRadialGradient) - result.properties = map[string]interface{}{} + result.properties = map[string]any{} for tag, value := range params { result.Set(tag, value) } @@ -106,7 +106,7 @@ func (gradient *backgroundGradient) parseGradientText(value string) []Background return points } -func (gradient *backgroundGradient) Set(tag string, value interface{}) bool { +func (gradient *backgroundGradient) Set(tag string, value any) bool { switch tag = strings.ToLower(tag); tag { case Repeating: @@ -261,19 +261,18 @@ func (gradient *backgroundGradient) writeGradient(session Session, buffer *strin switch value := point.Pos.(type) { case string: if value != "" { - if value[0] == '@' { - value, _ = session.Constant(value[1:]) - } - if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto { - buffer.WriteRune(' ') - buffer.WriteString(pos.cssString("")) + 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("")) + buffer.WriteString(value.cssString("", session)) } } } @@ -296,7 +295,7 @@ func (image *backgroundLinearGradient) Clone() BackgroundElement { return result } -func (gradient *backgroundLinearGradient) Set(tag string, value interface{}) bool { +func (gradient *backgroundLinearGradient) Set(tag string, value any) bool { if strings.ToLower(tag) == Direction { switch value := value.(type) { case AngleUnit: @@ -402,7 +401,7 @@ func (gradient *backgroundRadialGradient) normalizeTag(tag string) string { return tag } -func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) bool { +func (gradient *backgroundRadialGradient) Set(tag string, value any) bool { tag = gradient.normalizeTag(tag) switch tag { case RadialGradientRadius: @@ -426,7 +425,7 @@ func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) boo return true } - case []interface{}: + case []any: switch len(value) { case 0: delete(gradient.properties, RadialGradientRadius) @@ -477,7 +476,7 @@ func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) boo return gradient.backgroundGradient.Set(tag, value) } -func (gradient *backgroundRadialGradient) Get(tag string) interface{} { +func (gradient *backgroundRadialGradient) Get(tag string) any { return gradient.backgroundGradient.Get(gradient.normalizeTag(tag)) } @@ -512,9 +511,9 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string { if r, ok := StringToSizeUnit(text); ok && r.Type != Auto { buffer.WriteString("ellipse ") shapeText = "" - buffer.WriteString(r.cssString("")) + buffer.WriteString(r.cssString("", session)) buffer.WriteString(" ") - buffer.WriteString(r.cssString("")) + buffer.WriteString(r.cssString("", session)) buffer.WriteString(" ") } else { ErrorLog(`Invalid radial gradient radius: ` + text) @@ -539,9 +538,9 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string { if value.Type != Auto { buffer.WriteString("ellipse ") shapeText = "" - buffer.WriteString(value.cssString("")) + buffer.WriteString(value.cssString("", session)) buffer.WriteString(" ") - buffer.WriteString(value.cssString("")) + buffer.WriteString(value.cssString("", session)) buffer.WriteString(" ") } @@ -553,11 +552,11 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string { buffer.WriteString("ellipse ") shapeText = "" for i := 0; i < count; i++ { - buffer.WriteString(value[i].cssString("50%")) + buffer.WriteString(value[i].cssString("50%", session)) buffer.WriteString(" ") } - case []interface{}: + case []any: count := len(value) if count > 2 { count = 2 @@ -568,13 +567,13 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string { if value[i] != nil { switch value := value[i].(type) { case SizeUnit: - buffer.WriteString(value.cssString("50%")) + 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%")) + buffer.WriteString(size.cssString("50%", session)) buffer.WriteString(" ") } else { buffer.WriteString("50% ") @@ -597,9 +596,9 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string { buffer.WriteString(shapeText) } buffer.WriteString("at ") - buffer.WriteString(x.cssString("50%")) + buffer.WriteString(x.cssString("50%", session)) buffer.WriteString(" ") - buffer.WriteString(y.cssString("50%")) + buffer.WriteString(y.cssString("50%", session)) } buffer.WriteString(", ") diff --git a/border.go b/border.go index e270c07..97a62f9 100644 --- a/border.go +++ b/border.go @@ -64,9 +64,9 @@ type borderProperty struct { propertyList } -func newBorderProperty(value interface{}) BorderProperty { +func newBorderProperty(value any) BorderProperty { border := new(borderProperty) - border.properties = map[string]interface{}{} + border.properties = map[string]any{} if value != nil { switch value := value.(type) { @@ -131,7 +131,7 @@ func newBorderProperty(value interface{}) BorderProperty { // NewBorder creates the new BorderProperty func NewBorder(params Params) BorderProperty { border := new(borderProperty) - border.properties = map[string]interface{}{} + border.properties = map[string]any{} if params != nil { for _, tag := range []string{Style, Width, ColorTag, Left, Right, Top, Bottom, LeftStyle, RightStyle, TopStyle, BottomStyle, @@ -213,7 +213,7 @@ func (border *borderProperty) writeString(buffer *strings.Builder, indent string buffer.WriteString("_{ ") comma := false - write := func(tag string, value interface{}) { + write := func(tag string, value any) { if comma { buffer.WriteString(", ") } @@ -430,7 +430,7 @@ func (border *borderProperty) Remove(tag string) { } } -func (border *borderProperty) Set(tag string, value interface{}) bool { +func (border *borderProperty) Set(tag string, value any) bool { if value == nil { border.Remove(tag) return true @@ -512,7 +512,7 @@ func (border *borderProperty) Set(tag string, value interface{}) bool { return false } -func (border *borderProperty) Get(tag string) interface{} { +func (border *borderProperty) Get(tag string) any { tag = border.normalizeTag(tag) if result, ok := border.properties[tag]; ok { @@ -658,11 +658,14 @@ func (border *borderProperty) cssWidth(builder cssBuilder, session Session) { borders.Top.Width == borders.Left.Width && borders.Top.Width == borders.Bottom.Width { if borders.Top.Width.Type != Auto { - builder.add("border-width", borders.Top.Width.cssString("0")) + builder.add("border-width", borders.Top.Width.cssString("0", session)) } } else { - builder.addValues("border-width", " ", borders.Top.Width.cssString("0"), - borders.Right.Width.cssString("0"), borders.Bottom.Width.cssString("0"), borders.Left.Width.cssString("0")) + builder.addValues("border-width", " ", + borders.Top.Width.cssString("0", session), + borders.Right.Width.cssString("0", session), + borders.Bottom.Width.cssString("0", session), + borders.Left.Width.cssString("0", session)) } } diff --git a/bounds.go b/bounds.go index 6f6626c..5de35bb 100644 --- a/bounds.go +++ b/bounds.go @@ -20,7 +20,7 @@ type boundsPropertyData struct { // NewBoundsProperty creates the new BoundsProperty object func NewBoundsProperty(params Params) BoundsProperty { bounds := new(boundsPropertyData) - bounds.properties = map[string]interface{}{} + bounds.properties = map[string]any{} if params != nil { for _, tag := range []string{Top, Right, Bottom, Left} { if value, ok := params[tag]; ok { @@ -79,7 +79,7 @@ func (bounds *boundsPropertyData) Remove(tag string) { bounds.propertyList.Remove(bounds.normalizeTag(tag)) } -func (bounds *boundsPropertyData) Set(tag string, value interface{}) bool { +func (bounds *boundsPropertyData) Set(tag string, value any) bool { if value == nil { bounds.Remove(tag) return true @@ -98,7 +98,7 @@ func (bounds *boundsPropertyData) Set(tag string, value interface{}) bool { return false } -func (bounds *boundsPropertyData) Get(tag string) interface{} { +func (bounds *boundsPropertyData) Get(tag string) any { tag = bounds.normalizeTag(tag) if value, ok := bounds.properties[tag]; ok { return value @@ -213,22 +213,25 @@ func (bounds *Bounds) String() string { bounds.Bottom.String() + "," + bounds.Left.String() } -func (bounds *Bounds) cssValue(tag string, builder cssBuilder) { +func (bounds *Bounds) cssValue(tag string, builder cssBuilder, session Session) { if bounds.allFieldsEqual() { - builder.add(tag, bounds.Top.cssString("0")) + builder.add(tag, bounds.Top.cssString("0", session)) } else { - builder.addValues(tag, " ", bounds.Top.cssString("0"), bounds.Right.cssString("0"), - bounds.Bottom.cssString("0"), bounds.Left.cssString("0")) + builder.addValues(tag, " ", + bounds.Top.cssString("0", session), + bounds.Right.cssString("0", session), + bounds.Bottom.cssString("0", session), + bounds.Left.cssString("0", session)) } } -func (bounds *Bounds) cssString() string { +func (bounds *Bounds) cssString(session Session) string { var builder cssValueBuilder - bounds.cssValue("", &builder) + bounds.cssValue("", &builder, session) return builder.finish() } -func (properties *propertyList) setBounds(tag string, value interface{}) bool { +func (properties *propertyList) setBounds(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { switch value := value.(type) { case string: @@ -260,6 +263,12 @@ func (properties *propertyList) setBounds(tag string, value interface{}) bool { case SizeUnit: properties.properties[tag] = value + case float32: + properties.properties[tag] = Px(float64(value)) + + case float64: + properties.properties[tag] = Px(value) + case Bounds: bounds := NewBoundsProperty(nil) if value.Top.Type != Auto { @@ -292,8 +301,12 @@ func (properties *propertyList) setBounds(tag string, value interface{}) bool { properties.properties[tag] = bounds default: - notCompatibleType(tag, value) - return false + if n, ok := isInt(value); ok { + properties.properties[tag] = Px(float64(n)) + } else { + notCompatibleType(tag, value) + return false + } } } @@ -340,7 +353,7 @@ func (properties *propertyList) removeBoundsSide(mainTag, sideTag string) { } } -func (properties *propertyList) setBoundsSide(mainTag, sideTag string, value interface{}) bool { +func (properties *propertyList) setBoundsSide(mainTag, sideTag string, value any) bool { bounds := properties.boundsProperty(mainTag) if bounds.Set(sideTag, value) { properties.properties[mainTag] = bounds diff --git a/canvas.go b/canvas.go index ff92252..d1c4df8 100644 --- a/canvas.go +++ b/canvas.go @@ -592,7 +592,7 @@ func (canvas *canvasData) writeFont(name string, script *strings.Builder) { func (canvas *canvasData) SetFont(name string, size SizeUnit) { canvas.script.WriteString("\nctx.font = '") - canvas.script.WriteString(size.cssString("1em")) + canvas.script.WriteString(size.cssString("1rem", canvas.View().Session())) canvas.writeFont(name, &canvas.script) } @@ -616,7 +616,7 @@ func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params F } } - script.WriteString(size.cssString("1em")) + script.WriteString(size.cssString("1rem", canvas.View().Session())) switch params.LineHeight.Type { case Auto: @@ -634,7 +634,7 @@ func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params F default: script.WriteString("/") - script.WriteString(params.LineHeight.cssString("")) + script.WriteString(params.LineHeight.cssString("", canvas.View().Session())) } canvas.writeFont(name, script) diff --git a/canvasView.go b/canvasView.go index c251bbf..a0b1286 100644 --- a/canvasView.go +++ b/canvasView.go @@ -21,7 +21,7 @@ type canvasViewData struct { // NewCanvasView creates the new custom draw view func NewCanvasView(session Session, params Params) CanvasView { view := new(canvasViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -31,8 +31,8 @@ func newCanvasView(session Session) View { } // Init initialize fields of ViewsContainer by default values -func (canvasView *canvasViewData) Init(session Session) { - canvasView.viewData.Init(session) +func (canvasView *canvasViewData) init(session Session) { + canvasView.viewData.init(session) canvasView.tag = "CanvasView" } @@ -63,11 +63,11 @@ func (canvasView *canvasViewData) remove(tag string) { } } -func (canvasView *canvasViewData) Set(tag string, value interface{}) bool { +func (canvasView *canvasViewData) Set(tag string, value any) bool { return canvasView.set(canvasView.normalizeTag(tag), value) } -func (canvasView *canvasViewData) set(tag string, value interface{}) bool { +func (canvasView *canvasViewData) set(tag string, value any) bool { if tag == DrawFunction { if value == nil { canvasView.drawer = nil @@ -85,11 +85,11 @@ func (canvasView *canvasViewData) set(tag string, value interface{}) bool { return canvasView.viewData.set(tag, value) } -func (canvasView *canvasViewData) Get(tag string) interface{} { +func (canvasView *canvasViewData) Get(tag string) any { return canvasView.get(canvasView.normalizeTag(tag)) } -func (canvasView *canvasViewData) get(tag string) interface{} { +func (canvasView *canvasViewData) get(tag string) any { if tag == DrawFunction { return canvasView.drawer } diff --git a/checkbox.go b/checkbox.go index f39291d..83bd860 100644 --- a/checkbox.go +++ b/checkbox.go @@ -23,7 +23,7 @@ type checkboxData struct { // NewCheckbox create new Checkbox object and return it func NewCheckbox(session Session, params Params) Checkbox { view := new(checkboxData) - view.Init(session) + view.init(session) setInitParams(view, Params{ ClickEvent: checkboxClickListener, KeyDownEvent: checkboxKeyListener, @@ -36,8 +36,8 @@ func newCheckbox(session Session) View { return NewCheckbox(session, nil) } -func (button *checkboxData) Init(session Session) { - button.viewsContainerData.Init(session) +func (button *checkboxData) init(session Session) { + button.viewsContainerData.init(session) button.tag = "Checkbox" button.systemClass = "ruiGridLayout ruiCheckbox" button.checkedListeners = []func(Checkbox, bool){} @@ -51,7 +51,7 @@ func (button *checkboxData) Focusable() bool { return true } -func (button *checkboxData) Get(tag string) interface{} { +func (button *checkboxData) Get(tag string) any { switch strings.ToLower(tag) { case CheckboxChangedEvent: return button.checkedListeners @@ -60,11 +60,11 @@ func (button *checkboxData) Get(tag string) interface{} { return button.viewsContainerData.Get(tag) } -func (button *checkboxData) Set(tag string, value interface{}) bool { +func (button *checkboxData) Set(tag string, value any) bool { return button.set(tag, value) } -func (button *checkboxData) set(tag string, value interface{}) bool { +func (button *checkboxData) set(tag string, value any) bool { switch tag { case CheckboxChangedEvent: if !button.setChangedListener(value) { @@ -194,80 +194,32 @@ func (button *checkboxData) changedCheckboxState(state bool) { } func checkboxClickListener(view View) { - view.Set(Checked, !IsCheckboxChecked(view, "")) + view.Set(Checked, !IsCheckboxChecked(view)) BlurView(view) } func checkboxKeyListener(view View, event KeyEvent) { switch event.Code { case "Enter", "Space": - view.Set(Checked, !IsCheckboxChecked(view, "")) + view.Set(Checked, !IsCheckboxChecked(view)) } } -func (button *checkboxData) setChangedListener(value interface{}) bool { - if value == nil { - if len(button.checkedListeners) > 0 { - button.checkedListeners = []func(Checkbox, bool){} - } - return true - } - - switch value := value.(type) { - case func(Checkbox, bool): - button.checkedListeners = []func(Checkbox, bool){value} - - case func(bool): - fn := func(_ Checkbox, checked bool) { - value(checked) - } - button.checkedListeners = []func(Checkbox, bool){fn} - - case []func(Checkbox, bool): - button.checkedListeners = value - - case []func(bool): - listeners := make([]func(Checkbox, bool), len(value)) - for i, val := range value { - if val == nil { - return false - } - - listeners[i] = func(_ Checkbox, checked bool) { - val(checked) - } - } - button.checkedListeners = listeners - - case []interface{}: - listeners := make([]func(Checkbox, bool), len(value)) - for i, val := range value { - if val == nil { - return false - } - - switch val := val.(type) { - case func(Checkbox, bool): - listeners[i] = val - - case func(bool): - listeners[i] = func(_ Checkbox, date bool) { - val(date) - } - - default: - return false - } - } - button.checkedListeners = listeners +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) { session := button.Session() - vAlign, _ := enumStyledProperty(button, CheckboxVerticalAlign, LeftAlign) - hAlign, _ := enumStyledProperty(button, CheckboxHorizontalAlign, TopAlign) + vAlign := GetCheckboxVerticalAlign(button) + hAlign := GetCheckboxHorizontalAlign(button) switch hAlign { case CenterAlign: if vAlign == BottomAlign { @@ -284,7 +236,7 @@ func (button *checkboxData) cssStyle(self View, builder cssBuilder) { } if gap, ok := sizeConstant(session, "ruiCheckboxGap"); ok && gap.Type != Auto && gap.Value > 0 { - builder.add("gap", gap.cssString("0")) + builder.add("gap", gap.cssString("0", session)) } builder.add("align-items", "stretch") @@ -294,8 +246,8 @@ func (button *checkboxData) cssStyle(self View, builder cssBuilder) { } func (button *checkboxData) htmlCheckbox(buffer *strings.Builder, checked bool) (int, int) { - vAlign, _ := enumStyledProperty(button, CheckboxVerticalAlign, LeftAlign) - hAlign, _ := enumStyledProperty(button, CheckboxHorizontalAlign, TopAlign) + vAlign := GetCheckboxVerticalAlign(button) + hAlign := GetCheckboxHorizontalAlign(button) buffer.WriteString(`
0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { - if result, ok := colorStyledProperty(view, ColorPickerValue); ok { - return result + if value, ok := colorProperty(view, ColorPickerValue, view.Session()); ok { + return value } - for _, tag := range []string{Value, ColorTag} { + for _, tag := range []string{ColorPickerValue, Value, ColorTag} { if value := valueFromStyle(view, tag); value != nil { if result, ok := valueToColor(value, view.Session()); ok { return result @@ -247,17 +204,7 @@ func GetColorPickerValue(view View, subviewID string) Color { // GetColorChangedListeners returns the ColorChangedListener list of an ColorPicker subview. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetColorChangedListeners(view View, subviewID string) []func(ColorPicker, Color) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ColorChangedEvent); value != nil { - if listeners, ok := value.([]func(ColorPicker, Color)); ok { - return listeners - } - } - } - return []func(ColorPicker, Color){} +// 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) { + return getEventListeners[ColorPicker, Color](view, subviewID, ColorChangedEvent) } diff --git a/columnLayout.go b/columnLayout.go index 8ef7d35..ec10221 100644 --- a/columnLayout.go +++ b/columnLayout.go @@ -47,7 +47,7 @@ type columnLayoutData struct { // NewColumnLayout create new ColumnLayout object and return it func NewColumnLayout(session Session, params Params) ColumnLayout { view := new(columnLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -57,8 +57,8 @@ func newColumnLayout(session Session) View { } // Init initialize fields of ColumnLayout by default values -func (ColumnLayout *columnLayoutData) Init(session Session) { - ColumnLayout.viewsContainerData.Init(session) +func (ColumnLayout *columnLayoutData) init(session Session) { + ColumnLayout.viewsContainerData.init(session) ColumnLayout.tag = "ColumnLayout" //ColumnLayout.systemClass = "ruiColumnLayout" } @@ -76,7 +76,7 @@ func (columnLayout *columnLayoutData) normalizeTag(tag string) string { return tag } -func (columnLayout *columnLayoutData) Get(tag string) interface{} { +func (columnLayout *columnLayoutData) Get(tag string) any { return columnLayout.get(columnLayout.normalizeTag(tag)) } @@ -97,11 +97,11 @@ func (columnLayout *columnLayoutData) remove(tag string) { } } -func (columnLayout *columnLayoutData) Set(tag string, value interface{}) bool { +func (columnLayout *columnLayoutData) Set(tag string, value any) bool { return columnLayout.set(columnLayout.normalizeTag(tag), value) } -func (columnLayout *columnLayoutData) set(tag string, value interface{}) bool { +func (columnLayout *columnLayoutData) set(tag string, value any) bool { if value == nil { columnLayout.remove(tag) return true @@ -137,50 +137,26 @@ func (columnLayout *columnLayoutData) set(tag string, value interface{}) bool { // GetColumnCount returns int value which specifies number of columns into which the content of // ColumnLayout is break. If the return value is 0 then the number of columns is calculated // based on the "column-width" property. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnCount(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - result, _ := intStyledProperty(view, ColumnCount, 0) - return result +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnCount(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, ColumnCount, 0) } // GetColumnWidth returns SizeUnit value which specifies the width of each column of ColumnLayout. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnWidth(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, ColumnWidth) - return result +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnWidth(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ColumnWidth, false) } // GetColumnGap returns SizeUnit property which specifies the size of the gap (gutter) between columns of ColumnLayout. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnGap(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, ColumnGap) - return result +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnGap(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ColumnGap, false) } -// GetColumnSeparator returns ViewBorder struct which specifies the line drawn between -// columns in a multi-column ColumnLayout. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnSeparator(view View, subviewID string) ViewBorder { - if subviewID != "" { - view = ViewByID(view, subviewID) +func getColumnSeparator(view View, subviewID []string) ViewBorder { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -199,27 +175,34 @@ func GetColumnSeparator(view View, subviewID string) ViewBorder { return ViewBorder{} } +// GetColumnSeparator returns ViewBorder struct which specifies the line drawn between +// columns in a multi-column ColumnLayout. +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnSeparator(view View, subviewID ...string) ViewBorder { + return getColumnSeparator(view, subviewID) +} + // ColumnSeparatorStyle returns int value which specifies the style of the line drawn between // columns in a multi-column layout. // Valid values are NoneLine (0), SolidLine (1), DashedLine (2), DottedLine (3), and DoubleLine (4). -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnSeparatorStyle(view View, subviewID string) int { - border := GetColumnSeparator(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnSeparatorStyle(view View, subviewID ...string) int { + border := getColumnSeparator(view, subviewID) return border.Style } // ColumnSeparatorWidth returns SizeUnit value which specifies the width of the line drawn between // columns in a multi-column layout. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnSeparatorWidth(view View, subviewID string) SizeUnit { - border := GetColumnSeparator(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnSeparatorWidth(view View, subviewID ...string) SizeUnit { + border := getColumnSeparator(view, subviewID) return border.Width } // ColumnSeparatorColor returns Color value which specifies the color of the line drawn between // columns in a multi-column layout. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetColumnSeparatorColor(view View, subviewID string) Color { - border := GetColumnSeparator(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetColumnSeparatorColor(view View, subviewID ...string) Color { + border := getColumnSeparator(view, subviewID) return border.Color } diff --git a/columnSeparator.go b/columnSeparator.go index b3d91fa..8e3a1d4 100644 --- a/columnSeparator.go +++ b/columnSeparator.go @@ -18,11 +18,11 @@ type columnSeparatorProperty struct { propertyList } -func newColumnSeparatorProperty(value interface{}) ColumnSeparatorProperty { +func newColumnSeparatorProperty(value any) ColumnSeparatorProperty { if value == nil { separator := new(columnSeparatorProperty) - separator.properties = map[string]interface{}{} + separator.properties = map[string]any{} return separator } @@ -32,7 +32,7 @@ func newColumnSeparatorProperty(value interface{}) ColumnSeparatorProperty { case DataObject: separator := new(columnSeparatorProperty) - separator.properties = map[string]interface{}{} + separator.properties = map[string]any{} for _, tag := range []string{Style, Width, ColorTag} { if val, ok := value.PropertyValue(tag); ok && val != "" { separator.set(tag, value) @@ -42,7 +42,7 @@ func newColumnSeparatorProperty(value interface{}) ColumnSeparatorProperty { case ViewBorder: separator := new(columnSeparatorProperty) - separator.properties = map[string]interface{}{ + separator.properties = map[string]any{ Style: value.Style, Width: value.Width, ColorTag: value.Color, @@ -57,7 +57,7 @@ func newColumnSeparatorProperty(value interface{}) ColumnSeparatorProperty { // NewColumnSeparator creates the new ColumnSeparatorProperty func NewColumnSeparator(params Params) ColumnSeparatorProperty { separator := new(columnSeparatorProperty) - separator.properties = map[string]interface{}{} + separator.properties = map[string]any{} if params != nil { for _, tag := range []string{Style, Width, ColorTag} { if value, ok := params[tag]; ok && value != nil { @@ -117,7 +117,7 @@ func (separator *columnSeparatorProperty) Remove(tag string) { } } -func (separator *columnSeparatorProperty) Set(tag string, value interface{}) bool { +func (separator *columnSeparatorProperty) Set(tag string, value any) bool { tag = separator.normalizeTag(tag) if value == nil { @@ -140,7 +140,7 @@ func (separator *columnSeparatorProperty) Set(tag string, value interface{}) boo return false } -func (separator *columnSeparatorProperty) Get(tag string) interface{} { +func (separator *columnSeparatorProperty) Get(tag string) any { tag = separator.normalizeTag(tag) if result, ok := separator.properties[tag]; ok { @@ -167,8 +167,9 @@ func (separator *columnSeparatorProperty) cssValue(session Session) string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - if value.Width.Type != Auto && value.Width.Type != SizeInFraction && value.Width.Value > 0 { - buffer.WriteString(value.Width.cssString("")) + if value.Width.Type != Auto && value.Width.Type != SizeInFraction && + (value.Width.Value > 0 || value.Width.Type == SizeFunction) { + buffer.WriteString(value.Width.cssString("", session)) } styles := enumProperties[BorderStyle].cssValues diff --git a/customView.go b/customView.go index 66b864c..e88df6c 100644 --- a/customView.go +++ b/customView.go @@ -45,26 +45,26 @@ func (customView *CustomViewData) setTag(tag string) { // 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. -func (customView *CustomViewData) Get(tag string) interface{} { +func (customView *CustomViewData) Get(tag string) any { return customView.superView.Get(tag) } -func (customView *CustomViewData) getRaw(tag string) interface{} { +func (customView *CustomViewData) getRaw(tag string) any { return customView.superView.getRaw(tag) } -func (customView *CustomViewData) setRaw(tag string, value interface{}) { +func (customView *CustomViewData) setRaw(tag string, value any) { customView.superView.setRaw(tag, value) } // 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 // a description of the error is written to the log -func (customView *CustomViewData) Set(tag string, value interface{}) bool { +func (customView *CustomViewData) Set(tag string, value any) bool { return customView.superView.Set(tag, value) } -func (customView *CustomViewData) SetAnimated(tag string, value interface{}, animation Animation) bool { +func (customView *CustomViewData) SetAnimated(tag string, value any, animation Animation) bool { return customView.superView.SetAnimated(tag, value, animation) } @@ -87,10 +87,6 @@ func (customView *CustomViewData) Clear() { customView.superView.Clear() } -// Init initializes fields of View by default values -func (customView *CustomViewData) Init(session Session) { -} - func (customView *CustomViewData) cssViewStyle(buffer cssBuilder, session Session) { customView.superView.cssViewStyle(buffer, session) } @@ -264,9 +260,22 @@ func (customView *CustomViewData) setScroll(x, y, width, height float64) { } } -func (customView *CustomViewData) getTransitions() Params { +func (customView *CustomViewData) Transition(tag string) Animation { if customView.superView != nil { - return customView.superView.getTransitions() + return customView.superView.Transition(tag) + } + return nil +} + +func (customView *CustomViewData) Transitions() map[string]Animation { + if customView.superView != nil { + return customView.superView.Transitions() + } + return map[string]Animation{} +} + +func (customView *CustomViewData) SetTransition(tag string, animation Animation) { + if customView.superView != nil { + customView.superView.SetTransition(tag, animation) } - return Params{} } diff --git a/datePicker.go b/datePicker.go index 5c1b808..2709b71 100644 --- a/datePicker.go +++ b/datePicker.go @@ -29,7 +29,7 @@ type datePickerData struct { // NewDatePicker create new DatePicker object and return it func NewDatePicker(session Session, params Params) DatePicker { view := new(datePickerData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -38,8 +38,8 @@ func newDatePicker(session Session) View { return NewDatePicker(session, nil) } -func (picker *datePickerData) Init(session Session) { - picker.viewData.Init(session) +func (picker *datePickerData) init(session Session) { + picker.viewData.init(session) picker.tag = "DatePicker" picker.dateChangedListeners = []func(DatePicker, time.Time){} } @@ -96,7 +96,7 @@ func (picker *datePickerData) remove(tag string) { case DatePickerValue: if _, ok := picker.properties[DatePickerValue]; ok { delete(picker.properties, DatePickerValue) - date := GetDatePickerValue(picker, "") + date := GetDatePickerValue(picker) if picker.created { picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat))) } @@ -114,11 +114,11 @@ func (picker *datePickerData) remove(tag string) { picker.propertyChangedEvent(tag) } -func (picker *datePickerData) Set(tag string, value interface{}) bool { +func (picker *datePickerData) Set(tag string, value any) bool { return picker.set(picker.normalizeTag(tag), value) } -func (picker *datePickerData) set(tag string, value interface{}) bool { +func (picker *datePickerData) set(tag string, value any) bool { if value == nil { picker.remove(tag) return true @@ -204,9 +204,9 @@ func (picker *datePickerData) set(tag string, value interface{}) bool { } case DatePickerStep: - oldStep := GetDatePickerStep(picker, "") + oldStep := GetDatePickerStep(picker) if picker.setIntProperty(DatePickerStep, value) { - if step := GetDatePickerStep(picker, ""); oldStep != step { + if step := GetDatePickerStep(picker); oldStep != step { if picker.created { if step > 0 { updateProperty(picker.htmlID(), Step, strconv.Itoa(step), picker.session) @@ -220,7 +220,7 @@ func (picker *datePickerData) set(tag string, value interface{}) bool { } case DatePickerValue: - oldDate := GetDatePickerValue(picker, "") + oldDate := GetDatePickerValue(picker) if date, ok := setTimeValue(DatePickerValue); ok { if date != oldDate { if picker.created { @@ -235,57 +235,14 @@ func (picker *datePickerData) set(tag string, value interface{}) bool { } case DateChangedEvent: - switch value := value.(type) { - case func(DatePicker, time.Time): - picker.dateChangedListeners = []func(DatePicker, time.Time){value} - - case func(time.Time): - fn := func(_ DatePicker, date time.Time) { - value(date) - } - picker.dateChangedListeners = []func(DatePicker, time.Time){fn} - - case []func(DatePicker, time.Time): - picker.dateChangedListeners = value - - case []func(time.Time): - listeners := make([]func(DatePicker, time.Time), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(_ DatePicker, date time.Time) { - val(date) - } - } - picker.dateChangedListeners = listeners - - case []interface{}: - listeners := make([]func(DatePicker, time.Time), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - switch val := val.(type) { - case func(DatePicker, time.Time): - listeners[i] = val - - case func(time.Time): - listeners[i] = func(_ DatePicker, date time.Time) { - val(date) - } - - default: - notCompatibleType(tag, val) - return false - } - } - picker.dateChangedListeners = listeners + listeners, ok := valueToEventListeners[DatePicker, time.Time](value) + if !ok { + notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(DatePicker, time.Time){} } + picker.dateChangedListeners = listeners picker.propertyChangedEvent(tag) return true @@ -295,11 +252,11 @@ func (picker *datePickerData) set(tag string, value interface{}) bool { return false } -func (picker *datePickerData) Get(tag string) interface{} { +func (picker *datePickerData) Get(tag string) any { return picker.get(picker.normalizeTag(tag)) } -func (picker *datePickerData) get(tag string) interface{} { +func (picker *datePickerData) get(tag string) any { switch tag { case DateChangedEvent: return picker.dateChangedListeners @@ -337,7 +294,7 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) } buffer.WriteString(` value="`) - buffer.WriteString(GetDatePickerValue(picker, "").Format(dateFormat)) + buffer.WriteString(GetDatePickerValue(picker).Format(dateFormat)) buffer.WriteByte('"') buffer.WriteString(` oninput="editViewInputEvent(this)"`) @@ -347,7 +304,7 @@ func (picker *datePickerData) htmlProperties(self View, buffer *strings.Builder) } func (picker *datePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` disabled`) } picker.viewData.htmlDisabledProperties(self, buffer) @@ -358,7 +315,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data case "textChanged": if text, ok := data.PropertyValue("text"); ok { if value, err := time.Parse(dateFormat, text); err == nil { - oldValue := GetDatePickerValue(picker, "") + oldValue := GetDatePickerValue(picker) picker.properties[DatePickerValue] = value if value != oldValue { for _, listener := range picker.dateChangedListeners { @@ -374,7 +331,7 @@ func (picker *datePickerData) handleCommand(self View, command string, data Data } func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) { - valueToTime := func(value interface{}) (time.Time, bool) { + valueToTime := func(value any) (time.Time, bool) { if value != nil { switch value := value.(type) { case time.Time: @@ -408,10 +365,10 @@ func getDateProperty(view View, mainTag, shortTag string) (time.Time, bool) { // GetDatePickerMin returns the min date of DatePicker subview and "true" as the second value if the min date is set, // "false" as the second value otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDatePickerMin(view View, subviewID string) (time.Time, bool) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return getDateProperty(view, DatePickerMin, Min) @@ -421,10 +378,10 @@ func GetDatePickerMin(view View, subviewID string) (time.Time, bool) { // GetDatePickerMax returns the max date of DatePicker subview and "true" as the second value if the min date is set, // "false" as the second value otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDatePickerMax(view View, subviewID string) (time.Time, bool) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return getDateProperty(view, DatePickerMax, Max) @@ -433,24 +390,16 @@ func GetDatePickerMax(view View, subviewID string) (time.Time, bool) { } // GetDatePickerStep returns the date changing step in days of DatePicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDatePickerStep(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, _ := intStyledProperty(view, DatePickerStep, 0); result >= 0 { - return result - } - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetDatePickerStep(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, DatePickerStep, 0) } // GetDatePickerValue returns the date of DatePicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDatePickerValue(view View, subviewID string) time.Time { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return time.Now() @@ -461,17 +410,7 @@ func GetDatePickerValue(view View, subviewID string) time.Time { // GetDateChangedListeners returns the DateChangedListener list of an DatePicker subview. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDateChangedListeners(view View, subviewID string) []func(DatePicker, time.Time) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(DateChangedEvent); value != nil { - if listeners, ok := value.([]func(DatePicker, time.Time)); ok { - return listeners - } - } - } - return []func(DatePicker, time.Time){} +// 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) { + return getEventListeners[DatePicker, time.Time](view, subviewID, DateChangedEvent) } diff --git a/defaultTheme.rui b/defaultTheme.rui index 07024ab..524481a 100644 --- a/defaultTheme.rui +++ b/defaultTheme.rui @@ -61,6 +61,8 @@ theme { ruiTabHeight = 32px, ruiTabBarPadding = 2px, ruiTabRadius = 2px, + ruiArrowSize = 16px, + ruiArrowWidth = 16px, }, constants:touch = _{ ruiButtonHorizontalPadding = 20px, @@ -72,6 +74,7 @@ theme { text-size = 10pt, text-color = @ruiTextColor, background-color = @ruiBackgroundColor, + accent-color = @ruiHighlightColor, }, ruiButton { align = center, @@ -216,7 +219,6 @@ theme { background-color = @ruiPopupBackgroundColor, text-color = @ruiPopupTextColor, radius = 4px, - shadow = _{spread-radius=4px, blur=16px, color=@ruiPopupShadow }, }, ruiPopupTitle { background-color = @ruiPopupTitleColor, diff --git a/detailsView.go b/detailsView.go index 5748b12..8cf9307 100644 --- a/detailsView.go +++ b/detailsView.go @@ -24,7 +24,7 @@ type detailsViewData struct { // NewDetailsView create new DetailsView object and return it func NewDetailsView(session Session, params Params) DetailsView { view := new(detailsViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -34,8 +34,8 @@ func newDetailsView(session Session) View { } // Init initialize fields of DetailsView by default values -func (detailsView *detailsViewData) Init(session Session) { - detailsView.viewsContainerData.Init(session) +func (detailsView *detailsViewData) init(session Session) { + detailsView.viewsContainerData.init(session) detailsView.tag = "DetailsView" //detailsView.systemClass = "ruiDetailsView" } @@ -68,11 +68,11 @@ func (detailsView *detailsViewData) remove(tag string) { } } -func (detailsView *detailsViewData) Set(tag string, value interface{}) bool { +func (detailsView *detailsViewData) Set(tag string, value any) bool { return detailsView.set(strings.ToLower(tag), value) } -func (detailsView *detailsViewData) set(tag string, value interface{}) bool { +func (detailsView *detailsViewData) set(tag string, value any) bool { if value == nil { detailsView.remove(tag) return true @@ -110,7 +110,7 @@ func (detailsView *detailsViewData) set(tag string, value interface{}) bool { return false } if detailsView.created { - if IsDetailsExpanded(detailsView, "") { + if IsDetailsExpanded(detailsView) { updateProperty(detailsView.htmlID(), "open", "", detailsView.Session()) } else { removeProperty(detailsView.htmlID(), "open", detailsView.Session()) @@ -133,11 +133,11 @@ func (detailsView *detailsViewData) set(tag string, value interface{}) bool { return true } -func (detailsView *detailsViewData) Get(tag string) interface{} { +func (detailsView *detailsViewData) Get(tag string) any { return detailsView.get(strings.ToLower(tag)) } -func (detailsView *detailsViewData) get(tag string) interface{} { +func (detailsView *detailsViewData) get(tag string) any { return detailsView.viewsContainerData.get(tag) } @@ -148,7 +148,7 @@ func (detailsView *detailsViewData) htmlTag() string { func (detailsView *detailsViewData) htmlProperties(self View, buffer *strings.Builder) { detailsView.viewsContainerData.htmlProperties(self, buffer) buffer.WriteString(` ontoggle="detailsEvent(this)"`) - if IsDetailsExpanded(detailsView, "") { + if IsDetailsExpanded(detailsView) { buffer.WriteString(` open`) } } @@ -157,7 +157,7 @@ func (detailsView *detailsViewData) htmlSubviews(self View, buffer *strings.Buil if value, ok := detailsView.properties[Summary]; ok { switch value := value.(type) { case string: - if !GetNotTranslate(detailsView, "") { + if !GetNotTranslate(detailsView) { value, _ = detailsView.session.GetString(value) } buffer.WriteString("") @@ -186,10 +186,10 @@ func (detailsView *detailsViewData) handleCommand(self View, command string, dat } // GetDetailsSummary returns a value of the Summary property of DetailsView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDetailsSummary(view View, subviewID string) View { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if value := view.Get(Summary); value != nil { @@ -206,15 +206,7 @@ func GetDetailsSummary(view View, subviewID string) View { } // IsDetailsExpanded returns a value of the Expanded property of DetailsView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsDetailsExpanded(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Expanded); ok { - return result - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsDetailsExpanded(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Expanded, false) } diff --git a/dropDownList.go b/dropDownList.go index 1ded185..9658976 100644 --- a/dropDownList.go +++ b/dropDownList.go @@ -6,6 +6,9 @@ import ( "strings" ) +// DropDownEvent is the constant for "drop-down-event" property tag. +// The "drop-down-event" event occurs when a list item becomes selected. +// The main listener format: func(DropDownList, int), where the second argument is the item index. const DropDownEvent = "drop-down-event" // DropDownList - the interface of a drop-down list view @@ -17,14 +20,14 @@ type DropDownList interface { type dropDownListData struct { viewData items []string - disabledItems []interface{} + disabledItems []any dropDownListener []func(DropDownList, int) } // NewDropDownList create new DropDownList object and return it func NewDropDownList(session Session, params Params) DropDownList { view := new(dropDownListData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -33,11 +36,11 @@ func newDropDownList(session Session) View { return NewDropDownList(session, nil) } -func (list *dropDownListData) Init(session Session) { - list.viewData.Init(session) +func (list *dropDownListData) init(session Session) { + list.viewData.init(session) list.tag = "DropDownList" list.items = []string{} - list.disabledItems = []interface{}{} + list.disabledItems = []any{} list.dropDownListener = []func(DropDownList, int){} } @@ -66,7 +69,7 @@ func (list *dropDownListData) remove(tag string) { case DisabledItems: if len(list.disabledItems) > 0 { - list.disabledItems = []interface{}{} + list.disabledItems = []any{} if list.created { updateInnerHTML(list.htmlID(), list.session) } @@ -80,7 +83,7 @@ func (list *dropDownListData) remove(tag string) { } case Current: - oldCurrent := GetCurrent(list, "") + oldCurrent := GetCurrent(list) delete(list.properties, Current) if oldCurrent != 0 { if list.created { @@ -95,11 +98,11 @@ func (list *dropDownListData) remove(tag string) { } } -func (list *dropDownListData) Set(tag string, value interface{}) bool { +func (list *dropDownListData) Set(tag string, value any) bool { return list.set(strings.ToLower(tag), value) } -func (list *dropDownListData) set(tag string, value interface{}) bool { +func (list *dropDownListData) set(tag string, value any) bool { if value == nil { list.remove(tag) return true @@ -113,15 +116,24 @@ func (list *dropDownListData) set(tag string, value interface{}) bool { return list.setDisabledItems(value) case DropDownEvent: - return list.setDropDownListener(value) + listeners, ok := valueToEventListeners[DropDownList, int](value) + if !ok { + notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(DropDownList, int){} + } + list.dropDownListener = listeners + list.propertyChangedEvent(tag) + return true case Current: - oldCurrent := GetCurrent(list, "") + oldCurrent := GetCurrent(list) if !list.setIntProperty(Current, value) { return false } - if current := GetCurrent(list, ""); oldCurrent != current { + if current := GetCurrent(list); oldCurrent != current { if list.created { list.session.runScript(fmt.Sprintf(`selectDropDownListItem('%s', %d)`, list.htmlID(), current)) } @@ -133,7 +145,7 @@ func (list *dropDownListData) set(tag string, value interface{}) bool { return list.viewData.set(tag, value) } -func (list *dropDownListData) setItems(value interface{}) bool { +func (list *dropDownListData) setItems(value any) bool { switch value := value.(type) { case string: list.items = []string{value} @@ -155,7 +167,7 @@ func (list *dropDownListData) setItems(value interface{}) bool { list.items[i] = str.String() } - case []interface{}: + case []any: items := make([]string, 0, len(value)) for _, v := range value { switch val := v.(type) { @@ -206,16 +218,16 @@ func (list *dropDownListData) setItems(value interface{}) bool { return true } -func (list *dropDownListData) setDisabledItems(value interface{}) bool { +func (list *dropDownListData) setDisabledItems(value any) bool { switch value := value.(type) { case []int: - list.disabledItems = make([]interface{}, len(value)) + list.disabledItems = make([]any, len(value)) for i, n := range value { list.disabledItems[i] = n } - case []interface{}: - disabledItems := make([]interface{}, len(value)) + case []any: + disabledItems := make([]any, len(value)) for i, val := range value { if val == nil { notCompatibleType(DisabledItems, value) @@ -248,7 +260,7 @@ func (list *dropDownListData) setDisabledItems(value interface{}) bool { case string: values := strings.Split(value, ",") - disabledItems := make([]interface{}, len(values)) + disabledItems := make([]any, len(values)) for i, str := range values { str = strings.Trim(str, " ") if str == "" { @@ -291,69 +303,11 @@ func (list *dropDownListData) setDisabledItems(value interface{}) bool { } -func (list *dropDownListData) setDropDownListener(value interface{}) bool { - switch value := value.(type) { - case func(DropDownList, int): - list.dropDownListener = []func(DropDownList, int){value} - - case func(int): - list.dropDownListener = []func(DropDownList, int){func(_ DropDownList, index int) { - value(index) - }} - - case []func(DropDownList, int): - list.dropDownListener = value - - case []func(int): - listeners := make([]func(DropDownList, int), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(DropDownEvent, value) - return false - } - listeners[i] = func(_ DropDownList, index int) { - val(index) - } - } - list.dropDownListener = listeners - - case []interface{}: - listeners := make([]func(DropDownList, int), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(DropDownEvent, value) - return false - } - switch val := val.(type) { - case func(DropDownList, int): - listeners[i] = val - - case func(int): - listeners[i] = func(_ DropDownList, index int) { - val(index) - } - - default: - notCompatibleType(DropDownEvent, value) - return false - } - } - list.dropDownListener = listeners - - default: - notCompatibleType(DropDownEvent, value) - return false - } - - list.propertyChangedEvent(DropDownEvent) - return true -} - -func (list *dropDownListData) Get(tag string) interface{} { +func (list *dropDownListData) Get(tag string) any { return list.get(strings.ToLower(tag)) } -func (list *dropDownListData) get(tag string) interface{} { +func (list *dropDownListData) get(tag string) any { switch tag { case Items: return list.items @@ -382,9 +336,9 @@ func (list *dropDownListData) htmlTag() string { func (list *dropDownListData) htmlSubviews(self View, buffer *strings.Builder) { if list.items != nil { - current := GetCurrent(list, "") - notTranslate := GetNotTranslate(list, "") - disabledItems := GetDropDownDisabledItems(list, "") + current := GetCurrent(list) + notTranslate := GetNotTranslate(list) + disabledItems := GetDropDownDisabledItems(list) for i, item := range list.items { disabled := false for _, index := range disabledItems { @@ -418,7 +372,7 @@ func (list *dropDownListData) htmlProperties(self View, buffer *strings.Builder) func (list *dropDownListData) htmlDisabledProperties(self View, buffer *strings.Builder) { list.viewData.htmlDisabledProperties(self, buffer) - if IsDisabled(list, "") { + if IsDisabled(list) { buffer.WriteString(`disabled`) } } @@ -435,7 +389,7 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data case "itemSelected": if text, ok := data.PropertyValue("number"); ok { if number, err := strconv.Atoi(text); err == nil { - if GetCurrent(list, "") != number && number >= 0 && number < len(list.items) { + if GetCurrent(list) != number && number >= 0 && number < len(list.items) { list.properties[Current] = number list.onSelectedItemChanged(number) } @@ -450,19 +404,17 @@ func (list *dropDownListData) handleCommand(self View, command string, data Data return true } -func GetDropDownListeners(view View) []func(DropDownList, int) { - if value := view.Get(DropDownEvent); value != nil { - if listeners, ok := value.([]func(DropDownList, int)); ok { - return listeners - } - } - return []func(DropDownList, int){} +// GetDropDownListeners returns the "drop-down-event" listener list. If there are no listeners then the empty list is returned. +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetDropDownListeners(view View, subviewID ...string) []func(DropDownList, int) { + return getEventListeners[DropDownList, int](view, subviewID, DropDownEvent) } -// func GetDropDownItems return the view items list -func GetDropDownItems(view View, subviewID string) []string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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. +func GetDropDownItems(view View, subviewID ...string) []string { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if list, ok := view.(DropDownList); ok { @@ -472,14 +424,16 @@ func GetDropDownItems(view View, subviewID string) []string { return []string{} } -// func GetDropDownDisabledItems return the list of disabled item indexes -func GetDropDownDisabledItems(view View, subviewID string) []int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// GetDropDownDisabledItems return the list of DropDownList disabled item indexes. +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } + if view != nil { if value := view.Get(DisabledItems); value != nil { - if values, ok := value.([]interface{}); ok { + if values, ok := value.([]any); ok { count := len(values) if count > 0 { result := make([]int, 0, count) diff --git a/editView.go b/editView.go index 362d256..f60f3b4 100644 --- a/editView.go +++ b/editView.go @@ -48,7 +48,7 @@ type editViewData struct { // NewEditView create new EditView object and return it func NewEditView(session Session, params Params) EditView { view := new(editViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -57,8 +57,8 @@ func newEditView(session Session) View { return NewEditView(session, nil) } -func (edit *editViewData) Init(session Session) { - edit.viewData.Init(session) +func (edit *editViewData) init(session Session) { + edit.viewData.init(session) edit.textChangeListeners = []func(EditView, string){} edit.tag = "EditView" } @@ -132,7 +132,7 @@ func (edit *editViewData) remove(tag string) { case Text: if exists { - oldText := GetText(edit, "") + oldText := GetText(edit) delete(edit.properties, tag) if oldText != "" { edit.textChanged("") @@ -144,7 +144,7 @@ func (edit *editViewData) remove(tag string) { case EditViewPattern: if exists { - oldText := GetEditViewPattern(edit, "") + oldText := GetEditViewPattern(edit) delete(edit.properties, tag) if oldText != "" { if edit.created { @@ -156,7 +156,7 @@ func (edit *editViewData) remove(tag string) { case EditViewType: if exists { - oldType := GetEditViewType(edit, "") + oldType := GetEditViewType(edit) delete(edit.properties, tag) if oldType != 0 { if edit.created { @@ -168,10 +168,10 @@ func (edit *editViewData) remove(tag string) { case EditWrap: if exists { - oldWrap := IsEditViewWrap(edit, "") + oldWrap := IsEditViewWrap(edit) delete(edit.properties, tag) - if GetEditViewType(edit, "") == MultiLineText { - if wrap := IsEditViewWrap(edit, ""); wrap != oldWrap { + if GetEditViewType(edit) == MultiLineText { + if wrap := IsEditViewWrap(edit); wrap != oldWrap { if edit.created { if wrap { updateProperty(edit.htmlID(), "wrap", "soft", edit.session) @@ -190,11 +190,11 @@ func (edit *editViewData) remove(tag string) { } } -func (edit *editViewData) Set(tag string, value interface{}) bool { +func (edit *editViewData) Set(tag string, value any) bool { return edit.set(edit.normalizeTag(tag), value) } -func (edit *editViewData) set(tag string, value interface{}) bool { +func (edit *editViewData) set(tag string, value any) bool { if value == nil { edit.remove(tag) return true @@ -202,13 +202,13 @@ func (edit *editViewData) set(tag string, value interface{}) bool { switch tag { case Text: - oldText := GetText(edit, "") + oldText := GetText(edit) if text, ok := value.(string); ok { edit.properties[Text] = text - if text = GetText(edit, ""); oldText != text { + if text = GetText(edit); oldText != text { edit.textChanged(text) if edit.created { - if GetEditViewType(edit, "") == MultiLineText { + if GetEditViewType(edit) == MultiLineText { updateInnerHTML(edit.htmlID(), edit.Session()) } else { text = strings.ReplaceAll(text, `"`, `\"`) @@ -224,10 +224,10 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case Hint: - oldText := GetHint(edit, "") + oldText := GetHint(edit) if text, ok := value.(string); ok { edit.properties[Hint] = text - if text = GetHint(edit, ""); oldText != text { + if text = GetHint(edit); oldText != text { if edit.created { if text != "" { updateProperty(edit.htmlID(), "placeholder", text, edit.session) @@ -242,9 +242,9 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case MaxLength: - oldMaxLength := GetMaxLength(edit, "") + oldMaxLength := GetMaxLength(edit) if edit.setIntProperty(MaxLength, value) { - if maxLength := GetMaxLength(edit, ""); maxLength != oldMaxLength { + if maxLength := GetMaxLength(edit); maxLength != oldMaxLength { if edit.created { if maxLength > 0 { updateProperty(edit.htmlID(), "maxlength", strconv.Itoa(maxLength), edit.session) @@ -261,7 +261,7 @@ func (edit *editViewData) set(tag string, value interface{}) bool { case ReadOnly: if edit.setBoolProperty(ReadOnly, value) { if edit.created { - if IsReadOnly(edit, "") { + if IsReadOnly(edit) { updateProperty(edit.htmlID(), ReadOnly, "", edit.session) } else { removeProperty(edit.htmlID(), ReadOnly, edit.session) @@ -275,7 +275,7 @@ func (edit *editViewData) set(tag string, value interface{}) bool { case Spellcheck: if edit.setBoolProperty(Spellcheck, value) { if edit.created { - updateBoolProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit, ""), edit.session) + updateBoolProperty(edit.htmlID(), Spellcheck, IsSpellcheck(edit), edit.session) } edit.propertyChangedEvent(tag) return true @@ -283,10 +283,10 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case EditViewPattern: - oldText := GetEditViewPattern(edit, "") + oldText := GetEditViewPattern(edit) if text, ok := value.(string); ok { edit.properties[EditViewPattern] = text - if text = GetEditViewPattern(edit, ""); oldText != text { + if text = GetEditViewPattern(edit); oldText != text { if edit.created { if text != "" { updateProperty(edit.htmlID(), Pattern, text, edit.session) @@ -301,9 +301,9 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case EditViewType: - oldType := GetEditViewType(edit, "") + oldType := GetEditViewType(edit) if edit.setEnumProperty(EditViewType, value, enumProperties[EditViewType].values) { - if GetEditViewType(edit, "") != oldType { + if GetEditViewType(edit) != oldType { if edit.created { updateInnerHTML(edit.parentHTMLID(), edit.session) } @@ -314,10 +314,10 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case EditWrap: - oldWrap := IsEditViewWrap(edit, "") + oldWrap := IsEditViewWrap(edit) if edit.setBoolProperty(EditWrap, value) { - if GetEditViewType(edit, "") == MultiLineText { - if wrap := IsEditViewWrap(edit, ""); wrap != oldWrap { + if GetEditViewType(edit) == MultiLineText { + if wrap := IsEditViewWrap(edit); wrap != oldWrap { if edit.created { if wrap { updateProperty(edit.htmlID(), "wrap", "soft", edit.session) @@ -333,80 +333,34 @@ func (edit *editViewData) set(tag string, value interface{}) bool { return false case EditTextChangedEvent: - ok := edit.setChangeListeners(value) + listeners, ok := valueToEventListeners[EditView, string](value) if !ok { notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(EditView, string){} } + edit.textChangeListeners = listeners edit.propertyChangedEvent(tag) - return ok + return true } return edit.viewData.set(tag, value) } -func (edit *editViewData) setChangeListeners(value interface{}) bool { - switch value := value.(type) { - case func(EditView, string): - edit.textChangeListeners = []func(EditView, string){value} - - case func(string): - fn := func(_ EditView, text string) { - value(text) - } - edit.textChangeListeners = []func(EditView, string){fn} - - case []func(EditView, string): - edit.textChangeListeners = value - - case []func(string): - listeners := make([]func(EditView, string), len(value)) - for i, v := range value { - if v == nil { - return false - } - listeners[i] = func(_ EditView, text string) { - v(text) - } - } - edit.textChangeListeners = listeners - - case []interface{}: - listeners := make([]func(EditView, string), len(value)) - for i, v := range value { - if v == nil { - return false - } - switch v := v.(type) { - case func(EditView, string): - listeners[i] = v - - case func(string): - listeners[i] = func(_ EditView, text string) { - v(text) - } - - default: - return false - } - } - edit.textChangeListeners = listeners - - default: - return false - } - return true -} - -func (edit *editViewData) Get(tag string) interface{} { +func (edit *editViewData) Get(tag string) any { return edit.get(edit.normalizeTag(tag)) } -func (edit *editViewData) get(tag string) interface{} { +func (edit *editViewData) get(tag string) any { + if tag == EditTextChangedEvent { + return edit.textChangeListeners + } return edit.viewData.get(tag) } func (edit *editViewData) AppendText(text string) { - if GetEditViewType(edit, "") == MultiLineText { + if GetEditViewType(edit) == MultiLineText { if value := edit.getRaw(Text); value != nil { if textValue, ok := value.(string); ok { textValue += text @@ -425,7 +379,7 @@ func (edit *editViewData) AppendText(text string) { } edit.set(Text, text) } else { - edit.set(Text, GetText(edit, "")+text) + edit.set(Text, GetText(edit)+text) } } @@ -437,7 +391,7 @@ func (edit *editViewData) textChanged(newText string) { } func (edit *editViewData) htmlTag() string { - if GetEditViewType(edit, "") == MultiLineText { + if GetEditViewType(edit) == MultiLineText { return "textarea" } return "input" @@ -447,14 +401,14 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { edit.viewData.htmlProperties(self, buffer) writeSpellcheck := func() { - if spellcheck := IsSpellcheck(edit, ""); spellcheck { + if spellcheck := IsSpellcheck(edit); spellcheck { buffer.WriteString(` spellcheck="true"`) } else { buffer.WriteString(` spellcheck="false"`) } } - editType := GetEditViewType(edit, "") + editType := GetEditViewType(edit) switch editType { case SingleLineText: buffer.WriteString(` type="text" inputmode="text"`) @@ -476,7 +430,7 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { buffer.WriteString(` type="tel" inputmode="tel"`) case MultiLineText: - if IsEditViewWrap(edit, "") { + if IsEditViewWrap(edit) { buffer.WriteString(` wrap="soft"`) } else { buffer.WriteString(` wrap="off"`) @@ -484,11 +438,11 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { writeSpellcheck() } - if IsReadOnly(edit, "") { + if IsReadOnly(edit) { buffer.WriteString(` readonly`) } - if maxLength := GetMaxLength(edit, ""); maxLength > 0 { + if maxLength := GetMaxLength(edit); maxLength > 0 { buffer.WriteString(` maxlength="`) buffer.WriteString(strconv.Itoa(maxLength)) buffer.WriteByte('"') @@ -501,21 +455,21 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { return textToJS(text) } - if hint := GetHint(edit, ""); hint != "" { + if hint := GetHint(edit); hint != "" { buffer.WriteString(` placeholder="`) buffer.WriteString(convertText(hint)) buffer.WriteByte('"') } buffer.WriteString(` oninput="editViewInputEvent(this)"`) - if pattern := GetEditViewPattern(edit, ""); pattern != "" { + if pattern := GetEditViewPattern(edit); pattern != "" { buffer.WriteString(` pattern="`) buffer.WriteString(convertText(pattern)) buffer.WriteByte('"') } if editType != MultiLineText { - if text := GetText(edit, ""); text != "" { + if text := GetText(edit); text != "" { buffer.WriteString(` value="`) buffer.WriteString(convertText(text)) buffer.WriteByte('"') @@ -524,25 +478,25 @@ func (edit *editViewData) htmlProperties(self View, buffer *strings.Builder) { } func (edit *editViewData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` disabled`) } edit.viewData.htmlDisabledProperties(self, buffer) } func (edit *editViewData) htmlSubviews(self View, buffer *strings.Builder) { - if GetEditViewType(edit, "") == MultiLineText { - buffer.WriteString(textToJS(GetText(edit, ""))) + if GetEditViewType(edit) == MultiLineText { + buffer.WriteString(textToJS(GetText(edit))) } } func (edit *editViewData) handleCommand(self View, command string, data DataObject) bool { switch command { case "textChanged": - oldText := GetText(edit, "") + oldText := GetText(edit) if text, ok := data.PropertyValue("text"); ok { edit.properties[Text] = text - if text := GetText(edit, ""); text != oldText { + if text := GetText(edit); text != oldText { edit.textChanged(text) } } @@ -553,10 +507,10 @@ func (edit *editViewData) handleCommand(self View, command string, data DataObje } // GetText returns a text of the EditView subview. -// If the second argument (subviewID) is "" then a text of the first argument (view) is returned. -func GetText(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if value := view.getRaw(Text); value != nil { @@ -569,10 +523,10 @@ func GetText(view View, subviewID string) string { } // GetHint returns a hint text of the subview. -// If the second argument (subviewID) is "" then a text of the first argument (view) is returned. -func GetHint(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if text, ok := stringProperty(view, Hint, view.Session()); ok { @@ -590,82 +544,41 @@ func GetHint(view View, subviewID string) string { } // GetMaxLength returns a maximal lenght of EditView. If a maximal lenght is not limited then 0 is returned -// If the second argument (subviewID) is "" then a value of the first argument (view) is returned. -func GetMaxLength(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := intStyledProperty(view, MaxLength, 0); ok { - return result - } - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned. +func GetMaxLength(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, MaxLength, 0) } // IsReadOnly returns the true if a EditView works in read only mode. -// If the second argument (subviewID) is "" then a value of the first argument (view) is returned. -func IsReadOnly(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, ReadOnly); ok { - return result - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned. +func IsReadOnly(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, ReadOnly, false) } // IsSpellcheck returns a value of the Spellcheck property of EditView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsSpellcheck(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if spellcheck, ok := boolStyledProperty(view, Spellcheck); ok { - return spellcheck - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsSpellcheck(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Spellcheck, false) } // GetTextChangedListeners returns the TextChangedListener list of an EditView or MultiLineEditView subview. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextChangedListeners(view View, subviewID string) []func(EditView, string) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(EditTextChangedEvent); value != nil { - if result, ok := value.([]func(EditView, string)); ok { - return result - } - } - } - return []func(EditView, string){} +// 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) { + return getEventListeners[EditView, string](view, subviewID, EditTextChangedEvent) } // GetEditViewType returns a value of the Type property of EditView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetEditViewType(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return SingleLineText - } - t, _ := enumStyledProperty(view, EditViewType, SingleLineText) - return t +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetEditViewType(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, EditViewType, SingleLineText, false) } // GetEditViewPattern returns a value of the Pattern property of EditView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetEditViewPattern(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if pattern, ok := stringProperty(view, EditViewPattern, view.Session()); ok { @@ -683,22 +596,13 @@ func GetEditViewPattern(view View, subviewID string) string { } // IsEditViewWrap returns a value of the EditWrap property of MultiLineEditView. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsEditViewWrap(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if wrap, ok := boolStyledProperty(view, EditWrap); ok { - return wrap - } - } - return false - +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsEditViewWrap(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, EditWrap, false) } // AppendEditText appends the text to the EditView content. -// If the second argument (subviewID) is "" 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 AppendEditText(view View, subviewID string, text string) { if subviewID != "" { if edit := EditViewByID(view, subviewID); edit != nil { @@ -713,14 +617,7 @@ func AppendEditText(view View, subviewID string, text string) { } // GetCaretColor returns the color of the text input carret. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCaretColor(view View, subviewID string) Color { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - t, _ := colorStyledProperty(view, CaretColor) - return t +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetCaretColor(view View, subviewID ...string) Color { + return colorStyledProperty(view, subviewID, CaretColor, false) } diff --git a/filePicker.go b/filePicker.go index 980295f..63203eb 100644 --- a/filePicker.go +++ b/filePicker.go @@ -72,7 +72,7 @@ func (file *FileInfo) initBy(node DataValue) { // NewFilePicker create new FilePicker object and return it func NewFilePicker(session Session, params Params) FilePicker { view := new(filePickerData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -81,8 +81,8 @@ func newFilePicker(session Session) View { return NewFilePicker(session, nil) } -func (picker *filePickerData) Init(session Session) { - picker.viewData.Init(session) +func (picker *filePickerData) init(session Session) { + picker.viewData.init(session) picker.tag = "FilePicker" picker.files = []FileInfo{} picker.loader = map[int]func(FileInfo, []byte){} @@ -139,11 +139,11 @@ func (picker *filePickerData) remove(tag string) { } } -func (picker *filePickerData) Set(tag string, value interface{}) bool { +func (picker *filePickerData) Set(tag string, value any) bool { return picker.set(strings.ToLower(tag), value) } -func (picker *filePickerData) set(tag string, value interface{}) bool { +func (picker *filePickerData) set(tag string, value any) bool { if value == nil { picker.remove(tag) return true @@ -151,57 +151,14 @@ func (picker *filePickerData) set(tag string, value interface{}) bool { switch tag { case FileSelectedEvent: - switch value := value.(type) { - case func(FilePicker, []FileInfo): - picker.fileSelectedListeners = []func(FilePicker, []FileInfo){value} - - case func([]FileInfo): - fn := func(_ FilePicker, files []FileInfo) { - value(files) - } - picker.fileSelectedListeners = []func(FilePicker, []FileInfo){fn} - - case []func(FilePicker, []FileInfo): - picker.fileSelectedListeners = value - - case []func([]FileInfo): - listeners := make([]func(FilePicker, []FileInfo), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(_ FilePicker, files []FileInfo) { - val(files) - } - } - picker.fileSelectedListeners = listeners - - case []interface{}: - listeners := make([]func(FilePicker, []FileInfo), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - switch val := val.(type) { - case func(FilePicker, []FileInfo): - listeners[i] = val - - case func([]FileInfo): - listeners[i] = func(_ FilePicker, files []FileInfo) { - val(files) - } - - default: - notCompatibleType(tag, val) - return false - } - } - picker.fileSelectedListeners = listeners + listeners, ok := valueToEventListeners[FilePicker, []FileInfo](value) + if !ok { + notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(FilePicker, []FileInfo){} } + picker.fileSelectedListeners = listeners picker.propertyChangedEvent(tag) return true @@ -294,7 +251,7 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) } buffer.WriteString(` type="file"`) - if multiple, ok := boolStyledProperty(picker, Multiple); ok && multiple { + if IsMultipleFilePicker(picker) { buffer.WriteString(` multiple`) } @@ -305,7 +262,7 @@ func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) } func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` disabled`) } picker.viewData.htmlDisabledProperties(self, buffer) @@ -377,9 +334,14 @@ func (picker *filePickerData) handleCommand(self View, command string, data Data // GetFilePickerFiles returns the list of FilePicker selected files // If there are no files selected then an empty slice is returned (the result is always not nil) -// If the second argument (subviewID) is "" then selected files of the first argument (view) is returned -func GetFilePickerFiles(view View, subviewID string) []FileInfo { - if picker := FilePickerByID(view, subviewID); picker != nil { +// If the second argument (subviewID) is not specified or it is "" then selected files of the first argument (view) is returned +func GetFilePickerFiles(view View, subviewID ...string) []FileInfo { + subview := "" + if len(subviewID) > 0 { + subview = subviewID[0] + } + + if picker := FilePickerByID(view, subview); picker != nil { return picker.Files() } return []FileInfo{} @@ -395,24 +357,16 @@ func LoadFilePickerFile(view View, subviewID string, file FileInfo, result func( } // IsMultipleFilePicker returns "true" if multiple files can be selected in the FilePicker, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsMultipleFilePicker(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Multiple); ok { - return result - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsMultipleFilePicker(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Multiple, false) } // GetFilePickerAccept returns sets the list of allowed file extensions or MIME types. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetFilePickerAccept(view View, subviewID string) []string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { accept, ok := stringProperty(view, Accept, view.Session()) @@ -434,17 +388,7 @@ func GetFilePickerAccept(view View, subviewID string) []string { // GetFileSelectedListeners returns the "file-selected-event" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetFileSelectedListeners(view View, subviewID string) []func(FilePicker, []FileInfo) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(FileSelectedEvent); value != nil { - if result, ok := value.([]func(FilePicker, []FileInfo)); ok { - return result - } - } - } - return []func(FilePicker, []FileInfo){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetFileSelectedListeners(view View, subviewID ...string) []func(FilePicker, []FileInfo) { + return getEventListeners[FilePicker, []FileInfo](view, subviewID, FileSelectedEvent) } diff --git a/focusEvents.go b/focusEvents.go index 025c702..963f159 100644 --- a/focusEvents.go +++ b/focusEvents.go @@ -20,22 +20,22 @@ const ( LostFocusEvent = "lost-focus-event" ) -func valueToFocusListeners(value interface{}) ([]func(View), bool) { +func valueToNoParamListeners[V any](value any) ([]func(V), bool) { if value == nil { return nil, true } switch value := value.(type) { - case func(View): - return []func(View){value}, true + case func(V): + return []func(V){value}, true case func(): - fn := func(View) { + fn := func(V) { value() } - return []func(View){fn}, true + return []func(V){fn}, true - case []func(View): + case []func(V): if len(value) == 0 { return nil, true } @@ -51,33 +51,33 @@ func valueToFocusListeners(value interface{}) ([]func(View), bool) { if count == 0 { return nil, true } - listeners := make([]func(View), count) + listeners := make([]func(V), count) for i, v := range value { if v == nil { return nil, false } - listeners[i] = func(View) { + listeners[i] = func(V) { v() } } return listeners, true - case []interface{}: + case []any: count := len(value) if count == 0 { return nil, true } - listeners := make([]func(View), count) + listeners := make([]func(V), count) for i, v := range value { if v == nil { return nil, false } switch v := v.(type) { - case func(View): + case func(V): listeners[i] = v case func(): - listeners[i] = func(View) { + listeners[i] = func(V) { v() } @@ -96,8 +96,8 @@ var focusEvents = map[string]struct{ jsEvent, jsFunc string }{ LostFocusEvent: {jsEvent: "onblur", jsFunc: "blurEvent"}, } -func (view *viewData) setFocusListener(tag string, value interface{}) bool { - listeners, ok := valueToFocusListeners(value) +func (view *viewData) setFocusListener(tag string, value any) bool { + listeners, ok := valueToNoParamListeners[View](value) if !ok { notCompatibleType(tag, value) return false @@ -125,10 +125,11 @@ func (view *viewData) removeFocusListener(tag string) { } } -func getFocusListeners(view View, subviewID string, tag string) []func(View) { - if subviewID != "" { - view = ViewByID(view, subviewID) +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 { @@ -148,13 +149,13 @@ func focusEventsHtml(view View, buffer *strings.Builder) { } // GetFocusListeners returns a FocusListener list. If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetFocusListeners(view View, subviewID string) []func(View) { +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetFocusListeners(view View, subviewID ...string) []func(View) { return getFocusListeners(view, subviewID, FocusEvent) } // GetLostFocusListeners returns a LostFocusListener list. If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetLostFocusListeners(view View, subviewID string) []func(View) { +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetLostFocusListeners(view View, subviewID ...string) []func(View) { return getFocusListeners(view, subviewID, LostFocusEvent) } diff --git a/go.mod b/go.mod index ff93f55..4ad2028 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/anoshenko/rui -go 1.17 +go 1.18 require github.com/gorilla/websocket v1.5.0 diff --git a/gridLayout.go b/gridLayout.go index 3de52bf..674d561 100644 --- a/gridLayout.go +++ b/gridLayout.go @@ -17,7 +17,7 @@ type gridLayoutData struct { // NewGridLayout create new GridLayout object and return it func NewGridLayout(session Session, params Params) GridLayout { view := new(gridLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -27,8 +27,8 @@ func newGridLayout(session Session) View { } // Init initialize fields of GridLayout by default values -func (gridLayout *gridLayoutData) Init(session Session) { - gridLayout.viewsContainerData.Init(session) +func (gridLayout *gridLayoutData) init(session Session) { + gridLayout.viewsContainerData.init(session) gridLayout.tag = "GridLayout" gridLayout.systemClass = "ruiGridLayout" } @@ -37,15 +37,17 @@ func (gridLayout *gridLayoutData) String() string { return getViewString(gridLayout) } -func (style *viewStyle) setGridCellSize(tag string, value interface{}) bool { +func (style *viewStyle) setGridCellSize(tag string, value any) bool { setValues := func(values []string) bool { count := len(values) if count > 1 { - sizes := make([]interface{}, count) + sizes := make([]any, count) for i, val := range values { val = strings.Trim(val, " \t\n\r") if isConstantName(val) { sizes[i] = val + } else if fn := parseSizeFunc(val); fn != nil { + sizes[i] = SizeUnit{Type: SizeFunction, Function: fn} } else if size, err := stringToSizeUnit(val); err == nil { sizes[i] = size } else { @@ -99,13 +101,13 @@ func (style *viewStyle) setGridCellSize(tag string, value interface{}) bool { return false } - case []interface{}: + case []any: count := len(value) if count == 0 { invalidPropertyValue(tag, value) return false } - sizes := make([]interface{}, count) + sizes := make([]any, count) for i, val := range value { switch val := val.(type) { case SizeUnit: @@ -145,7 +147,7 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string { case 1: if cellSize[0].Type != Auto { - return `repeat(auto-fill, ` + cellSize[0].cssString(`auto`) + `)` + return `repeat(auto-fill, ` + cellSize[0].cssString(`auto`, session) + `)` } default: @@ -161,14 +163,14 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string { } if !allAuto { if allEqual { - return fmt.Sprintf(`repeat(%d, %s)`, len(cellSize), cellSize[0].cssString(`auto`)) + return fmt.Sprintf(`repeat(%d, %s)`, len(cellSize), cellSize[0].cssString(`auto`, session)) } buffer := allocStringBuilder() defer freeStringBuilder(buffer) for _, size := range cellSize { buffer.WriteRune(' ') - buffer.WriteString(size.cssString(`auto`)) + buffer.WriteString(size.cssString(`auto`, session)) } return buffer.String() } @@ -195,14 +197,14 @@ func (gridLayout *gridLayoutData) normalizeTag(tag string) string { return tag } -func (gridLayout *gridLayoutData) Get(tag string) interface{} { +func (gridLayout *gridLayoutData) Get(tag string) any { return gridLayout.get(gridLayout.normalizeTag(tag)) } -func (gridLayout *gridLayoutData) get(tag string) interface{} { +func (gridLayout *gridLayoutData) get(tag string) any { if tag == Gap { - rowGap := GetGridRowGap(gridLayout, "") - columnGap := GetGridColumnGap(gridLayout, "") + rowGap := GetGridRowGap(gridLayout) + columnGap := GetGridColumnGap(gridLayout) if rowGap.Equal(columnGap) { return rowGap } @@ -239,22 +241,18 @@ func (gridLayout *gridLayoutData) remove(tag string) { } } -func (gridLayout *gridLayoutData) Set(tag string, value interface{}) bool { +func (gridLayout *gridLayoutData) Set(tag string, value any) bool { return gridLayout.set(gridLayout.normalizeTag(tag), value) } -func (gridLayout *gridLayoutData) set(tag string, value interface{}) bool { +func (gridLayout *gridLayoutData) set(tag string, value any) bool { if value == nil { gridLayout.remove(tag) return true } if tag == Gap { - if gridLayout.set(GridRowGap, value) && gridLayout.set(GridColumnGap, value) { - gridLayout.propertyChangedEvent(Gap) - return true - } - return false + return gridLayout.set(GridRowGap, value) && gridLayout.set(GridColumnGap, value) } if gridLayout.viewsContainerData.set(tag, value) { @@ -285,7 +283,7 @@ func gridCellSizes(properties Properties, tag string, session Session) []SizeUni case SizeUnit: return []SizeUnit{value} - case []interface{}: + case []any: result := make([]SizeUnit, len(value)) for i, val := range value { result[i] = AutoSize() @@ -323,53 +321,29 @@ func (gridLayout *gridLayoutData) cssStyle(self View, builder cssBuilder) { */ // GetCellVerticalAlign returns the vertical align of a GridLayout cell content: TopAlign (0), BottomAlign (1), CenterAlign (2), StretchAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCellVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumStyledProperty(view, CellVerticalAlign, StretchAlign); ok { - return align - } - } - return StretchAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetCellVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, CellVerticalAlign, StretchAlign, false) } // GetCellHorizontalAlign returns the vertical align of a GridLayout cell content: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCellHorizontalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumStyledProperty(view, CellHorizontalAlign, StretchAlign); ok { - return align - } - } - return StretchAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetCellHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, CellHorizontalAlign, StretchAlign, false) } // GetGridAutoFlow returns the value of the "grid-auto-flow" property -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetGridAutoFlow(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumStyledProperty(view, GridAutoFlow, 0); ok { - return align - } - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetGridAutoFlow(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, GridAutoFlow, 0, false) } // GetCellWidth returns the width of a GridLayout cell. If the result is an empty array, then the width is not set. // If the result is a single value array, then the width of all cell is equal. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCellWidth(view View, subviewID string) []SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return gridCellSizes(view, CellWidth, view.Session()) @@ -379,10 +353,10 @@ func GetCellWidth(view View, subviewID string) []SizeUnit { // GetCellHeight returns the height of a GridLayout cell. If the result is an empty array, then the height is not set. // If the result is a single value array, then the height of all cell is equal. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCellHeight(view View, subviewID string) []SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return gridCellSizes(view, CellHeight, view.Session()) @@ -391,29 +365,13 @@ func GetCellHeight(view View, subviewID string) []SizeUnit { } // GetGridRowGap returns the gap between GridLayout rows. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetGridRowGap(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, GridRowGap); ok { - return result - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetGridRowGap(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, GridRowGap, false) } // GetGridColumnGap returns the gap between GridLayout columns. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetGridColumnGap(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, GridColumnGap); ok { - return result - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetGridColumnGap(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, GridColumnGap, false) } diff --git a/imageView.go b/imageView.go index 79bfbac..4ed1ae4 100644 --- a/imageView.go +++ b/imageView.go @@ -54,7 +54,7 @@ type imageViewData struct { // NewImageView create new ImageView object and return it func NewImageView(session Session, params Params) ImageView { view := new(imageViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -64,8 +64,8 @@ func newImageView(session Session) View { } // Init initialize fields of imageView by default values -func (imageView *imageViewData) Init(session Session) { - imageView.viewData.Init(session) +func (imageView *imageViewData) init(session Session) { + imageView.viewData.init(session) imageView.tag = "ImageView" //imageView.systemClass = "ruiImageView" @@ -114,82 +114,11 @@ func (imageView *imageViewData) remove(tag string) { } } -func (imageView *imageViewData) Set(tag string, value interface{}) bool { +func (imageView *imageViewData) Set(tag string, value any) bool { return imageView.set(imageView.normalizeTag(tag), value) } -func valueToImageListeners(value interface{}) ([]func(ImageView), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(ImageView): - return []func(ImageView){value}, true - - case func(): - fn := func(ImageView) { - value() - } - return []func(ImageView){fn}, true - - case []func(ImageView): - 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(ImageView), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(ImageView) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(ImageView), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(ImageView): - listeners[i] = v - - case func(): - listeners[i] = func(ImageView) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - -func (imageView *imageViewData) set(tag string, value interface{}) bool { +func (imageView *imageViewData) set(tag string, value any) bool { if value == nil { imageView.remove(tag) return true @@ -228,8 +157,12 @@ func (imageView *imageViewData) set(tag string, value interface{}) bool { notCompatibleType(tag, value) case LoadedEvent, ErrorEvent: - if listeners, ok := valueToImageListeners(value); ok { - imageView.properties[tag] = listeners + if listeners, ok := valueToNoParamListeners[ImageView](value); ok { + if listeners == nil { + delete(imageView.properties, tag) + } else { + imageView.properties[tag] = listeners + } return true } @@ -248,7 +181,7 @@ func (imageView *imageViewData) set(tag string, value interface{}) bool { return false } -func (imageView *imageViewData) Get(tag string) interface{} { +func (imageView *imageViewData) Get(tag string) any { return imageView.viewData.get(imageView.normalizeTag(tag)) } @@ -311,7 +244,7 @@ func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builde } } - if text := GetImageViewAltText(imageView, ""); text != "" { + if text := GetImageViewAltText(imageView); text != "" { buffer.WriteString(` alt="`) buffer.WriteString(textToJS(text)) buffer.WriteString(`"`) @@ -333,8 +266,8 @@ func (imageView *imageViewData) cssStyle(self View, builder cssBuilder) { builder.add("object-fit", "none") } - vAlign := GetImageViewVerticalAlign(imageView, "") - hAlign := GetImageViewHorizontalAlign(imageView, "") + vAlign := GetImageViewVerticalAlign(imageView) + hAlign := GetImageViewHorizontalAlign(imageView) if vAlign != CenterAlign || hAlign != CenterAlign { var position string switch hAlign { @@ -390,10 +323,10 @@ func (imageView *imageViewData) CurrentSource() string { } // GetImageViewSource returns the image URL of an ImageView subview. -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetImageViewSource(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -406,10 +339,10 @@ func GetImageViewSource(view View, subviewID string) string { } // GetImageViewAltText returns an alternative text description of an ImageView subview. -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetImageViewAltText(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -425,40 +358,19 @@ func GetImageViewAltText(view View, subviewID string) string { // GetImageViewFit returns how the content of a replaced ImageView subview: // NoneFit (0), ContainFit (1), CoverFit (2), FillFit (3), or ScaleDownFit (4). -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetImageViewFit(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - - if value, ok := enumStyledProperty(view, Fit, NoneFit); ok { - return value - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned +func GetImageViewFit(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, Fit, NoneFit, false) } // GetImageViewVerticalAlign return the vertical align of an ImageView subview: TopAlign (0), BottomAlign (1), CenterAlign (2) -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetImageViewVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - - if align, ok := enumStyledProperty(view, ImageVerticalAlign, LeftAlign); ok { - return align - } - return CenterAlign +// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned +func GetImageViewVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ImageVerticalAlign, LeftAlign, false) } // GetImageViewHorizontalAlign return the vertical align of an ImageView subview: LeftAlign (0), RightAlign (1), CenterAlign (2) -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetImageViewHorizontalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - - if align, ok := enumStyledProperty(view, ImageHorizontalAlign, LeftAlign); ok { - return align - } - return CenterAlign +// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned +func GetImageViewHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ImageHorizontalAlign, LeftAlign, false) } diff --git a/keyEvents.go b/keyEvents.go index 1c11f47..5778290 100644 --- a/keyEvents.go +++ b/keyEvents.go @@ -50,34 +50,52 @@ type KeyEvent struct { MetaKey bool } -func valueToKeyListeners(value interface{}) ([]func(View, KeyEvent), bool) { +func (event *KeyEvent) init(data DataObject) { + getBool := func(tag string) bool { + if value, ok := data.PropertyValue(tag); ok && value == "1" { + return true + } + return false + } + + event.Key, _ = data.PropertyValue("key") + event.Code, _ = data.PropertyValue("code") + event.TimeStamp = getTimeStamp(data) + event.Repeat = getBool("repeat") + event.CtrlKey = getBool("ctrlKey") + event.ShiftKey = getBool("shiftKey") + event.AltKey = getBool("altKey") + 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(View, KeyEvent): - return []func(View, KeyEvent){value}, true + case func(V, E): + return []func(V, E){value}, true - case func(KeyEvent): - fn := func(_ View, event KeyEvent) { + case func(E): + fn := func(_ V, event E) { value(event) } - return []func(View, KeyEvent){fn}, true + return []func(V, E){fn}, true - case func(View): - fn := func(view View, _ KeyEvent) { + case func(V): + fn := func(view V, _ E) { value(view) } - return []func(View, KeyEvent){fn}, true + return []func(V, E){fn}, true case func(): - fn := func(View, KeyEvent) { + fn := func(V, E) { value() } - return []func(View, KeyEvent){fn}, true + return []func(V, E){fn}, true - case []func(View, KeyEvent): + case []func(V, E): if len(value) == 0 { return nil, true } @@ -88,33 +106,33 @@ func valueToKeyListeners(value interface{}) ([]func(View, KeyEvent), bool) { } return value, true - case []func(KeyEvent): + case []func(E): count := len(value) if count == 0 { return nil, true } - listeners := make([]func(View, KeyEvent), count) + listeners := make([]func(V, E), count) for i, v := range value { if v == nil { return nil, false } - listeners[i] = func(_ View, event KeyEvent) { + listeners[i] = func(_ V, event E) { v(event) } } return listeners, true - case []func(View): + case []func(V): count := len(value) if count == 0 { return nil, true } - listeners := make([]func(View, KeyEvent), count) + listeners := make([]func(V, E), count) for i, v := range value { if v == nil { return nil, false } - listeners[i] = func(view View, _ KeyEvent) { + listeners[i] = func(view V, _ E) { v(view) } } @@ -125,43 +143,43 @@ func valueToKeyListeners(value interface{}) ([]func(View, KeyEvent), bool) { if count == 0 { return nil, true } - listeners := make([]func(View, KeyEvent), count) + listeners := make([]func(V, E), count) for i, v := range value { if v == nil { return nil, false } - listeners[i] = func(View, KeyEvent) { + listeners[i] = func(V, E) { v() } } return listeners, true - case []interface{}: + case []any: count := len(value) if count == 0 { return nil, true } - listeners := make([]func(View, KeyEvent), count) + listeners := make([]func(V, E), count) for i, v := range value { if v == nil { return nil, false } switch v := v.(type) { - case func(View, KeyEvent): + case func(V, E): listeners[i] = v - case func(KeyEvent): - listeners[i] = func(_ View, event KeyEvent) { + case func(E): + listeners[i] = func(_ V, event E) { v(event) } - case func(View): - listeners[i] = func(view View, _ KeyEvent) { + case func(V): + listeners[i] = func(view V, _ E) { v(view) } case func(): - listeners[i] = func(View, KeyEvent) { + listeners[i] = func(V, E) { v() } @@ -180,8 +198,8 @@ var keyEvents = map[string]struct{ jsEvent, jsFunc string }{ KeyUpEvent: {jsEvent: "onkeyup", jsFunc: "keyUpEvent"}, } -func (view *viewData) setKeyListener(tag string, value interface{}) bool { - listeners, ok := valueToKeyListeners(value) +func (view *viewData) setKeyListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, KeyEvent](value) if !ok { notCompatibleType(tag, value) return false @@ -209,67 +227,48 @@ func (view *viewData) removeKeyListener(tag string) { } } -func getKeyListeners(view View, subviewID string, tag string) []func(View, KeyEvent) { - if subviewID != "" { - view = ViewByID(view, subviewID) +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(View, KeyEvent)); ok { + if result, ok := value.([]func(V, E)); ok { return result } } } - return []func(View, KeyEvent){} + return []func(V, E){} } func keyEventsHtml(view View, buffer *strings.Builder) { for tag, js := range keyEvents { - if listeners := getKeyListeners(view, "", tag); len(listeners) > 0 { + if listeners := getEventListeners[View, KeyEvent](view, nil, tag); len(listeners) > 0 { buffer.WriteString(js.jsEvent + `="` + js.jsFunc + `(this, event)" `) } } } func handleKeyEvents(view View, tag string, data DataObject) { - listeners := getKeyListeners(view, "", tag) - if len(listeners) == 0 { - return - } + listeners := getEventListeners[View, KeyEvent](view, nil, tag) + if len(listeners) > 0 { + var event KeyEvent + event.init(data) - getBool := func(tag string) bool { - if value, ok := data.PropertyValue(tag); ok && value == "1" { - return true + for _, listener := range listeners { + listener(view, event) } - return false - } - - key, _ := data.PropertyValue("key") - code, _ := data.PropertyValue("code") - event := KeyEvent{ - TimeStamp: getTimeStamp(data), - Key: key, - Code: code, - Repeat: getBool("repeat"), - CtrlKey: getBool("ctrlKey"), - ShiftKey: getBool("shiftKey"), - AltKey: getBool("altKey"), - MetaKey: getBool("metaKey"), - } - - for _, listener := range listeners { - listener(view, event) } } // 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 "" then a value from the first argument (view) is returned. -func GetKeyDownListeners(view View, subviewID string) []func(View, KeyEvent) { - return getKeyListeners(view, subviewID, KeyDownEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetKeyDownListeners(view View, subviewID ...string) []func(View, KeyEvent) { + return getEventListeners[View, KeyEvent](view, subviewID, KeyDownEvent) } // GetKeyUpListeners returns the "key-up-event" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetKeyUpListeners(view View, subviewID string) []func(View, KeyEvent) { - return getKeyListeners(view, subviewID, KeyUpEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetKeyUpListeners(view View, subviewID ...string) []func(View, KeyEvent) { + return getEventListeners[View, KeyEvent](view, subviewID, KeyUpEvent) } diff --git a/listAdapter.go b/listAdapter.go index fccd642..efa3d69 100644 --- a/listAdapter.go +++ b/listAdapter.go @@ -77,7 +77,7 @@ func (adapter *viewListAdapter) ListItem(index int, session Session) View { func (adapter *viewListAdapter) IsListItemEnabled(index int) bool { if index >= 0 && index < len(adapter.items) { - return !IsDisabled(adapter.items[index], "") + return !IsDisabled(adapter.items[index]) } return true } diff --git a/listLayout.go b/listLayout.go index 29f47bb..dc608de 100644 --- a/listLayout.go +++ b/listLayout.go @@ -33,7 +33,7 @@ type listLayoutData struct { // NewListLayout create new ListLayout object and return it func NewListLayout(session Session, params Params) ListLayout { view := new(listLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -43,8 +43,8 @@ func newListLayout(session Session) View { } // Init initialize fields of ViewsAlignContainer by default values -func (listLayout *listLayoutData) Init(session Session) { - listLayout.viewsContainerData.Init(session) +func (listLayout *listLayoutData) init(session Session) { + listLayout.viewsContainerData.init(session) listLayout.tag = "ListLayout" listLayout.systemClass = "ruiListLayout" } @@ -58,19 +58,41 @@ func (listLayout *listLayoutData) normalizeTag(tag string) string { switch tag { case "wrap": tag = ListWrap + + case "row-gap": + return ListRowGap + + case ColumnGap: + return ListColumnGap } return tag } -func (listLayout *listLayoutData) Get(tag string) interface{} { +func (listLayout *listLayoutData) Get(tag string) any { return listLayout.get(listLayout.normalizeTag(tag)) } +func (listLayout *listLayoutData) get(tag string) any { + if tag == Gap { + if rowGap := GetListRowGap(listLayout); rowGap.Equal(GetListColumnGap(listLayout)) { + return rowGap + } + return AutoSize() + } + + return listLayout.viewsContainerData.get(tag) +} + func (listLayout *listLayoutData) Remove(tag string) { listLayout.remove(listLayout.normalizeTag(tag)) } func (listLayout *listLayoutData) remove(tag string) { + if tag == Gap { + listLayout.remove(ListRowGap) + listLayout.remove(ListColumnGap) + return + } listLayout.viewsContainerData.remove(tag) if listLayout.created { switch tag { @@ -80,16 +102,20 @@ func (listLayout *listLayoutData) remove(tag string) { } } -func (listLayout *listLayoutData) Set(tag string, value interface{}) bool { +func (listLayout *listLayoutData) Set(tag string, value any) bool { return listLayout.set(listLayout.normalizeTag(tag), value) } -func (listLayout *listLayoutData) set(tag string, value interface{}) bool { +func (listLayout *listLayoutData) set(tag string, value any) bool { if value == nil { listLayout.remove(tag) return true } + if tag == Gap { + return listLayout.set(ListRowGap, value) && listLayout.set(ListColumnGap, value) + } + if listLayout.viewsContainerData.set(tag, value) { if listLayout.created { switch tag { @@ -113,38 +139,24 @@ func (listLayout *listLayoutData) htmlSubviews(self View, buffer *strings.Builde // GetListVerticalAlign returns the vertical align of a ListLayout or ListView sibview: // TopAlign (0), BottomAlign (1), CenterAlign (2), or StretchAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return LeftAlign - } - result, _ := enumProperty(view, VerticalAlign, view.Session(), 0) - return result +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, VerticalAlign, TopAlign, false) } // GetListHorizontalAlign returns the vertical align of a ListLayout or ListView subview: // LeftAlign (0), RightAlign (1), CenterAlign (2), or StretchAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListHorizontalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return TopAlign - } - result, _ := enumProperty(view, HorizontalAlign, view.Session(), 0) - return result +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, HorizontalAlign, LeftAlign, false) } // GetListOrientation returns the orientation of a ListLayout or ListView subview: // TopDownOrientation (0), StartToEndOrientation (1), BottomUpOrientation (2), or EndToStartOrientation (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListOrientation(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -163,16 +175,20 @@ func GetListOrientation(view View, subviewID string) int { } // GetListWrap returns the wrap type of a ListLayout or ListView subview: -// WrapOff (0), WrapOn (1), or WrapReverse (2) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListWrap(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, ListWrap, 0); ok { - return result - } - } - return ListWrapOff +// ListWrapOff (0), ListWrapOn (1), or ListWrapReverse (2) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListWrap(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ListWrap, ListWrapOff, false) +} + +// GetListRowGap returns the gap between ListLayout or ListView rows. +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListRowGap(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ListRowGap, false) +} + +// GetListColumnGap returns the gap between ListLayout or ListView columns. +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListColumnGap(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ListColumnGap, false) } diff --git a/listView.go b/listView.go index f8206bd..e949c14 100644 --- a/listView.go +++ b/listView.go @@ -69,7 +69,7 @@ type listViewData struct { // NewListView creates the new list view func NewListView(session Session, params Params) ListView { view := new(listViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -79,8 +79,8 @@ func newListView(session Session) View { } // Init initialize fields of ViewsContainer by default values -func (listView *listViewData) Init(session Session) { - listView.viewData.Init(session) +func (listView *listViewData) init(session Session) { + listView.viewData.init(session) listView.tag = "ListView" listView.systemClass = "ruiListView" listView.items = []View{} @@ -110,6 +110,12 @@ func (listView *listViewData) normalizeTag(tag string) string { case "wrap": tag = ListWrap + + case "row-gap": + return ListRowGap + + case ColumnGap: + return ListColumnGap } return tag } @@ -120,6 +126,10 @@ func (listView *listViewData) Remove(tag string) { func (listView *listViewData) remove(tag string) { switch tag { + case Gap: + listView.remove(ListRowGap) + listView.remove(ListColumnGap) + case Checked: if len(listView.checkedItem) > 0 { listView.checkedItem = []int{} @@ -148,7 +158,7 @@ func (listView *listViewData) remove(tag string) { } case Current: - current := GetCurrent(listView, "") + current := GetCurrent(listView) delete(listView.properties, tag) if listView.created { updateInnerHTML(listView.htmlID(), listView.session) @@ -193,41 +203,53 @@ func (listView *listViewData) remove(tag string) { } } -func (listView *listViewData) Set(tag string, value interface{}) bool { +func (listView *listViewData) Set(tag string, value any) bool { return listView.set(listView.normalizeTag(tag), value) } -func (listView *listViewData) set(tag string, value interface{}) bool { +func (listView *listViewData) set(tag string, value any) bool { if value == nil { listView.remove(tag) return true } switch tag { + case Gap: + return listView.set(ListRowGap, value) && listView.set(ListColumnGap, value) + case ListItemClickedEvent: - listeners := listView.valueToItemListeners(value) - if listeners == nil { + listeners, ok := valueToEventListeners[ListView, int](value) + if !ok { notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(ListView, int){} } listView.clickedListeners = listeners listView.propertyChangedEvent(tag) return true case ListItemSelectedEvent: - listeners := listView.valueToItemListeners(value) - if listeners == nil { + listeners, ok := valueToEventListeners[ListView, int](value) + if !ok { notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(ListView, int){} } listView.selectedListeners = listeners listView.propertyChangedEvent(tag) return true case ListItemCheckedEvent: - if !listView.setItemCheckedEvent(value) { + listeners, ok := valueToEventListeners[ListView, []int](value) + if !ok { + notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(ListView, []int){} } + listView.checkedListeners = listeners listView.propertyChangedEvent(tag) return true @@ -242,11 +264,11 @@ func (listView *listViewData) set(tag string, value interface{}) bool { } case Current: - oldCurrent := GetCurrent(listView, "") + oldCurrent := GetCurrent(listView) if !listView.setIntProperty(Current, value) { return false } - current := GetCurrent(listView, "") + current := GetCurrent(listView) if oldCurrent == current { return true } @@ -255,7 +277,7 @@ func (listView *listViewData) set(tag string, value interface{}) bool { listener(listView, current) } - case Orientation, ListWrap, VerticalAlign, HorizontalAlign, Style, StyleDisabled, ItemWidth, ItemHeight: + case Orientation, ListWrap, ListRowGap, ListColumnGap, VerticalAlign, HorizontalAlign, Style, StyleDisabled, ItemWidth, ItemHeight: result := listView.viewData.set(tag, value) if result && listView.created { updateInnerHTML(listView.htmlID(), listView.session) @@ -288,67 +310,18 @@ func (listView *listViewData) set(tag string, value interface{}) bool { return true } -func (listView *listViewData) setItemCheckedEvent(value interface{}) bool { - switch value := value.(type) { - case func(ListView, []int): - listView.checkedListeners = []func(ListView, []int){value} - - case func([]int): - fn := func(_ ListView, date []int) { - value(date) - } - listView.checkedListeners = []func(ListView, []int){fn} - - case []func(ListView, []int): - listView.checkedListeners = value - - case []func([]int): - listeners := make([]func(ListView, []int), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(ListItemCheckedEvent, val) - return false - } - - listeners[i] = func(_ ListView, date []int) { - val(date) - } - } - listView.checkedListeners = listeners - - case []interface{}: - listeners := make([]func(ListView, []int), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(ListItemCheckedEvent, val) - return false - } - - switch val := val.(type) { - case func(ListView, []int): - listeners[i] = val - - case func([]int): - listeners[i] = func(_ ListView, checked []int) { - val(checked) - } - - default: - notCompatibleType(ListItemCheckedEvent, val) - return false - } - } - listView.checkedListeners = listeners - } - return true -} - -func (listView *listViewData) Get(tag string) interface{} { +func (listView *listViewData) Get(tag string) any { return listView.get(listView.normalizeTag(tag)) } -func (listView *listViewData) get(tag string) interface{} { +func (listView *listViewData) get(tag string) any { switch tag { + case Gap: + if rowGap := GetListRowGap(listView); rowGap.Equal(GetListColumnGap(listView)) { + return rowGap + } + return AutoSize() + case ListItemClickedEvent: return listView.clickedListeners @@ -376,7 +349,7 @@ func (listView *listViewData) get(tag string) interface{} { return listView.viewData.get(tag) } -func (listView *listViewData) setItems(value interface{}) bool { +func (listView *listViewData) setItems(value any) bool { switch value := value.(type) { case []string: listView.adapter = NewTextListAdapter(value, nil) @@ -412,7 +385,7 @@ func (listView *listViewData) setItems(value interface{}) bool { listView.adapter = NewTextListAdapter(items, nil) } - case []interface{}: + case []any: items := make([]View, len(value)) for i, val := range value { switch value := val.(type) { @@ -460,62 +433,7 @@ func (listView *listViewData) setItems(value interface{}) bool { return true } -func (listView *listViewData) valueToItemListeners(value interface{}) []func(ListView, int) { - if value == nil { - return []func(ListView, int){} - } - - switch value := value.(type) { - case func(ListView, int): - return []func(ListView, int){value} - - case func(int): - fn := func(_ ListView, index int) { - value(index) - } - return []func(ListView, int){fn} - - case []func(ListView, int): - return value - - case []func(int): - listeners := make([]func(ListView, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - listeners[i] = func(_ ListView, index int) { - val(index) - } - } - return listeners - - case []interface{}: - listeners := make([]func(ListView, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - switch val := val.(type) { - case func(ListView, int): - listeners[i] = val - - case func(int): - listeners[i] = func(_ ListView, index int) { - val(index) - } - - default: - return nil - } - } - return listeners - } - - return nil -} - -func (listView *listViewData) setChecked(value interface{}) bool { +func (listView *listViewData) setChecked(value any) bool { var checked []int if value == nil { checked = []int{} @@ -544,7 +462,7 @@ func (listView *listViewData) setChecked(value interface{}) bool { } } - switch GetListViewCheckbox(listView, "") { + switch GetListViewCheckbox(listView) { case SingleCheckbox: count := len(checked) if count > 1 { @@ -628,14 +546,14 @@ func (listView *listViewData) getItemFrames() []Frame { func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) { values := enumProperties[ItemHorizontalAlign].cssValues - if hAlign := GetListItemHorizontalAlign(listView, ""); hAlign >= 0 && hAlign < len(values) { + if hAlign := GetListItemHorizontalAlign(listView); hAlign >= 0 && hAlign < len(values) { buffer.WriteString(" justify-items: ") buffer.WriteString(values[hAlign]) buffer.WriteRune(';') } values = enumProperties[ItemVerticalAlign].cssValues - if vAlign := GetListItemVerticalAlign(listView, ""); vAlign >= 0 && vAlign < len(values) { + if vAlign := GetListItemVerticalAlign(listView); vAlign >= 0 && vAlign < len(values) { buffer.WriteString(" align-items: ") buffer.WriteString(values[vAlign]) buffer.WriteRune(';') @@ -643,15 +561,15 @@ func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) { } func (listView *listViewData) itemSize(self View, buffer *strings.Builder) { - if itemWidth := GetListItemWidth(listView, ""); itemWidth.Type != Auto { + if itemWidth := GetListItemWidth(listView); itemWidth.Type != Auto { buffer.WriteString(` min-width: `) - buffer.WriteString(itemWidth.cssString("")) + buffer.WriteString(itemWidth.cssString("", listView.Session())) buffer.WriteRune(';') } - if itemHeight := GetListItemHeight(listView, ""); itemHeight.Type != Auto { + if itemHeight := GetListItemHeight(listView); itemHeight.Type != Auto { buffer.WriteString(` min-height: `) - buffer.WriteString(itemHeight.cssString("")) + buffer.WriteString(itemHeight.cssString("", listView.Session())) buffer.WriteRune(';') } } @@ -741,7 +659,7 @@ func (listView *listViewData) checkboxItemDiv(self View, checkbox, hCheckboxAlig if gap, ok := sizeConstant(listView.session, "ruiCheckboxGap"); ok && gap.Type != Auto { itemStyleBuilder.WriteString(` grid-gap: `) - itemStyleBuilder.WriteString(gap.cssString("auto")) + itemStyleBuilder.WriteString(gap.cssString("auto", listView.Session())) itemStyleBuilder.WriteRune(';') } @@ -801,14 +719,14 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde count := listView.adapter.ListSize() listViewID := listView.htmlID() - hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView, "") - vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView, "") + hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView) + vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView) itemDiv := listView.checkboxItemDiv(self, checkbox, hCheckboxAlign, vCheckboxAlign) onDiv, offDiv, contentDiv := listView.getDivs(self, checkbox, hCheckboxAlign, vCheckboxAlign) - current := GetCurrent(listView, "") - checkedItems := GetListViewCheckedItems(listView, "") + current := GetCurrent(listView) + checkedItems := GetListViewCheckedItems(listView) for i := 0; i < count; i++ { buffer.WriteString(`
= 0 && current < listView.adapter.ListSize() { buffer.WriteString(` data-current="`) buffer.WriteString(listView.htmlID()) @@ -953,8 +871,8 @@ func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder) func (listView *listViewData) cssStyle(self View, builder cssBuilder) { listView.viewData.cssStyle(self, builder) - if GetListWrap(listView, "") != WrapOff { - switch GetListOrientation(listView, "") { + if GetListWrap(listView) != WrapOff { + switch GetListOrientation(listView) { case TopDownOrientation, BottomUpOrientation: builder.add(`max-height`, `100%`) default: @@ -976,8 +894,20 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) { buffer.WriteString(`
`) - checkbox := GetListViewCheckbox(listView, "") + checkbox := GetListViewCheckbox(listView) if checkbox == NoneCheckbox { listView.noneCheckboxSubviews(self, buffer) } else { @@ -1130,9 +1056,9 @@ func (listView *listViewData) handleCommand(self View, command string, data Data } func (listView *listViewData) onItemClick() { - current := GetCurrent(listView, "") - if current >= 0 && !IsDisabled(listView, "") { - checkbox := GetListViewCheckbox(listView, "") + current := GetCurrent(listView) + if current >= 0 && !IsDisabled(listView) { + checkbox := GetListViewCheckbox(listView) m: switch checkbox { case SingleCheckbox: @@ -1191,121 +1117,67 @@ func (listView *listViewData) onItemResize(self View, index string, x, y, width, } // GetVerticalAlign return the vertical align of a list: TopAlign (0), BottomAlign (1), CenterAlign (2), StretchAlign (3) -func GetVerticalAlign(view View) int { - if align, ok := enumProperty(view, VerticalAlign, view.Session(), TopAlign); ok { - return align - } - return TopAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, VerticalAlign, TopAlign, false) } -// GetHorizontalAlign return the vertical align of a list: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3) -func GetHorizontalAlign(view View) int { - if align, ok := enumProperty(view, HorizontalAlign, view.Session(), LeftAlign); ok { - return align - } - return LeftAlign +// GetHorizontalAlign return the vertical align of a list/checkbox: LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, HorizontalAlign, LeftAlign, false) } // GetListItemClickedListeners returns a ListItemClickedListener of the ListView. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemClickedListeners(view View, subviewID string) []func(ListView, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ListItemClickedEvent); value != nil { - if result, ok := value.([]func(ListView, int)); ok { - return result - } - } - } - return []func(ListView, int){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemClickedListeners(view View, subviewID ...string) []func(ListView, int) { + return getEventListeners[ListView, int](view, subviewID, ListItemClickedEvent) } // GetListItemSelectedListeners returns a ListItemSelectedListener of the ListView. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemSelectedListeners(view View, subviewID string) []func(ListView, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ListItemSelectedEvent); value != nil { - if result, ok := value.([]func(ListView, int)); ok { - return result - } - } - } - return []func(ListView, int){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemSelectedListeners(view View, subviewID ...string) []func(ListView, int) { + return getEventListeners[ListView, int](view, subviewID, ListItemSelectedEvent) } // GetListItemCheckedListeners returns a ListItemCheckedListener of the ListView. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemCheckedListeners(view View, subviewID string) []func(ListView, []int) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ListItemCheckedEvent); value != nil { - if result, ok := value.([]func(ListView, []int)); ok { - return result - } - } - } - return []func(ListView, []int){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemCheckedListeners(view View, subviewID ...string) []func(ListView, []int) { + return getEventListeners[ListView, []int](view, subviewID, ListItemCheckedEvent) } // GetListItemWidth returns the width of a ListView item. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemWidth(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - result, _ := sizeProperty(view, ItemWidth, view.Session()) - return result - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemWidth(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ItemWidth, false) } // GetListItemHeight returns the height of a ListView item. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemHeight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - result, _ := sizeProperty(view, ItemHeight, view.Session()) - return result - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemHeight(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, ItemHeight, false) } // GetListViewCheckbox returns the ListView checkbox type: NoneCheckbox (0), SingleCheckbox (1), or MultipleCheckbox (2). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListViewCheckbox(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - result, _ := enumProperty(view, ItemCheckbox, view.Session(), 0) - return result - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListViewCheckbox(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ItemCheckbox, 0, false) } // GetListViewCheckedItems returns the array of ListView checked items. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListViewCheckedItems(view View, subviewID string) []int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListViewCheckedItems(view View, subviewID ...string) []int { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } + if view != nil { if listView, ok := view.(ListView); ok { checkedItems := listView.getCheckedItems() - switch GetListViewCheckbox(view, "") { + switch GetListViewCheckbox(view) { case NoneCheckbox: return []int{} @@ -1334,66 +1206,34 @@ func IsListViewCheckedItem(view View, subviewID string, index int) bool { // GetListViewCheckboxVerticalAlign returns the vertical align of the ListView checkbox: // TopAlign (0), BottomAlign (1), CenterAlign (2) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListViewCheckboxVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumProperty(view, CheckboxVerticalAlign, view.Session(), TopAlign); ok { - return align - } - } - return TopAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListViewCheckboxVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, CheckboxVerticalAlign, TopAlign, false) } // GetListViewCheckboxHorizontalAlign returns the horizontal align of the ListView checkbox: // LeftAlign (0), RightAlign (1), CenterAlign (2) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListViewCheckboxHorizontalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumProperty(view, CheckboxHorizontalAlign, view.Session(), LeftAlign); ok { - return align - } - } - return LeftAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListViewCheckboxHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, CheckboxHorizontalAlign, LeftAlign, false) } // GetListItemVerticalAlign returns the vertical align of the ListView item content: // TopAlign (0), BottomAlign (1), CenterAlign (2) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumProperty(view, ItemVerticalAlign, view.Session(), TopAlign); ok { - return align - } - } - return TopAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ItemVerticalAlign, TopAlign, false) } // ItemHorizontalAlign returns the horizontal align of the ListView item content: // LeftAlign (0), RightAlign (1), CenterAlign (2), StretchAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListItemHorizontalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if align, ok := enumProperty(view, ItemHorizontalAlign, view.Session(), LeftAlign); ok { - return align - } - } - return LeftAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListItemHorizontalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, ItemHorizontalAlign, LeftAlign, false) } // GetListItemFrame - returns the location and size of the ListView item in pixels. -// If the second argument (subviewID) is "" 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 GetListItemFrame(view View, subviewID string, index int) Frame { if subviewID != "" { view = ViewByID(view, subviewID) @@ -1410,10 +1250,10 @@ func GetListItemFrame(view View, subviewID string, index int) Frame { } // GetListViewAdapter - returns the ListView adapter. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetListViewAdapter(view View, subviewID string) ListAdapter { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetListViewAdapter(view View, subviewID ...string) ListAdapter { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if value := view.Get(Items); value != nil { @@ -1426,11 +1266,12 @@ func GetListViewAdapter(view View, subviewID string) ListAdapter { } // ReloadListViewData updates ListView content -// If the second argument (subviewID) is "" then content the first argument (view) is updated. -func ReloadListViewData(view View, subviewID string) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then content the first argument (view) is updated. +func ReloadListViewData(view View, subviewID ...string) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } + if view != nil { if listView, ok := view.(ListView); ok { listView.ReloadListViewData() diff --git a/mediaPlayer.go b/mediaPlayer.go index 80b55a2..9ea43b9 100644 --- a/mediaPlayer.go +++ b/mediaPlayer.go @@ -163,8 +163,8 @@ type MediaSource struct { MimeType string } -func (player *mediaPlayerData) Init(session Session) { - player.viewData.Init(session) +func (player *mediaPlayerData) init(session Session) { + player.viewData.init(session) player.tag = "MediaPlayer" } @@ -185,11 +185,11 @@ func (player *mediaPlayerData) remove(tag string) { player.propertyChanged(tag) } -func (player *mediaPlayerData) Set(tag string, value interface{}) bool { +func (player *mediaPlayerData) Set(tag string, value any) bool { return player.set(strings.ToLower(tag), value) } -func (player *mediaPlayerData) set(tag string, value interface{}) bool { +func (player *mediaPlayerData) set(tag string, value any) bool { if value == nil { player.remove(tag) return true @@ -205,7 +205,7 @@ func (player *mediaPlayerData) set(tag string, value interface{}) bool { case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent, LoadStartEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent: - if listeners, ok := valueToPlayerListeners(value); ok { + if listeners, ok := valueToNoParamListeners[MediaPlayer](value); ok { if listeners == nil { delete(player.properties, tag) } else { @@ -218,7 +218,7 @@ func (player *mediaPlayerData) set(tag string, value interface{}) bool { notCompatibleType(tag, value) case DurationChangedEvent, RateChangedEvent, TimeUpdateEvent, VolumeChangedEvent: - if listeners, ok := valueToPlayerTimeListeners(value); ok { + if listeners, ok := valueToEventListeners[MediaPlayer, float64](value); ok { if listeners == nil { delete(player.properties, tag) } else { @@ -257,7 +257,7 @@ func (player *mediaPlayerData) set(tag string, value interface{}) bool { return false } -func (player *mediaPlayerData) setSource(value interface{}) bool { +func (player *mediaPlayerData) setSource(value any) bool { switch value := value.(type) { case string: src := MediaSource{Url: value, MimeType: ""} @@ -311,203 +311,7 @@ func (player *mediaPlayerData) setSource(value interface{}) bool { return true } -func valueToPlayerListeners(value interface{}) ([]func(MediaPlayer), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(MediaPlayer): - return []func(MediaPlayer){value}, true - - case func(): - fn := func(MediaPlayer) { - value() - } - return []func(MediaPlayer){fn}, true - - case []func(MediaPlayer): - 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(MediaPlayer), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(MediaPlayer) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(MediaPlayer), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(MediaPlayer): - listeners[i] = v - - case func(): - listeners[i] = func(MediaPlayer) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - -func valueToPlayerTimeListeners(value interface{}) ([]func(MediaPlayer, float64), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(MediaPlayer, float64): - return []func(MediaPlayer, float64){value}, true - - case func(float64): - fn := func(_ MediaPlayer, time float64) { - value(time) - } - return []func(MediaPlayer, float64){fn}, true - - case func(MediaPlayer): - fn := func(player MediaPlayer, _ float64) { - value(player) - } - return []func(MediaPlayer, float64){fn}, true - - case func(): - fn := func(MediaPlayer, float64) { - value() - } - return []func(MediaPlayer, float64){fn}, true - - case []func(MediaPlayer, float64): - if len(value) == 0 { - return nil, true - } - for _, fn := range value { - if fn == nil { - return nil, false - } - } - return value, true - - case []func(float64): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(MediaPlayer, float64), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(_ MediaPlayer, time float64) { - v(time) - } - } - return listeners, true - - case []func(MediaPlayer): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(MediaPlayer, float64), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(player MediaPlayer, _ float64) { - v(player) - } - } - return listeners, true - - case []func(): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(MediaPlayer, float64), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(MediaPlayer, float64) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(MediaPlayer, float64), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(MediaPlayer, float64): - listeners[i] = v - - case func(float64): - listeners[i] = func(_ MediaPlayer, time float64) { - v(time) - } - - case func(MediaPlayer): - listeners[i] = func(player MediaPlayer, _ float64) { - v(player) - } - - case func(): - listeners[i] = func(MediaPlayer, float64) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - -func valueToPlayerErrorListeners(value interface{}) ([]func(MediaPlayer, int, string), bool) { +func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), bool) { if value == nil { return nil, true } @@ -593,7 +397,7 @@ func valueToPlayerErrorListeners(value interface{}) ([]func(MediaPlayer, int, st } return listeners, true - case []interface{}: + case []any: count := len(value) if count == 0 { return nil, true diff --git a/mouseEvents.go b/mouseEvents.go index e9ae3a6..8370279 100644 --- a/mouseEvents.go +++ b/mouseEvents.go @@ -144,131 +144,6 @@ type MouseEvent struct { MetaKey bool } -func valueToMouseListeners(value interface{}) ([]func(View, MouseEvent), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(View, MouseEvent): - return []func(View, MouseEvent){value}, true - - case func(MouseEvent): - fn := func(_ View, event MouseEvent) { - value(event) - } - return []func(View, MouseEvent){fn}, true - - case func(View): - fn := func(view View, _ MouseEvent) { - value(view) - } - return []func(View, MouseEvent){fn}, true - - case func(): - fn := func(View, MouseEvent) { - value() - } - return []func(View, MouseEvent){fn}, true - - case []func(View, MouseEvent): - if len(value) == 0 { - return nil, true - } - for _, fn := range value { - if fn == nil { - return nil, false - } - } - return value, true - - case []func(MouseEvent): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, MouseEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(_ View, event MouseEvent) { - v(event) - } - } - return listeners, true - - case []func(View): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, MouseEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(view View, _ MouseEvent) { - v(view) - } - } - return listeners, true - - case []func(): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, MouseEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(View, MouseEvent) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, MouseEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(View, MouseEvent): - listeners[i] = v - - case func(MouseEvent): - listeners[i] = func(_ View, event MouseEvent) { - v(event) - } - - case func(View): - listeners[i] = func(view View, _ MouseEvent) { - v(view) - } - - case func(): - listeners[i] = func(View, MouseEvent) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - var mouseEvents = map[string]struct{ jsEvent, jsFunc string }{ ClickEvent: {jsEvent: "onclick", jsFunc: "clickEvent"}, DoubleClickEvent: {jsEvent: "ondblclick", jsFunc: "doubleClickEvent"}, @@ -280,8 +155,8 @@ var mouseEvents = map[string]struct{ jsEvent, jsFunc string }{ ContextMenuEvent: {jsEvent: "oncontextmenu", jsFunc: "contextMenuEvent"}, } -func (view *viewData) setMouseListener(tag string, value interface{}) bool { - listeners, ok := valueToMouseListeners(value) +func (view *viewData) setMouseListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, MouseEvent](value) if !ok { notCompatibleType(tag, value) return false @@ -309,20 +184,6 @@ func (view *viewData) removeMouseListener(tag string) { } } -func getMouseListeners(view View, subviewID string, tag string) []func(View, MouseEvent) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(tag); value != nil { - if result, ok := value.([]func(View, MouseEvent)); ok { - return result - } - } - } - return []func(View, MouseEvent){} -} - func mouseEventsHtml(view View, buffer *strings.Builder) { for tag, js := range mouseEvents { if value := view.getRaw(tag); value != nil { @@ -363,64 +224,62 @@ func (event *MouseEvent) init(data DataObject) { } func handleMouseEvents(view View, tag string, data DataObject) { - listeners := getMouseListeners(view, "", tag) - if len(listeners) == 0 { - return - } + listeners := getEventListeners[View, MouseEvent](view, nil, tag) + if len(listeners) > 0 { + var event MouseEvent + event.init(data) - var event MouseEvent - event.init(data) - - for _, listener := range listeners { - listener(view, event) + for _, listener := range listeners { + listener(view, event) + } } } // GetClickListeners returns the "click-event" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetClickListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, ClickEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetClickListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, ClickEvent) } // GetDoubleClickListeners returns the "double-click-event" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetDoubleClickListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, DoubleClickEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetDoubleClickListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, DoubleClickEvent) } // GetContextMenuListeners returns the "context-menu" listener list. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetContextMenuListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, ContextMenuEvent) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetContextMenuListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, ContextMenuEvent) } // GetMouseDownListeners returns the "mouse-down" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetMouseDownListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, MouseDown) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetMouseDownListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, MouseDown) } // GetMouseUpListeners returns the "mouse-up" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetMouseUpListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, MouseUp) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetMouseUpListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, MouseUp) } // GetMouseMoveListeners returns the "mouse-move" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetMouseMoveListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, MouseMove) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetMouseMoveListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, MouseMove) } // GetMouseOverListeners returns the "mouse-over" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetMouseOverListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, MouseOver) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetMouseOverListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, MouseOver) } // GetMouseOutListeners returns the "mouse-out" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetMouseOutListeners(view View, subviewID string) []func(View, MouseEvent) { - return getMouseListeners(view, subviewID, MouseOut) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetMouseOutListeners(view View, subviewID ...string) []func(View, MouseEvent) { + return getEventListeners[View, MouseEvent](view, subviewID, MouseOut) } diff --git a/numberPicker.go b/numberPicker.go index a44f60a..49bc038 100644 --- a/numberPicker.go +++ b/numberPicker.go @@ -36,7 +36,7 @@ type numberPickerData struct { // NewNumberPicker create new NumberPicker object and return it func NewNumberPicker(session Session, params Params) NumberPicker { view := new(numberPickerData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -45,8 +45,8 @@ func newNumberPicker(session Session) View { return NewNumberPicker(session, nil) } -func (picker *numberPickerData) Init(session Session) { - picker.viewData.Init(session) +func (picker *numberPickerData) init(session Session) { + picker.viewData.init(session) picker.tag = "NumberPicker" picker.numberChangedListeners = []func(NumberPicker, float64){} } @@ -87,11 +87,11 @@ func (picker *numberPickerData) remove(tag string) { } } -func (picker *numberPickerData) Set(tag string, value interface{}) bool { +func (picker *numberPickerData) Set(tag string, value any) bool { return picker.set(picker.normalizeTag(tag), value) } -func (picker *numberPickerData) set(tag string, value interface{}) bool { +func (picker *numberPickerData) set(tag string, value any) bool { if value == nil { picker.remove(tag) return true @@ -99,65 +99,28 @@ func (picker *numberPickerData) set(tag string, value interface{}) bool { switch tag { case NumberChangedEvent: - switch value := value.(type) { - case func(NumberPicker, float64): - picker.numberChangedListeners = []func(NumberPicker, float64){value} - - case func(float64): - fn := func(_ NumberPicker, newValue float64) { - value(newValue) - } - picker.numberChangedListeners = []func(NumberPicker, float64){fn} - - case []func(NumberPicker, float64): - picker.numberChangedListeners = value - - case []func(float64): - listeners := make([]func(NumberPicker, float64), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(_ NumberPicker, newValue float64) { - val(newValue) - } - } - picker.numberChangedListeners = listeners - - case []interface{}: - listeners := make([]func(NumberPicker, float64), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - switch val := val.(type) { - case func(NumberPicker, float64): - listeners[i] = val - - default: - notCompatibleType(tag, val) - return false - } - } - picker.numberChangedListeners = listeners + listeners, ok := valueToEventListeners[NumberPicker, float64](value) + if !ok { + notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(NumberPicker, float64){} } + picker.numberChangedListeners = listeners picker.propertyChangedEvent(tag) return true case NumberPickerValue: - oldValue := GetNumberPickerValue(picker, "") - min, max := GetNumberPickerMinMax(picker, "") + oldValue := GetNumberPickerValue(picker) + min, max := GetNumberPickerMinMax(picker) if picker.setFloatProperty(NumberPickerValue, value, min, max) { - if newValue := GetNumberPickerValue(picker, ""); oldValue != newValue { + if f, ok := floatProperty(picker, NumberPickerValue, picker.Session(), min); ok && f != oldValue { + newValue, _ := floatTextProperty(picker, NumberPickerValue, picker.Session(), min) if picker.created { - picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%f')`, picker.htmlID(), newValue)) + picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), newValue)) } for _, listener := range picker.numberChangedListeners { - listener(picker, newValue) + listener(picker, f) } picker.propertyChangedEvent(tag) } @@ -177,29 +140,29 @@ func (picker *numberPickerData) propertyChanged(tag string) { if picker.created { switch tag { case NumberPickerType: - if GetNumberPickerType(picker, "") == NumberSlider { + if GetNumberPickerType(picker) == NumberSlider { updateProperty(picker.htmlID(), "type", "range", picker.session) } else { updateProperty(picker.htmlID(), "type", "number", picker.session) } case NumberPickerMin: - min, _ := GetNumberPickerMinMax(picker, "") + min, _ := GetNumberPickerMinMax(picker) updateProperty(picker.htmlID(), Min, strconv.FormatFloat(min, 'f', -1, 32), picker.session) case NumberPickerMax: - _, max := GetNumberPickerMinMax(picker, "") + _, max := GetNumberPickerMinMax(picker) updateProperty(picker.htmlID(), Max, strconv.FormatFloat(max, 'f', -1, 32), picker.session) case NumberPickerStep: - if step := GetNumberPickerStep(picker, ""); step > 0 { + if step := GetNumberPickerStep(picker); step > 0 { updateProperty(picker.htmlID(), Step, strconv.FormatFloat(step, 'f', -1, 32), picker.session) } else { updateProperty(picker.htmlID(), Step, "any", picker.session) } case NumberPickerValue: - value := GetNumberPickerValue(picker, "") + value := GetNumberPickerValue(picker) picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%f')`, picker.htmlID(), value)) for _, listener := range picker.numberChangedListeners { listener(picker, value) @@ -208,11 +171,11 @@ func (picker *numberPickerData) propertyChanged(tag string) { } } -func (picker *numberPickerData) Get(tag string) interface{} { +func (picker *numberPickerData) Get(tag string) any { return picker.get(picker.normalizeTag(tag)) } -func (picker *numberPickerData) get(tag string) interface{} { +func (picker *numberPickerData) get(tag string) any { switch tag { case NumberChangedEvent: return picker.numberChangedListeners @@ -229,13 +192,13 @@ func (picker *numberPickerData) htmlTag() string { func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) { picker.viewData.htmlProperties(self, buffer) - if GetNumberPickerType(picker, "") == NumberSlider { + if GetNumberPickerType(picker) == NumberSlider { buffer.WriteString(` type="range"`) } else { buffer.WriteString(` type="number"`) } - min, max := GetNumberPickerMinMax(picker, "") + min, max := GetNumberPickerMinMax(picker) if min != math.Inf(-1) { buffer.WriteString(` min="`) buffer.WriteString(strconv.FormatFloat(min, 'f', -1, 64)) @@ -248,7 +211,7 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde buffer.WriteByte('"') } - step := GetNumberPickerStep(picker, "") + step := GetNumberPickerStep(picker) if step != 0 { buffer.WriteString(` step="`) buffer.WriteString(strconv.FormatFloat(step, 'f', -1, 64)) @@ -258,14 +221,14 @@ func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builde } buffer.WriteString(` value="`) - buffer.WriteString(strconv.FormatFloat(GetNumberPickerValue(picker, ""), 'f', -1, 64)) + buffer.WriteString(strconv.FormatFloat(GetNumberPickerValue(picker), 'f', -1, 64)) buffer.WriteByte('"') buffer.WriteString(` oninput="editViewInputEvent(this)"`) } func (picker *numberPickerData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` disabled`) } picker.viewData.htmlDisabledProperties(self, buffer) @@ -276,8 +239,8 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da case "textChanged": if text, ok := data.PropertyValue("text"); ok { if value, err := strconv.ParseFloat(text, 32); err == nil { - oldValue := GetNumberPickerValue(picker, "") - picker.properties[NumberPickerValue] = value + oldValue := GetNumberPickerValue(picker) + picker.properties[NumberPickerValue] = text if value != oldValue { for _, listener := range picker.numberChangedListeners { listener(picker, value) @@ -294,70 +257,50 @@ func (picker *numberPickerData) handleCommand(self View, command string, data Da // GetNumberPickerType returns the type of NumberPicker subview. Valid values: // NumberEditor (0) - NumberPicker is presented by editor (default type); // NumberSlider (1) - NumberPicker is presented by slider. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetNumberPickerType(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - - t, _ := enumStyledProperty(view, NumberPickerType, NumberEditor) - return t +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetNumberPickerType(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, NumberPickerType, NumberEditor, false) } // GetNumberPickerMinMax returns the min and max value of NumberPicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetNumberPickerMinMax(view View, subviewID string) (float64, float64) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + var pickerType int + if len(subviewID) > 0 && subviewID[0] != "" { + pickerType = GetNumberPickerType(view, subviewID[0]) + } else { + pickerType = GetNumberPickerType(view) } - if view != nil { - t, _ := enumStyledProperty(view, NumberPickerType, NumberEditor) - var defMin, defMax float64 - if t == NumberSlider { - defMin = 0 - defMax = 1 - } else { - defMin = math.Inf(-1) - defMax = math.Inf(1) - } - min, ok := floatStyledProperty(view, NumberPickerMin, defMin) - if !ok { - min, _ = floatStyledProperty(view, Min, defMin) - } - - max, ok := floatStyledProperty(view, NumberPickerMax, defMax) - if !ok { - max, _ = floatStyledProperty(view, Max, defMax) - } - - if min > max { - return max, min - } - return min, max + var defMin, defMax float64 + if pickerType == NumberSlider { + defMin = 0 + defMax = 1 + } else { + defMin = math.Inf(-1) + defMax = math.Inf(1) } - return 0, 1 + + min := floatStyledProperty(view, subviewID, NumberPickerMin, defMin) + max := floatStyledProperty(view, subviewID, NumberPickerMax, defMax) + + if min > max { + return max, min + } + return min, max } // GetNumberPickerStep returns the value changing step of NumberPicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetNumberPickerStep(view View, subviewID string) float64 { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 +// 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 { + var max float64 + if len(subviewID) > 0 && subviewID[0] != "" { + _, max = GetNumberPickerMinMax(view, subviewID[0]) + } else { + _, max = GetNumberPickerMinMax(view) } - result, ok := floatStyledProperty(view, NumberPickerStep, 0) - if !ok { - result, _ = floatStyledProperty(view, Step, 0) - } - - _, max := GetNumberPickerMinMax(view, "") + result := floatStyledProperty(view, subviewID, NumberPickerStep, 0) if result > max { return max } @@ -365,36 +308,22 @@ func GetNumberPickerStep(view View, subviewID string) float64 { } // GetNumberPickerValue returns the value of NumberPicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetNumberPickerValue(view View, subviewID string) float64 { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 +// 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 { + var min float64 + if len(subviewID) > 0 && subviewID[0] != "" { + min, _ = GetNumberPickerMinMax(view, subviewID[0]) + } else { + min, _ = GetNumberPickerMinMax(view) } - min, _ := GetNumberPickerMinMax(view, "") - result, ok := floatStyledProperty(view, NumberPickerValue, min) - if !ok { - result, _ = floatStyledProperty(view, Value, min) - } + result := floatStyledProperty(view, subviewID, NumberPickerValue, min) return result } // GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetNumberChangedListeners(view View, subviewID string) []func(NumberPicker, float64) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(NumberChangedEvent); value != nil { - if listeners, ok := value.([]func(NumberPicker, float64)); ok { - return listeners - } - } - } - return []func(NumberPicker, float64){} +// 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) { + return getEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent) } diff --git a/outline.go b/outline.go index 939abc7..342fa26 100644 --- a/outline.go +++ b/outline.go @@ -18,7 +18,7 @@ type outlinePropertyData struct { func NewOutlineProperty(params Params) OutlineProperty { outline := new(outlinePropertyData) - outline.properties = map[string]interface{}{} + outline.properties = map[string]any{} for tag, value := range params { outline.Set(tag, value) } @@ -55,7 +55,7 @@ func (outline *outlinePropertyData) Remove(tag string) { delete(outline.properties, outline.normalizeTag(tag)) } -func (outline *outlinePropertyData) Set(tag string, value interface{}) bool { +func (outline *outlinePropertyData) Set(tag string, value any) bool { if value == nil { outline.Remove(tag) return true @@ -85,7 +85,7 @@ func (outline *outlinePropertyData) Set(tag string, value interface{}) bool { return false } -func (outline *outlinePropertyData) Get(tag string) interface{} { +func (outline *outlinePropertyData) Get(tag string) any { return outline.propertyList.Get(outline.normalizeTag(tag)) } @@ -103,18 +103,18 @@ type ViewOutline struct { Width SizeUnit } -func (outline ViewOutline) cssValue(builder cssBuilder) { +func (outline ViewOutline) cssValue(builder cssBuilder, session Session) { values := enumProperties[BorderStyle].cssValues if outline.Style > 0 && outline.Style < len(values) && outline.Color.Alpha() > 0 && outline.Width.Type != Auto && outline.Width.Type != SizeInFraction && outline.Width.Type != SizeInPercent && outline.Width.Value > 0 { - builder.addValues("outline", " ", outline.Width.cssString("0"), values[outline.Style], outline.Color.cssString()) + builder.addValues("outline", " ", outline.Width.cssString("0", session), values[outline.Style], outline.Color.cssString()) } } -func (outline ViewOutline) cssString() string { +func (outline ViewOutline) cssString(session Session) string { var builder cssValueBuilder - outline.cssValue(&builder) + outline.cssValue(&builder, session) return builder.finish() } @@ -128,7 +128,7 @@ func getOutline(properties Properties) OutlineProperty { return nil } -func (style *viewStyle) setOutline(value interface{}) bool { +func (style *viewStyle) setOutline(value any) bool { switch value := value.(type) { case OutlineProperty: style.properties[Outline] = value diff --git a/params.go b/params.go index dceca0f..b52b8d9 100644 --- a/params.go +++ b/params.go @@ -3,25 +3,25 @@ package rui import "sort" // Params defines a type of a parameters list -type Params map[string]interface{} +type Params map[string]any -func (params Params) Get(tag string) interface{} { +func (params Params) Get(tag string) any { return params.getRaw(tag) } -func (params Params) getRaw(tag string) interface{} { +func (params Params) getRaw(tag string) any { if value, ok := params[tag]; ok { return value } return nil } -func (params Params) Set(tag string, value interface{}) bool { +func (params Params) Set(tag string, value any) bool { params.setRaw(tag, value) return true } -func (params Params) setRaw(tag string, value interface{}) { +func (params Params) setRaw(tag string, value any) { if value != nil { params[tag] = value } else { diff --git a/pointerEvents.go b/pointerEvents.go index e0f78db..c565079 100644 --- a/pointerEvents.go +++ b/pointerEvents.go @@ -87,131 +87,6 @@ type PointerEvent struct { IsPrimary bool } -func valueToPointerListeners(value interface{}) ([]func(View, PointerEvent), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(View, PointerEvent): - return []func(View, PointerEvent){value}, true - - case func(PointerEvent): - fn := func(_ View, event PointerEvent) { - value(event) - } - return []func(View, PointerEvent){fn}, true - - case func(View): - fn := func(view View, _ PointerEvent) { - value(view) - } - return []func(View, PointerEvent){fn}, true - - case func(): - fn := func(View, PointerEvent) { - value() - } - return []func(View, PointerEvent){fn}, true - - case []func(View, PointerEvent): - if len(value) == 0 { - return nil, true - } - for _, fn := range value { - if fn == nil { - return nil, false - } - } - return value, true - - case []func(PointerEvent): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, PointerEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(_ View, event PointerEvent) { - v(event) - } - } - return listeners, true - - case []func(View): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, PointerEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(view View, _ PointerEvent) { - v(view) - } - } - return listeners, true - - case []func(): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, PointerEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(View, PointerEvent) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, PointerEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(View, PointerEvent): - listeners[i] = v - - case func(PointerEvent): - listeners[i] = func(_ View, event PointerEvent) { - v(event) - } - - case func(View): - listeners[i] = func(view View, _ PointerEvent) { - v(view) - } - - case func(): - listeners[i] = func(View, PointerEvent) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - var pointerEvents = map[string]struct{ jsEvent, jsFunc string }{ PointerDown: {jsEvent: "onpointerdown", jsFunc: "pointerDownEvent"}, PointerUp: {jsEvent: "onpointerup", jsFunc: "pointerUpEvent"}, @@ -221,8 +96,8 @@ var pointerEvents = map[string]struct{ jsEvent, jsFunc string }{ PointerOver: {jsEvent: "onpointerover", jsFunc: "pointerOverEvent"}, } -func (view *viewData) setPointerListener(tag string, value interface{}) bool { - listeners, ok := valueToPointerListeners(value) +func (view *viewData) setPointerListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, PointerEvent](value) if !ok { notCompatibleType(tag, value) return false @@ -250,20 +125,6 @@ func (view *viewData) removePointerListener(tag string) { } } -func getPointerListeners(view View, subviewID string, tag string) []func(View, PointerEvent) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(tag); value != nil { - if result, ok := value.([]func(View, PointerEvent)); ok { - return result - } - } - } - return []func(View, PointerEvent){} -} - func pointerEventsHtml(view View, buffer *strings.Builder) { for tag, js := range pointerEvents { if value := view.getRaw(tag); value != nil { @@ -291,7 +152,7 @@ func (event *PointerEvent) init(data DataObject) { } func handlePointerEvents(view View, tag string, data DataObject) { - listeners := getPointerListeners(view, "", tag) + listeners := getEventListeners[View, PointerEvent](view, nil, tag) if len(listeners) == 0 { return } @@ -305,37 +166,37 @@ 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. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerDownListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerDown) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerDownListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerDown) } // GetPointerUpListeners returns the "pointer-up" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerUpListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerUp) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerUpListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerUp) } // GetPointerMoveListeners returns the "pointer-move" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerMoveListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerMove) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerMoveListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerMove) } // GetPointerCancelListeners returns the "pointer-cancel" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerCancelListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerCancel) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerCancelListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerCancel) } // GetPointerOverListeners returns the "pointer-over" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerOverListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerOver) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerOverListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerOver) } // GetPointerOutListeners returns the "pointer-out" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetPointerOutListeners(view View, subviewID string) []func(View, PointerEvent) { - return getPointerListeners(view, subviewID, PointerOut) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetPointerOutListeners(view View, subviewID ...string) []func(View, PointerEvent) { + return getEventListeners[View, PointerEvent](view, subviewID, PointerOut) } diff --git a/popup.go b/popup.go index b4ac0e3..957318d 100644 --- a/popup.go +++ b/popup.go @@ -1,6 +1,8 @@ package rui -import "strings" +import ( + "strings" +) const ( // Title is the constant for the "title" property tag. @@ -36,6 +38,46 @@ const ( // It occurs after the Popup disappears from the screen. // The main listener for this event has the following format: func(Popup) DismissEvent = "dismiss-event" + + // Arrow is the constant for the "arrow" property tag. + // Using the "popup-arrow" int property you can add ... + Arrow = "arrow" + + // ArrowAlign is the constant for the "arrow-align" property tag. + // The "arrow-align" int property is used for set the horizontal alignment of the Popup arrow. + // Valid values: LeftAlign (0), RightAlign (1), TopAlign (0), BottomAlign (1), CenterAlign (2) + ArrowAlign = "arrow-align" + + // ArrowSize is the constant for the "arrow-size" property tag. + // The "arrow-size" SizeUnit property is used for set the size (length) of the Popup arrow. + ArrowSize = "arrow-size" + + // ArrowWidth is the constant for the "arrow-width" property tag. + // The "arrow-width" SizeUnit property is used for set the width of the Popup arrow. + ArrowWidth = "arrow-width" + + // ArrowOffset is the constant for the "arrow-offset" property tag. + // The "arrow-offset" SizeUnit property is used for set the offset of the Popup arrow. + ArrowOffset = "arrow-offset" + + // NoneArrow is value of the popup "arrow" property: no arrow + NoneArrow = 0 + + // TopArrow is value of the popup "arrow" property: + // Arrow at the top side of the pop-up window + TopArrow = 1 + + // RightArrow is value of the popup "arrow" property: + // Arrow on the right side of the pop-up window + RightArrow = 2 + + // BottomArrow is value of the popup "arrow" property: + // Arrow at the bottom of the pop-up window + BottomArrow = 3 + + // LeftArrow is value of the popup "arrow" property: + // Arrow on the left side of the pop-up window + LeftArrow = 4 ) // PopupButton describes a button that will be placed at the bottom of the window. @@ -65,152 +107,388 @@ type popupManager struct { popups []Popup } -func (popup *popupData) init(view View, params Params) { +type popupArrow struct { + column, row int + location, align int + size, width, off SizeUnit +} + +func (arrow *popupArrow) fixOff(popupView View) { + if arrow.align == CenterAlign && arrow.off.Type == Auto { + r := GetRadius(popupView) + switch arrow.location { + case TopArrow: + switch arrow.align { + case LeftAlign: + arrow.off = r.TopLeftX + + case RightAlign: + arrow.off = r.TopRightX + } + + case BottomArrow: + switch arrow.align { + case LeftAlign: + arrow.off = r.BottomLeftX + + case RightAlign: + arrow.off = r.BottomRightX + } + + case RightArrow: + switch arrow.align { + case TopAlign: + arrow.off = r.TopRightY + + case BottomAlign: + arrow.off = r.BottomRightY + } + + case LeftArrow: + switch arrow.align { + case TopAlign: + arrow.off = r.TopLeftY + + case BottomAlign: + arrow.off = r.BottomLeftY + } + } + } +} + +func (arrow *popupArrow) createView(popupView View) View { + session := popupView.Session() + + defaultSize := func(constTag string, defValue SizeUnit) SizeUnit { + if value, ok := session.Constant(constTag); ok { + if size, ok := StringToSizeUnit(value); ok && size.Type != Auto && size.Value != 0 { + return size + } + } + return defValue + } + + if arrow.size.Type == Auto || arrow.size.Value == 0 { + arrow.size = defaultSize("ruiArrowSize", Px(16)) + } + + if arrow.width.Type == Auto || arrow.width.Value == 0 { + arrow.width = defaultSize("ruiArrowWidth", Px(16)) + } + + params := Params{BackgroundColor: GetBackgroundColor(popupView)} + + if shadow := GetViewShadows(popupView); shadow != nil { + params[Shadow] = shadow + } + + if filter := GetBackdropFilter(popupView); filter != nil { + params[BackdropFilter] = filter + } + + switch arrow.location { + case TopArrow: + params[Row] = 0 + params[Column] = 1 + params[Clip] = PolygonClip([]any{"0%", "100%", "50%", "0%", "100%", "100%"}) + params[Width] = arrow.width + params[Height] = arrow.size + + case RightArrow: + params[Row] = 1 + params[Column] = 0 + params[Clip] = PolygonClip([]any{"0%", "0%", "100%", "50%", "0%", "100%"}) + params[Width] = arrow.size + params[Height] = arrow.width + + case BottomArrow: + params[Row] = 0 + params[Column] = 1 + params[Clip] = PolygonClip([]any{"0%", "0%", "50%", "100%", "100%", "0%"}) + params[Width] = arrow.width + params[Height] = arrow.size + + case LeftArrow: + params[Row] = 1 + params[Column] = 0 + params[Clip] = PolygonClip([]any{"100%", "0%", "0%", "50%", "100%", "100%"}) + params[Width] = arrow.size + params[Height] = arrow.width + } + + arrowView := NewView(session, params) + + params = Params{ + Row: arrow.row, + Column: arrow.column, + Content: arrowView, + } + + arrow.fixOff(popupView) + + switch arrow.location { + case TopArrow, BottomArrow: + cellWidth := make([]SizeUnit, 3) + switch arrow.align { + case LeftAlign: + cellWidth[0] = arrow.off + cellWidth[2] = Fr(1) + + case RightAlign: + cellWidth[0] = Fr(1) + cellWidth[2] = arrow.off + + default: + cellWidth[0] = Fr(1) + cellWidth[2] = Fr(1) + if arrow.off.Type != Auto && arrow.off.Value != 0 { + arrowView.Set(MarginLeft, arrow.off) + } + } + params[CellWidth] = cellWidth + + case RightArrow, LeftArrow: + cellHeight := make([]SizeUnit, 3) + switch arrow.align { + case TopAlign: + cellHeight[0] = arrow.off + cellHeight[2] = Fr(1) + + case BottomAlign: + cellHeight[0] = Fr(1) + cellHeight[2] = arrow.off + + default: + cellHeight[0] = Fr(1) + cellHeight[2] = Fr(1) + if arrow.off.Type != Auto && arrow.off.Value != 0 { + arrowView.Set(MarginTop, arrow.off) + } + } + params[CellHeight] = cellHeight + } + + return NewGridLayout(session, params) +} + +func (popup *popupData) init(view View, popupParams Params) { popup.view = view session := view.Session() - if params == nil { - params = Params{} + columnCount := 3 + rowCount := 3 + popupRow := 1 + popupColumn := 1 + arrow := popupArrow{ + row: 1, + column: 1, + align: CenterAlign, } - popup.dismissListener = []func(Popup){} - if value, ok := params[DismissEvent]; ok && value != nil { - switch value := value.(type) { - case func(Popup): - popup.dismissListener = []func(Popup){value} + switch arrow.location, _ = enumProperty(popupParams, Arrow, session, NoneArrow); arrow.location { + case TopArrow: + rowCount = 4 + popupRow = 2 - case func(): - popup.dismissListener = []func(Popup){ - func(_ Popup) { - value() - }, - } + case BottomArrow: + rowCount = 4 + arrow.row = 2 - case []func(Popup): - for _, fn := range value { - if fn != nil { - popup.dismissListener = append(popup.dismissListener, fn) - } - } + case LeftArrow: + columnCount = 4 + popupColumn = 2 - case []func(): - for _, fn := range value { - if fn != nil { - popup.dismissListener = append(popup.dismissListener, func(_ Popup) { - fn() - }) - } - } + case RightArrow: + columnCount = 4 + arrow.column = 2 - case []interface{}: - for _, val := range value { - if val != nil { - switch fn := val.(type) { - case func(Popup): - popup.dismissListener = append(popup.dismissListener, fn) - - case func(): - popup.dismissListener = append(popup.dismissListener, func(_ Popup) { - fn() - }) - } - } - } - } + default: } - var title View = nil - titleStyle := "ruiPopupTitle" - closeButton, _ := boolProperty(params, CloseButton, session) - outsideClose, _ := boolProperty(params, OutsideClose, session) - vAlign, _ := enumProperty(params, VerticalAlign, session, CenterAlign) - hAlign, _ := enumProperty(params, HorizontalAlign, session, CenterAlign) + cellWidth := make([]SizeUnit, columnCount) + switch hAlign, _ := enumProperty(popupParams, HorizontalAlign, session, CenterAlign); hAlign { + case LeftAlign: + cellWidth[columnCount-1] = Fr(1) - buttons := []PopupButton{} - if value, ok := params[Buttons]; ok && value != nil { - switch value := value.(type) { - case PopupButton: - buttons = []PopupButton{value} + case RightAlign: + cellWidth[0] = Fr(1) - case []PopupButton: - buttons = value - } + default: + cellWidth[0] = Fr(1) + cellWidth[columnCount-1] = Fr(1) } - popupView := NewGridLayout(view.Session(), Params{ + cellHeight := make([]SizeUnit, rowCount) + switch vAlign, _ := enumProperty(popupParams, VerticalAlign, session, CenterAlign); vAlign { + case LeftAlign: + cellHeight[rowCount-1] = Fr(1) + + case RightAlign: + cellHeight[0] = Fr(1) + + default: + cellHeight[0] = Fr(1) + cellHeight[rowCount-1] = Fr(1) + } + + layerParams := Params{ + Style: "ruiPopupLayer", + MaxWidth: Percent(100), + MaxHeight: Percent(100), + CellWidth: cellWidth, + CellHeight: cellHeight, + } + + params := Params{ Style: "ruiPopup", + Row: popupRow, + Column: popupColumn, MaxWidth: Percent(100), MaxHeight: Percent(100), CellVerticalAlign: StretchAlign, CellHorizontalAlign: StretchAlign, ClickEvent: func(View) {}, - }) + Shadow: NewShadowWithParams(Params{ + SpreadRadius: Px(4), + Blur: Px(16), + ColorTag: "@ruiPopupShadow", + }), + } - for tag, value := range params { - switch tag { - case Title: - switch value := value.(type) { - case string: - title = NewTextView(view.Session(), Params{Text: value}) + var closeButton View = nil + outsideClose := false + buttons := []PopupButton{} + titleStyle := "ruiPopupTitle" + var title View = nil - case View: - title = value + for tag, value := range popupParams { + if value != nil { + switch tag { + case VerticalAlign, HorizontalAlign, Arrow, Row, Column: + // Do nothing + + case Margin: + layerParams[Padding] = value + + case MarginLeft: + layerParams[PaddingLeft] = value + + case MarginRight: + layerParams[PaddingRight] = value + + case MarginTop: + layerParams[PaddingTop] = value + + case MarginBottom: + layerParams[PaddingBottom] = value + + case CloseButton: + closeButton = NewGridLayout(session, Params{ + Column: 1, + Height: "@ruiPopupTitleHeight", + Width: "@ruiPopupTitleHeight", + CellHorizontalAlign: CenterAlign, + CellVerticalAlign: CenterAlign, + TextSize: Px(20), + Content: "✕", + NotTranslate: true, + ClickEvent: func(View) { + popup.Dismiss() + }, + }) + + case OutsideClose: + outsideClose, _ = boolProperty(popupParams, OutsideClose, session) + + case Buttons: + switch value := value.(type) { + case PopupButton: + buttons = []PopupButton{value} + + case []PopupButton: + buttons = value + } + + case Title: + switch value := value.(type) { + case string: + title = NewTextView(view.Session(), Params{Text: value}) + + case View: + title = value + + default: + notCompatibleType(Title, value) + } + + case TitleStyle: + switch value := value.(type) { + case string: + titleStyle = value + + default: + notCompatibleType(TitleStyle, value) + } + + case DismissEvent: + if listeners, ok := valueToNoParamListeners[Popup](value); ok { + if listeners != nil { + popup.dismissListener = listeners + } + } else { + notCompatibleType(tag, value) + } + + case ArrowAlign: + switch text := value.(type) { + case string: + switch text { + case "top": + value = "left" + + case "bottom": + value = "right" + } + } + arrow.align, _ = enumProperty(popupParams, ArrowAlign, session, CenterAlign) + + case ArrowSize: + arrow.size, _ = sizeProperty(popupParams, ArrowSize, session) + + case ArrowOffset: + arrow.off, _ = sizeProperty(popupParams, ArrowOffset, session) default: - notCompatibleType(Title, value) + params[tag] = value } - - case TitleStyle: - switch value := value.(type) { - case string: - titleStyle = value - - default: - notCompatibleType(TitleStyle, value) - } - - case CloseButton, OutsideClose, VerticalAlign, HorizontalAlign, Buttons: - // do nothing - - default: - popupView.Set(tag, value) } } - var cellHeight []SizeUnit + popupView := NewGridLayout(view.Session(), params) + + var popupCellHeight []SizeUnit viewRow := 0 - if title != nil || closeButton { - viewRow = 1 - titleHeight, _ := sizeConstant(popup.Session(), "ruiPopupTitleHeight") - titleView := NewGridLayout(session, Params{ + if title != nil || closeButton != nil { + titleContent := []View{} + if title != nil { + titleContent = append(titleContent, title) + } + if closeButton != nil { + titleContent = append(titleContent, closeButton) + } + popupView.Append(NewGridLayout(session, Params{ Row: 0, Style: titleStyle, - CellWidth: []SizeUnit{Fr(1), titleHeight}, + CellWidth: []any{Fr(1), AutoSize()}, CellVerticalAlign: CenterAlign, PaddingLeft: Px(12), - }) - if title != nil { - titleView.Append(title) - } - if closeButton { - titleView.Append(NewGridLayout(session, Params{ - Column: 1, - Height: titleHeight, - Width: titleHeight, - CellHorizontalAlign: CenterAlign, - CellVerticalAlign: CenterAlign, - TextSize: Px(20), - Content: "✕", - ClickEvent: func(View) { - popup.Dismiss() - }, - })) - } + Content: titleContent, + })) - popupView.Append(titleView) - cellHeight = []SizeUnit{AutoSize(), Fr(1)} + viewRow = 1 + popupCellHeight = []SizeUnit{AutoSize(), Fr(1)} } else { - cellHeight = []SizeUnit{Fr(1)} + popupCellHeight = []SizeUnit{Fr(1)} } view.Set(Row, viewRow) @@ -218,7 +496,7 @@ func (popup *popupData) init(view View, params Params) { if buttonCount := len(buttons); buttonCount > 0 { buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign) - cellHeight = append(cellHeight, AutoSize()) + popupCellHeight = append(popupCellHeight, AutoSize()) gap, _ := sizeConstant(session, "ruiPopupButtonGap") cellWidth := []SizeUnit{} for i := 0; i < buttonCount; i++ { @@ -256,16 +534,16 @@ func (popup *popupData) init(view View, params Params) { Content: buttonsPanel, })) } - popupView.Set(CellHeight, cellHeight) - popup.layerView = NewGridLayout(session, Params{ - Style: "ruiPopupLayer", - CellVerticalAlign: vAlign, - CellHorizontalAlign: hAlign, - Content: NewColumnLayout(session, Params{Content: popupView}), - MaxWidth: Percent(100), - MaxHeight: Percent(100), - }) + popupView.Set(CellHeight, popupCellHeight) + + if arrow.location != NoneArrow { + layerParams[Content] = []View{popupView, arrow.createView(popupView)} + } else { + layerParams[Content] = []View{popupView} + } + + popup.layerView = NewGridLayout(session, layerParams) if outsideClose { popup.layerView.Set(ClickEvent, func(View) { diff --git a/popupUtils.go b/popupUtils.go index bcc696d..9275a2b 100644 --- a/popupUtils.go +++ b/popupUtils.go @@ -103,19 +103,22 @@ func ShowCancellableQuestion(title, text string, session Session, onYes func(), } type popupMenuData struct { - items []string - session Session - popup Popup - result func(int) + items []string + disabled []int + session Session + popup Popup + result func(int) } func (popup *popupMenuData) itemClick(list ListView, n int) { - if popup.popup != nil { - popup.popup.Dismiss() - popup.popup = nil - } - if popup.result != nil { - popup.result(n) + if popup.IsListItemEnabled(n) { + if popup.popup != nil { + popup.popup.Dismiss() + popup.popup = nil + } + if popup.result != nil { + popup.result(n) + } } } @@ -131,6 +134,13 @@ func (popup *popupMenuData) ListItem(index int, session Session) View { } func (popup *popupMenuData) IsListItemEnabled(index int) bool { + if popup.disabled != nil { + for _, n := range popup.disabled { + if index == n { + return false + } + } + } return true } @@ -165,13 +175,18 @@ func ShowMenu(session Session, params Params) Popup { return nil } - value, ok = params[PopupMenuResult] - if ok && value != nil { + if value, ok := params[PopupMenuResult]; ok && value != nil { if result, ok := value.(func(int)); ok { data.result = result } } + if value, ok := params[DisabledItems]; ok && value != nil { + if value, ok := value.([]int); ok { + data.disabled = value + } + } + listView := NewListView(session, Params{ Items: adapter, Orientation: TopDownOrientation, @@ -181,7 +196,7 @@ func ShowMenu(session Session, params Params) Popup { popupParams := Params{} for tag, value := range params { switch tag { - case Items, PopupMenuResult: + case Items, PopupMenuResult, DisabledItems: // do nothing default: diff --git a/progressBar.go b/progressBar.go index 9ee5347..f93a372 100644 --- a/progressBar.go +++ b/progressBar.go @@ -22,7 +22,7 @@ type progressBarData struct { // NewProgressBar create new ProgressBar object and return it func NewProgressBar(session Session, params Params) ProgressBar { view := new(progressBarData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -31,8 +31,8 @@ func newProgressBar(session Session) View { return NewProgressBar(session, nil) } -func (progress *progressBarData) Init(session Session) { - progress.viewData.Init(session) +func (progress *progressBarData) init(session Session) { + progress.viewData.init(session) progress.tag = "ProgressBar" } @@ -65,19 +65,19 @@ func (progress *progressBarData) propertyChanged(tag string) { if progress.created { switch tag { case ProgressBarMax: - updateProperty(progress.htmlID(), Max, strconv.FormatFloat(GetProgressBarMax(progress, ""), 'f', -1, 32), progress.session) + updateProperty(progress.htmlID(), Max, strconv.FormatFloat(GetProgressBarMax(progress), 'f', -1, 32), progress.session) case ProgressBarValue: - updateProperty(progress.htmlID(), Value, strconv.FormatFloat(GetProgressBarValue(progress, ""), 'f', -1, 32), progress.session) + updateProperty(progress.htmlID(), Value, strconv.FormatFloat(GetProgressBarValue(progress), 'f', -1, 32), progress.session) } } } -func (progress *progressBarData) Set(tag string, value interface{}) bool { +func (progress *progressBarData) Set(tag string, value any) bool { return progress.set(progress.normalizeTag(tag), value) } -func (progress *progressBarData) set(tag string, value interface{}) bool { +func (progress *progressBarData) set(tag string, value any) bool { if progress.viewData.set(tag, value) { progress.propertyChanged(tag) return true @@ -85,7 +85,7 @@ func (progress *progressBarData) set(tag string, value interface{}) bool { return false } -func (progress *progressBarData) Get(tag string) interface{} { +func (progress *progressBarData) Get(tag string) any { return progress.get(progress.normalizeTag(tag)) } @@ -97,44 +97,22 @@ func (progress *progressBarData) htmlProperties(self View, buffer *strings.Build progress.viewData.htmlProperties(self, buffer) buffer.WriteString(` max="`) - buffer.WriteString(strconv.FormatFloat(GetProgressBarMax(progress, ""), 'f', -1, 64)) + buffer.WriteString(strconv.FormatFloat(GetProgressBarMax(progress), 'f', -1, 64)) buffer.WriteByte('"') buffer.WriteString(` value="`) - buffer.WriteString(strconv.FormatFloat(GetProgressBarValue(progress, ""), 'f', -1, 64)) + buffer.WriteString(strconv.FormatFloat(GetProgressBarValue(progress), 'f', -1, 64)) buffer.WriteByte('"') } // GetProgressBarMax returns the max value of ProgressBar subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetProgressBarMax(view View, subviewID string) float64 { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - - result, ok := floatStyledProperty(view, ProgressBarMax, 1) - if !ok { - result, _ = floatStyledProperty(view, Max, 1) - } - return result +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetProgressBarMax(view View, subviewID ...string) float64 { + return floatStyledProperty(view, subviewID, ProgressBarMax, 1) } // GetProgressBarValue returns the value of ProgressBar subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetProgressBarValue(view View, subviewID string) float64 { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - - result, ok := floatStyledProperty(view, ProgressBarValue, 0) - if !ok { - result, _ = floatStyledProperty(view, Value, 0) - } - return result +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetProgressBarValue(view View, subviewID ...string) float64 { + return floatStyledProperty(view, subviewID, ProgressBarValue, 0) } diff --git a/properties.go b/properties.go index 1b27e80..a53ef8e 100644 --- a/properties.go +++ b/properties.go @@ -9,13 +9,13 @@ import ( type Properties interface { // 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. - Get(tag string) interface{} - getRaw(tag string) interface{} + Get(tag string) any + getRaw(tag string) any // 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 // a description of the error is written to the log - Set(tag string, value interface{}) bool - setRaw(tag string, value interface{}) + Set(tag string, value any) bool + setRaw(tag string, value any) // Remove removes the property with name defined by the argument Remove(tag string) // Clear removes all properties @@ -25,25 +25,25 @@ type Properties interface { } type propertyList struct { - properties map[string]interface{} + properties map[string]any } func (properties *propertyList) init() { - properties.properties = map[string]interface{}{} + properties.properties = map[string]any{} } -func (properties *propertyList) Get(tag string) interface{} { +func (properties *propertyList) Get(tag string) any { return properties.getRaw(strings.ToLower(tag)) } -func (properties *propertyList) getRaw(tag string) interface{} { +func (properties *propertyList) getRaw(tag string) any { if value, ok := properties.properties[tag]; ok { return value } return nil } -func (properties *propertyList) setRaw(tag string, value interface{}) { +func (properties *propertyList) setRaw(tag string, value any) { properties.properties[tag] = value } @@ -56,7 +56,7 @@ func (properties *propertyList) remove(tag string) { } func (properties *propertyList) Clear() { - properties.properties = map[string]interface{}{} + properties.properties = map[string]any{} } func (properties *propertyList) AllTags() []string { diff --git a/properties_test.go b/properties_test.go index 3de1304..840e04d 100644 --- a/properties_test.go +++ b/properties_test.go @@ -32,49 +32,49 @@ func TestProperties(t *testing.T) { t.Error(`list.Get("name") is not string`) } - sizeValues := []interface{}{"@small", "auto", "10px", Pt(20), AutoSize()} + sizeValues := []any{"@small", "auto", "10px", Pt(20), AutoSize()} for _, value := range sizeValues { if !list.setSizeProperty("size", value) { t.Errorf(`setSizeProperty("size", %v) fail`, value) } } - failSizeValues := []interface{}{"@small,big", "abc", "10", Color(20), 100} + failSizeValues := []any{"@small,big", "abc", "10", Color(20), 100} for _, value := range failSizeValues { if list.setSizeProperty("size", value) { t.Errorf(`setSizeProperty("size", %v) success`, value) } } - angleValues := []interface{}{"@angle", "2pi", "π", "3deg", "60°", Rad(1.5), Deg(45), 1, 1.5} + angleValues := []any{"@angle", "2pi", "π", "3deg", "60°", Rad(1.5), Deg(45), 1, 1.5} for _, value := range angleValues { if !list.setAngleProperty("angle", value) { t.Errorf(`setAngleProperty("angle", %v) fail`, value) } } - failAngleValues := []interface{}{"@angle,2", "pi32", "deg", "60°x", Color(0xFFFFFFFF)} + failAngleValues := []any{"@angle,2", "pi32", "deg", "60°x", Color(0xFFFFFFFF)} for _, value := range failAngleValues { if list.setAngleProperty("angle", value) { t.Errorf(`setAngleProperty("angle", %v) success`, value) } } - colorValues := []interface{}{"@color", "#FF234567", "#234567", "rgba(30%, 128, 0.5, .25)", "rgb(30%, 128, 0.5)", Color(0xFFFFFFFF), 0xFFFFFFFF, White} + colorValues := []any{"@color", "#FF234567", "#234567", "rgba(30%, 128, 0.5, .25)", "rgb(30%, 128, 0.5)", Color(0xFFFFFFFF), 0xFFFFFFFF, White} for _, color := range colorValues { if !list.setColorProperty("color", color) { t.Errorf(`list.setColorProperty("color", %v) fail`, color) } } - failColorValues := []interface{}{"@color|2", "#FF234567FF", "#TT234567", "rgba(500%, 128, 10.5, .25)", 10.6} + failColorValues := []any{"@color|2", "#FF234567FF", "#TT234567", "rgba(500%, 128, 10.5, .25)", 10.6} for _, color := range failColorValues { if list.setColorProperty("color", color) { t.Errorf(`list.setColorProperty("color", %v) success`, color) } } - enumValues := []interface{}{"@enum", "inherit", "on", Inherit, 2} + enumValues := []any{"@enum", "inherit", "on", Inherit, 2} inheritOffOn := inheritOffOnValues() for _, value := range enumValues { if !list.setEnumProperty("enum", value, inheritOffOn) { @@ -82,56 +82,56 @@ func TestProperties(t *testing.T) { } } - failEnumValues := []interface{}{"@enum 13", "inherit2", "onn", -1, 10} + failEnumValues := []any{"@enum 13", "inherit2", "onn", -1, 10} for _, value := range failEnumValues { if list.setEnumProperty("enum", value, inheritOffOn) { t.Errorf(`list.setEnumProperty("enum", %v, %v) success`, value, inheritOffOn) } } - boolValues := []interface{}{"@bool", "true", "yes ", "on", " 1", "false", "no", "off", "0", 0, 1, false, true} + boolValues := []any{"@bool", "true", "yes ", "on", " 1", "false", "no", "off", "0", 0, 1, false, true} for _, value := range boolValues { if !list.setBoolProperty("bool", value) { t.Errorf(`list.setBoolProperty("bool", %v) fail`, value) } } - failBoolValues := []interface{}{"@bool,2", "tr", "ys", "10", -1, 10, 0.8} + failBoolValues := []any{"@bool,2", "tr", "ys", "10", -1, 10, 0.8} for _, value := range failBoolValues { if list.setBoolProperty("bool", value) { t.Errorf(`list.setBoolProperty("bool", %v) success`, value) } } - intValues := []interface{}{"@int", " 100", "-10 ", 0, 250} + intValues := []any{"@int", " 100", "-10 ", 0, 250} for _, value := range intValues { if !list.setIntProperty("int", value) { t.Errorf(`list.setIntProperty("int", %v) fail`, value) } } - failIntValues := []interface{}{"@int|10", "100i", "-1.0 ", 0.0} + failIntValues := []any{"@int|10", "100i", "-1.0 ", 0.0} for _, value := range failIntValues { if list.setIntProperty("int", value) { t.Errorf(`list.setIntProperty("int", %v) success`, value) } } - floatValues := []interface{}{"@float", " 100.25", "-1.5e12 ", uint(0), 250, float32(10.2), float64(0)} + floatValues := []any{"@float", " 100.25", "-1.5e12 ", uint(0), 250, float32(10.2), float64(0)} for _, value := range floatValues { if !list.setFloatProperty("float", value) { t.Errorf(`list.setFloatProperty("float", %v) fail`, value) } } - failFloatValues := []interface{}{"@float|2", " 100.25i", "-1.5ee12 ", "abc"} + failFloatValues := []any{"@float|2", " 100.25i", "-1.5ee12 ", "abc"} for _, value := range failFloatValues { if list.setFloatProperty("float", value) { t.Errorf(`list.setFloatProperty("float", %v) success`, value) } } - boundsValues := []interface{}{"@bounds", "10px,20pt,@bottom,0", Em(2), []interface{}{"@top", Px(10), AutoSize(), "14pt"}} + boundsValues := []any{"@bounds", "10px,20pt,@bottom,0", Em(2), []any{"@top", Px(10), AutoSize(), "14pt"}} for _, value := range boundsValues { if !list.setBoundsProperty("margin", value) { t.Errorf(`list.setBoundsProperty("margin", %v) fail`, value) diff --git a/propertyGet.go b/propertyGet.go index 6b102d8..cc74a54 100644 --- a/propertyGet.go +++ b/propertyGet.go @@ -1,6 +1,7 @@ package rui import ( + "fmt" "strconv" "strings" ) @@ -31,16 +32,29 @@ func imageProperty(properties Properties, tag string, session Session) (string, return "", false } -func valueToSizeUnit(value interface{}, session Session) (SizeUnit, bool) { +func valueToSizeUnit(value any, session Session) (SizeUnit, bool) { if value != nil { switch value := value.(type) { case SizeUnit: return value, true + case SizeFunc: + return SizeUnit{Type: SizeFunction, Function: value}, true + case string: if text, ok := session.resolveConstants(value); ok { return StringToSizeUnit(text) } + + case float64: + return Px(value), true + + case float32: + return Px(float64(value)), true + } + + if n, ok := isInt(value); ok { + return Px(float64(n)), true } } @@ -67,7 +81,7 @@ func angleProperty(properties Properties, tag string, session Session) (AngleUni return AngleUnit{Type: 0, Value: 0}, false } -func valueToColor(value interface{}, session Session) (Color, bool) { +func valueToColor(value any, session Session) (Color, bool) { if value != nil { switch value := value.(type) { case Color: @@ -88,7 +102,7 @@ func colorProperty(properties Properties, tag string, session Session) (Color, b return valueToColor(properties.getRaw(tag), session) } -func valueToEnum(value interface{}, tag string, session Session, defaultValue int) (int, bool) { +func valueToEnum(value any, tag string, session Session, defaultValue int) (int, bool) { if value != nil { values := enumProperties[tag].values switch value := value.(type) { @@ -155,7 +169,7 @@ func enumProperty(properties Properties, tag string, session Session, defaultVal return valueToEnum(properties.getRaw(tag), tag, session, defaultValue) } -func valueToBool(value interface{}, session Session) (bool, bool) { +func valueToBool(value any, session Session) (bool, bool) { if value != nil { switch value := value.(type) { case bool: @@ -184,7 +198,7 @@ func boolProperty(properties Properties, tag string, session Session) (bool, boo return valueToBool(properties.getRaw(tag), session) } -func valueToInt(value interface{}, session Session, defaultValue int) (int, bool) { +func valueToInt(value any, session Session, defaultValue int) (int, bool) { if value != nil { switch value := value.(type) { case string: @@ -214,7 +228,7 @@ func intProperty(properties Properties, tag string, session Session, defaultValu return valueToInt(properties.getRaw(tag), session, defaultValue) } -func valueToFloat(value interface{}, session Session, defaultValue float64) (float64, bool) { +func valueToFloat(value any, session Session, defaultValue float64) (float64, bool) { if value != nil { switch value := value.(type) { case float64: @@ -238,7 +252,31 @@ func floatProperty(properties Properties, tag string, session Session, defaultVa return valueToFloat(properties.getRaw(tag), session, defaultValue) } -func valueToRange(value interface{}, session Session) (Range, bool) { +func valueToFloatText(value any, session Session, defaultValue float64) (string, bool) { + if value != nil { + switch value := value.(type) { + case float64: + return fmt.Sprintf("%g", value), true + + case string: + if text, ok := session.resolveConstants(value); ok { + if _, err := strconv.ParseFloat(text, 64); err != nil { + ErrorLog(err.Error()) + return fmt.Sprintf("%g", defaultValue), false + } + return text, true + } + } + } + + return fmt.Sprintf("%g", defaultValue), false +} + +func floatTextProperty(properties Properties, tag string, session Session, defaultValue float64) (string, bool) { + return valueToFloatText(properties.getRaw(tag), session, defaultValue) +} + +func valueToRange(value any, session Session) (Range, bool) { if value != nil { switch value := value.(type) { case Range: diff --git a/propertyNames.go b/propertyNames.go index 00e32bd..b3c4abe 100644 --- a/propertyNames.go +++ b/propertyNames.go @@ -45,6 +45,12 @@ const ( // Opacity is the degree to which content behind an element is hidden, and is the opposite of transparency. Opacity = "opacity" + // Overflow is the constant for the "overflow" property tag. + // The "overflow" int property sets the desired behavior for an element's overflow — i.e. + // when an element's content is too big to fit in its block formatting context — in both directions. + // Valid values: OverflowHidden (0), OverflowVisible (1), OverflowScroll (2), OverflowAuto (3) + Overflow = "overflow" + // Row is the constant for the "row" property tag. Row = "row" @@ -141,6 +147,10 @@ const ( // The "padding-bottom" SizeUnit property sets the height of the padding area to the bottom of a view. PaddingBottom = "padding-bottom" + // AccentColor is the constant for the "accent-color" property tag. + // The "accent-color" property sets sets the accent color for UI controls generated by some elements. + AccentColor = "accent-color" + // BackgroundColor is the constant for the "background-color" property tag. // The "background-color" property sets the background color of a view. BackgroundColor = "background-color" @@ -314,20 +324,20 @@ const ( // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. Underline = "underline" - // TextLineThickness is the constant for the "text-decoration-thickness" property tag. - // The "text-decoration-thickness" SizeUnit property sets the stroke thickness of the decoration line that + // TextLineThickness is the constant for the "text-line-thickness" property tag. + // The "text-line-thickness" SizeUnit property sets the stroke thickness of the decoration line that // is used on text in an element, such as a line-through, underline, or overline. // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. TextLineThickness = "text-line-thickness" - // TextLineStyle is the constant for the "text-decoration-style" property tag. - // The "text-decoration-style" int property sets the style of the lines specified by "text-decoration" property. + // TextLineStyle is the constant for the "text-line-style" property tag. + // The "text-line-style" int property sets the style of the lines specified by "text-decoration" property. // The style applies to all lines that are set with "text-decoration" property. // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. TextLineStyle = "text-line-style" - // TextLineColor is the constant for the "text-decoration-color" property tag. - // The "text-decoration-color" Color property sets the color of the lines specified by "text-decoration" property. + // TextLineColor is the constant for the "text-line-color" property tag. + // The "text-line-color" Color property sets the color of the lines specified by "text-decoration" property. // The color applies to all lines that are set with "text-decoration" property. // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. TextLineColor = "text-line-color" @@ -349,6 +359,11 @@ const ( // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. TextShadow = "text-shadow" + // TabSize is the constant for the "tab-size" property tag. + // The "tab-size" int property sets the width of tab characters (U+0009) in spaces. + // This is an inherited property, i.e. if it is not defined, then the value of the parent view is used. + TabSize = "tab-size" + // LetterSpacing is the constant for the "letter-spacing" property tag. // The "letter-spacing" SizeUnit property sets the horizontal spacing behavior between text characters. // This value is added to the natural spacing between characters while rendering the text. @@ -553,9 +568,19 @@ const ( // Orientation is the constant for the "orientation" property tag. Orientation = "orientation" - // Gap is the constant for the "gap" property tag. + // Gap is t he constant for the "gap" property tag. Gap = "gap" + // ListRowGap is the constant for the "list-row-gap" property tag. + // The "list-row-gap" SizeUnit properties allow to set the distance between the rows of the ListLayout or ListView. + // The default is 0px. + ListRowGap = "list-row-gap" + + // ListColumnGap is the constant for the "list-column-gap" property tag. + // The "list-column-gap" SizeUnit properties allow to set the distance between the columns of the GridLayout or ListView. + // The default is 0px. + ListColumnGap = "list-column-gap" + // Text is the constant for the "text" property tag. Text = "text" diff --git a/propertySet.go b/propertySet.go index ac78adc..34ead12 100644 --- a/propertySet.go +++ b/propertySet.go @@ -67,6 +67,7 @@ var boolProperties = []string{ var intProperties = []string{ ZIndex, + TabSize, HeadHeight, FootHeight, RowSpan, @@ -109,6 +110,8 @@ var sizeProperties = map[string]string{ WordSpacing: WordSpacing, LineHeight: LineHeight, TextLineThickness: "text-decoration-thickness", + ListRowGap: "row-gap", + ListColumnGap: "column-gap", GridRowGap: GridRowGap, GridColumnGap: GridColumnGap, ColumnWidth: ColumnWidth, @@ -179,6 +182,11 @@ var enumProperties = map[string]struct { "", []string{"visible", "invisible", "gone"}, }, + Overflow: { + []string{"hidden", "visible", "scroll", "auto"}, + Overflow, + []string{"hidden", "visible", "scroll", "auto"}, + }, TextAlign: { []string{"left", "right", "center", "justify"}, TextAlign, @@ -304,6 +312,11 @@ var enumProperties = map[string]struct { "", []string{"left", "right", "center", "stretch"}, }, + ArrowAlign: { + []string{"left", "right", "center"}, + "", + []string{"left", "right", "center"}, + }, CellVerticalAlign: { []string{"top", "bottom", "center", "stretch"}, "align-items", @@ -429,13 +442,18 @@ var enumProperties = map[string]struct { "resize", []string{"none", "both", "horizontal", "vertical"}, }, + Arrow: { + []string{"none", "top", "right", "bottom", "left"}, + "", + []string{"none", "top", "right", "bottom", "left"}, + }, } -func notCompatibleType(tag string, value interface{}) { +func notCompatibleType(tag string, value any) { ErrorLogF(`"%T" type not compatible with "%s" property`, value, tag) } -func invalidPropertyValue(tag string, value interface{}) { +func invalidPropertyValue(tag string, value any) { ErrorLogF(`Invalid value "%v" of "%s" property`, value, tag) } @@ -457,7 +475,7 @@ func isConstantName(text string) bool { return !strings.ContainsAny(text, ",;|\"'`+(){}[]<>/\\*&%! \t\n\r") } -func isInt(value interface{}) (int, bool) { +func isInt(value any) (int, bool) { var n int switch value := value.(type) { case int: @@ -497,7 +515,7 @@ func isInt(value interface{}) (int, bool) { return n, true } -func (properties *propertyList) setSimpleProperty(tag string, value interface{}) bool { +func (properties *propertyList) setSimpleProperty(tag string, value any) bool { if value == nil { delete(properties.properties, tag) return true @@ -515,19 +533,26 @@ func (properties *propertyList) setSimpleProperty(tag string, value interface{}) return false } -func (properties *propertyList) setSizeProperty(tag string, value interface{}) bool { +func (properties *propertyList) setSizeProperty(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { var size SizeUnit switch value := value.(type) { case string: var ok bool - if size, ok = StringToSizeUnit(value); !ok { + if fn := parseSizeFunc(value); fn != nil { + size.Type = SizeFunction + size.Function = fn + } else if size, ok = StringToSizeUnit(value); !ok { invalidPropertyValue(tag, value) return false } case SizeUnit: size = value + case SizeFunc: + size.Type = SizeFunction + size.Function = value + case float32: size.Type = SizeInPixel size.Value = float64(value) @@ -556,7 +581,7 @@ func (properties *propertyList) setSizeProperty(tag string, value interface{}) b return true } -func (properties *propertyList) setAngleProperty(tag string, value interface{}) bool { +func (properties *propertyList) setAngleProperty(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { var angle AngleUnit switch value := value.(type) { @@ -589,7 +614,7 @@ func (properties *propertyList) setAngleProperty(tag string, value interface{}) return true } -func (properties *propertyList) setColorProperty(tag string, value interface{}) bool { +func (properties *propertyList) setColorProperty(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { var result Color switch value := value.(type) { @@ -621,7 +646,7 @@ func (properties *propertyList) setColorProperty(tag string, value interface{}) return true } -func (properties *propertyList) setEnumProperty(tag string, value interface{}, values []string) bool { +func (properties *propertyList) setEnumProperty(tag string, value any, values []string) bool { if !properties.setSimpleProperty(tag, value) { var n int if text, ok := value.(string); ok { @@ -646,7 +671,7 @@ func (properties *propertyList) setEnumProperty(tag string, value interface{}, v return true } -func (properties *propertyList) setBoolProperty(tag string, value interface{}) bool { +func (properties *propertyList) setBoolProperty(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { if text, ok := value.(string); ok { switch strings.ToLower(strings.Trim(text, " \t")) { @@ -683,7 +708,7 @@ func (properties *propertyList) setBoolProperty(tag string, value interface{}) b return true } -func (properties *propertyList) setIntProperty(tag string, value interface{}) bool { +func (properties *propertyList) setIntProperty(tag string, value any) bool { if !properties.setSimpleProperty(tag, value) { if text, ok := value.(string); ok { n, err := strconv.Atoi(strings.Trim(text, " \t")) @@ -704,7 +729,7 @@ func (properties *propertyList) setIntProperty(tag string, value interface{}) bo return true } -func (properties *propertyList) setFloatProperty(tag string, value interface{}, min, max float64) bool { +func (properties *propertyList) setFloatProperty(tag string, value any, min, max float64) bool { if !properties.setSimpleProperty(tag, value) { f := float64(0) switch value := value.(type) { @@ -715,6 +740,12 @@ func (properties *propertyList) setFloatProperty(tag string, value interface{}, ErrorLog(err.Error()) return false } + if f < min || f > max { + ErrorLogF(`"%T" out of range of "%s" property`, value, tag) + return false + } + properties.properties[tag] = value + return true case float32: f = float64(value) @@ -742,11 +773,11 @@ func (properties *propertyList) setFloatProperty(tag string, value interface{}, return true } -func (properties *propertyList) Set(tag string, value interface{}) bool { +func (properties *propertyList) Set(tag string, value any) bool { return properties.set(strings.ToLower(tag), value) } -func (properties *propertyList) set(tag string, value interface{}) bool { +func (properties *propertyList) set(tag string, value any) bool { if value == nil { delete(properties.properties, tag) return true diff --git a/propertyValues.go b/propertyValues.go index 218d920..27e630b 100644 --- a/propertyValues.go +++ b/propertyValues.go @@ -3,29 +3,57 @@ package rui const ( // Visible - default value of the view Visibility property: View is visible Visible = 0 + // Invisible - value of the view Visibility property: View is invisible but takes place Invisible = 1 + // Gone - value of the view Visibility property: View is invisible and does not take place Gone = 2 + // OverflowHidden - value of the view "overflow" property: + // Content is clipped if necessary to fit the padding box. No scrollbars are provided, + // and no support for allowing the user to scroll (such as by dragging or using a scroll wheel) is allowed. + // The content can be scrolled programmatically, so the element is still a scroll container. + OverflowHidden = 0 + + // OverflowVisible - value of the view "overflow" property: + // Content is not clipped and may be rendered outside the padding box. + OverflowVisible = 1 + + // OverflowScroll - value of the view "overflow" property: + // Content is clipped if necessary to fit the padding box. Browsers always display scrollbars whether or + // not any content is actually clipped, preventing scrollbars from appearing or disappearing as content changes. + OverflowScroll = 2 + + // OverflowAuto - value of the view "overflow" property: + // Depends on the browser user agent. If content fits inside the padding box, it looks the same as OverflowVisible, + // but still establishes a new block formatting context. Desktop browsers provide scrollbars if content overflows. + OverflowAuto = 3 + // NoneTextTransform - not transform text NoneTextTransform = 0 + // CapitalizeTextTransform - capitalize text CapitalizeTextTransform = 1 + // LowerCaseTextTransform - transform text to lower case LowerCaseTextTransform = 2 + // UpperCaseTextTransform - transform text to upper case UpperCaseTextTransform = 3 // HorizontalTopToBottom - content flows horizontally from left to right, vertically from top to bottom. // The next horizontal line is positioned below the previous line. HorizontalTopToBottom = 0 + // HorizontalBottomToTop - content flows horizontally from left to right, vertically from bottom to top. // The next horizontal line is positioned above the previous line. HorizontalBottomToTop = 1 + // VerticalRightToLeft - content flows vertically from top to bottom, horizontally from right to left. // The next vertical line is positioned to the left of the previous line. VerticalRightToLeft = 2 + // VerticalLeftToRight - content flows vertically from top to bottom, horizontally from left to right. // The next vertical line is positioned to the right of the previous line. VerticalLeftToRight = 3 @@ -33,6 +61,7 @@ const ( // MixedTextOrientation - rotates the characters of horizontal scripts 90° clockwise. // Lays out the characters of vertical scripts naturally. Default value. MixedTextOrientation = 0 + // UprightTextOrientation - lays out the characters of horizontal scripts naturally (upright), // as well as the glyphs for vertical scripts. Note that this keyword causes all characters // to be considered as left-to-right: the used value of "text-direction" is forced to be "left-to-right". @@ -40,62 +69,84 @@ const ( // SystemTextDirection - direction of a text and other elements defined by system. This is the default value. SystemTextDirection = 0 + // LeftToRightDirection - text and other elements go from left to right. LeftToRightDirection = 1 + //RightToLeftDirection - text and other elements go from right to left. RightToLeftDirection = 2 // ThinFont - the value of "text-weight" property: the thin (hairline) text weight ThinFont = 1 + // ExtraLightFont - the value of "text-weight" property: the extra light (ultra light) text weight ExtraLightFont = 2 + // LightFont - the value of "text-weight" property: the light text weight LightFont = 3 + // NormalFont - the value of "text-weight" property (default value): the normal text weight NormalFont = 4 + // MediumFont - the value of "text-weight" property: the medium text weight MediumFont = 5 + // SemiBoldFont - the value of "text-weight" property: the semi bold (demi bold) text weight SemiBoldFont = 6 + // BoldFont - the value of "text-weight" property: the bold text weight BoldFont = 7 + // ExtraBoldFont - the value of "text-weight" property: the extra bold (ultra bold) text weight ExtraBoldFont = 8 + // BlackFont - the value of "text-weight" property: the black (heavy) text weight BlackFont = 9 // TopAlign - top vertical-align for the "vertical-align" property TopAlign = 0 + // BottomAlign - bottom vertical-align for the "vertical-align" property BottomAlign = 1 + // LeftAlign - the left horizontal-align for the "horizontal-align" property LeftAlign = 0 + // RightAlign - the right horizontal-align for the "horizontal-align" property RightAlign = 1 + // CenterAlign - the center horizontal/vertical-align for the "horizontal-align"/"vertical-align" property CenterAlign = 2 + // StretchAlign - the stretch horizontal/vertical-align for the "horizontal-align"/"vertical-align" property StretchAlign = 3 + // JustifyAlign - the justify text align for "text-align" property JustifyAlign = 3 + // BaselineAlign - the baseline cell-vertical-align for the "cell-vertical-align" property BaselineAlign = 4 // WhiteSpaceNormal - sequences of white space are collapsed. Newline characters in the source // are handled the same as other white space. Lines are broken as necessary to fill line boxes. WhiteSpaceNormal = 0 + // WhiteSpaceNowrap - collapses white space as for normal, but suppresses line breaks (text wrapping) // within the source. WhiteSpaceNowrap = 1 + // WhiteSpacePre - sequences of white space are preserved. Lines are only broken at newline // characters in the source and at
elements. WhiteSpacePre = 2 + // WhiteSpacePreWrap - Sequences of white space are preserved. Lines are broken at newline // characters, at
, and as necessary to fill line boxes. WhiteSpacePreWrap = 3 + // WhiteSpacePreLine - sequences of white space are collapsed. Lines are broken at newline characters, // at
, and as necessary to fill line boxes. WhiteSpacePreLine = 4 + // 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. // * A line breaking opportunity exists after every preserved white space character, including between white space characters. @@ -104,12 +155,15 @@ const ( // WordBreakNormal - use the default line break rule. WordBreakNormal = 0 + // WordBreakAll - to prevent overflow, word breaks should be inserted between any two characters // (excluding Chinese/Japanese/Korean text). WordBreakAll = 1 + // WordBreakKeepAll - word breaks should not be used for Chinese/Japanese/Korean (CJK) text. // Non-CJK text behavior is the same as for normal. WordBreakKeepAll = 2 + // WordBreakWord - when the block boundaries are exceeded, the remaining whole words can be split // in an arbitrary place, unless a more suitable place for the line break is found. WordBreakWord = 3 @@ -117,6 +171,7 @@ const ( // TextOverflowClip - truncate the text at the limit of the content area, therefore the truncation // can happen in the middle of a character. TextOverflowClip = 0 + // TextOverflowEllipsis - display an ellipsis ('…', U+2026 HORIZONTAL ELLIPSIS) to represent clipped text. // The ellipsis is displayed inside the content area, decreasing the amount of text displayed. // If there is not enough space to display the ellipsis, it is clipped. @@ -124,87 +179,111 @@ const ( // DefaultSemantics - default value of the view Semantic property DefaultSemantics = 0 + // ArticleSemantics - value of the view Semantic property: view represents a self-contained // composition in a document, page, application, or site, which is intended to be // independently distributable or reusable (e.g., in syndication) ArticleSemantics = 1 + // SectionSemantics - value of the view Semantic property: view represents // a generic standalone section of a document, which doesn't have a more specific // semantic element to represent it. SectionSemantics = 2 + // AsideSemantics - value of the view Semantic property: view represents a portion // of a document whose content is only indirectly related to the document's main content. // Asides are frequently presented as sidebars or call-out boxes. AsideSemantics = 3 + // HeaderSemantics - value of the view Semantic property: view represents introductory // content, typically a group of introductory or navigational aids. It may contain // some heading elements but also a logo, a search form, an author name, and other elements. HeaderSemantics = 4 + // MainSemantics - value of the view Semantic property: view represents the dominant content // of the application. The main content area consists of content that is directly related // to or expands upon the central topic of a document, or the central functionality of an application. MainSemantics = 5 + // FooterSemantics - value of the view Semantic property: view represents a footer for its // nearest sectioning content or sectioning root element. A footer view typically contains // information about the author of the section, copyright data or links to related documents. FooterSemantics = 6 + // NavigationSemantics - value of the view Semantic property: view represents a section of // a page whose purpose is to provide navigation links, either within the current document // or to other documents. Common examples of navigation sections are menus, tables of contents, // and indexes. NavigationSemantics = 7 + // FigureSemantics - value of the view Semantic property: view represents self-contained content, // potentially with an optional caption, which is specified using the FigureCaptionSemantics view. FigureSemantics = 8 + // FigureCaptionSemantics - value of the view Semantic property: view represents a caption or // legend describing the rest of the contents of its parent FigureSemantics view. FigureCaptionSemantics = 9 + // ButtonSemantics - value of the view Semantic property: view a clickable button ButtonSemantics = 10 + // ParagraphSemantics - value of the view Semantic property: view represents a paragraph. // Paragraphs are usually represented in visual media as blocks of text separated // from adjacent blocks by blank lines and/or first-line indentation ParagraphSemantics = 11 + // H1Semantics - value of the view Semantic property: view represent of first level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H1Semantics = 12 + // H2Semantics - value of the view Semantic property: view represent of second level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H2Semantics = 13 + // H3Semantics - value of the view Semantic property: view represent of third level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H3Semantics = 14 + // H4Semantics - value of the view Semantic property: view represent of fourth level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H4Semantics = 15 + // H5Semantics - value of the view Semantic property: view represent of fifth level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H5Semantics = 16 + // H6Semantics - value of the view Semantic property: view represent of sixth level section headings. // H1Semantics is the highest section level and H6Semantics is the lowest. H6Semantics = 17 + // BlockquoteSemantics - value of the view Semantic property: view indicates that // the enclosed text is an extended quotation. BlockquoteSemantics = 18 + // CodeSemantics - value of the view Semantic property: view displays its contents styled // in a fashion intended to indicate that the text is a short fragment of computer code CodeSemantics = 19 // NoneFloat - value of the view "float" property: the View must not float. NoneFloat = 0 + // LeftFloat - value of the view "float" property: the View must float on the left side of its containing block. LeftFloat = 1 + // RightFloat - value of the view "float" property: the View must float on the right side of its containing block. RightFloat = 2 // NoneResize - value of the view "resize" property: the View The offers no user-controllable method for resizing it. NoneResize = 0 + // BothResize - value of the view "resize" property: the View displays a mechanism for allowing // the user to resize it, which may be resized both horizontally and vertically. BothResize = 1 + // HorizontalResize - value of the view "resize" property: the View displays a mechanism for allowing // the user to resize it in the horizontal direction. HorizontalResize = 2 + // VerticalResize - value of the view "resize" property: the View displays a mechanism for allowing // the user to resize it in the vertical direction. VerticalResize = 3 @@ -212,14 +291,17 @@ const ( // RowAutoFlow - value of the "grid-auto-flow" property of the GridLayout: // Views are placed by filling each row in turn, adding new rows as necessary. RowAutoFlow = 0 + // ColumnAutoFlow - value of the "grid-auto-flow" property of the GridLayout: // Views are placed by filling each column in turn, adding new columns as necessary. ColumnAutoFlow = 1 + // RowDenseAutoFlow - value of the "grid-auto-flow" property of the GridLayout: // Views are placed by filling each row, adding new rows as necessary. // "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. // This may cause views to appear out-of-order, when doing so would fill in holes left by larger views. RowDenseAutoFlow = 2 + // ColumnDenseAutoFlow - value of the "grid-auto-flow" property of the GridLayout: // Views are placed by filling each column, adding new columns as necessary. // "dense" packing algorithm attempts to fill in holes earlier in the grid, if smaller items come up later. diff --git a/radius.go b/radius.go index 17674e8..fee7f1f 100644 --- a/radius.go +++ b/radius.go @@ -109,7 +109,7 @@ type radiusPropertyData struct { // NewRadiusProperty creates the new RadiusProperty func NewRadiusProperty(params Params) RadiusProperty { result := new(radiusPropertyData) - result.properties = map[string]interface{}{} + result.properties = map[string]any{} if params != nil { for _, tag := range []string{X, Y, TopLeft, TopRight, BottomLeft, BottomRight, TopLeftX, TopLeftY, TopRightX, TopRightY, BottomLeftX, BottomLeftY, BottomRightX, BottomRightY} { @@ -172,7 +172,7 @@ func (radius *radiusPropertyData) deleteUnusedTags() { } } - equalValue := func(value1, value2 interface{}) bool { + equalValue := func(value1, value2 any) bool { switch value1 := value1.(type) { case string: switch value2 := value2.(type) { @@ -243,7 +243,7 @@ func (radius *radiusPropertyData) Remove(tag string) { } -func (radius *radiusPropertyData) Set(tag string, value interface{}) bool { +func (radius *radiusPropertyData) Set(tag string, value any) bool { if value == nil { radius.Remove(tag) return true @@ -318,7 +318,7 @@ func (radius *radiusPropertyData) Set(tag string, value interface{}) bool { return false } -func (radius *radiusPropertyData) Get(tag string) interface{} { +func (radius *radiusPropertyData) Get(tag string) any { tag = radius.normalizeTag(tag) if value, ok := radius.properties[tag]; ok { return value @@ -455,7 +455,7 @@ func (radius BoxRadius) String() string { return buffer.String() } -func (radius BoxRadius) cssValue(builder cssBuilder) { +func (radius BoxRadius) cssValue(builder cssBuilder, session Session) { if (radius.TopLeftX.Type == Auto || radius.TopLeftX.Value == 0) && (radius.TopLeftY.Type == Auto || radius.TopLeftY.Value == 0) && @@ -471,23 +471,23 @@ func (radius BoxRadius) cssValue(builder cssBuilder) { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - buffer.WriteString(radius.TopLeftX.cssString("0")) + buffer.WriteString(radius.TopLeftX.cssString("0", session)) if radius.AllAnglesIsEqual() { if !radius.TopLeftX.Equal(radius.TopLeftY) { buffer.WriteString(" / ") - buffer.WriteString(radius.TopLeftY.cssString("0")) + buffer.WriteString(radius.TopLeftY.cssString("0", session)) } } else { buffer.WriteRune(' ') - buffer.WriteString(radius.TopRightX.cssString("0")) + buffer.WriteString(radius.TopRightX.cssString("0", session)) buffer.WriteRune(' ') - buffer.WriteString(radius.BottomRightX.cssString("0")) + buffer.WriteString(radius.BottomRightX.cssString("0", session)) buffer.WriteRune(' ') - buffer.WriteString(radius.BottomLeftX.cssString("0")) + buffer.WriteString(radius.BottomLeftX.cssString("0", session)) if !radius.TopLeftX.Equal(radius.TopLeftY) || !radius.TopRightX.Equal(radius.TopRightY) || @@ -495,22 +495,22 @@ func (radius BoxRadius) cssValue(builder cssBuilder) { !radius.BottomRightX.Equal(radius.BottomRightY) { buffer.WriteString(" / ") - buffer.WriteString(radius.TopLeftY.cssString("0")) + buffer.WriteString(radius.TopLeftY.cssString("0", session)) buffer.WriteRune(' ') - buffer.WriteString(radius.TopRightY.cssString("0")) + buffer.WriteString(radius.TopRightY.cssString("0", session)) buffer.WriteRune(' ') - buffer.WriteString(radius.BottomRightY.cssString("0")) + buffer.WriteString(radius.BottomRightY.cssString("0", session)) buffer.WriteRune(' ') - buffer.WriteString(radius.BottomLeftY.cssString("0")) + buffer.WriteString(radius.BottomLeftY.cssString("0", session)) } } builder.add("border-radius", buffer.String()) } -func (radius BoxRadius) cssString() string { +func (radius BoxRadius) cssString(session Session) string { var builder cssValueBuilder - radius.cssValue(&builder) + radius.cssValue(&builder, session) return builder.finish() } @@ -570,7 +570,7 @@ func getRadiusProperty(style Properties) RadiusProperty { return NewRadiusProperty(nil) } -func (properties *propertyList) setRadius(value interface{}) bool { +func (properties *propertyList) setRadius(value any) bool { if value == nil { delete(properties.properties, Radius) @@ -645,7 +645,16 @@ func (properties *propertyList) setRadius(value interface{}) bool { properties.properties[Radius] = radius return true + case float32: + return properties.setRadius(Px(float64(value))) + + case float64: + return properties.setRadius(Px(value)) + default: + if n, ok := isInt(value); ok { + return properties.setRadius(Px(float64(n))) + } notCompatibleType(Radius, value) } @@ -664,7 +673,7 @@ func (properties *propertyList) removeRadiusElement(tag string) { } } -func (properties *propertyList) setRadiusElement(tag string, value interface{}) bool { +func (properties *propertyList) setRadiusElement(tag string, value any) bool { if value == nil { properties.removeRadiusElement(tag) return true @@ -679,7 +688,7 @@ func (properties *propertyList) setRadiusElement(tag string, value interface{}) return false } -func getRadiusElement(style Properties, tag string) interface{} { +func getRadiusElement(style Properties, tag string) any { value := style.Get(Radius) if value != nil { switch value := value.(type) { diff --git a/resizable.go b/resizable.go index d4cb8df..4a65741 100644 --- a/resizable.go +++ b/resizable.go @@ -45,7 +45,7 @@ type resizableData struct { // NewResizable create new Resizable object and return it func NewResizable(session Session, params Params) Resizable { view := new(resizableData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -54,8 +54,8 @@ func newResizable(session Session) View { return NewResizable(session, nil) } -func (resizable *resizableData) Init(session Session) { - resizable.viewData.Init(session) +func (resizable *resizableData) init(session Session) { + resizable.viewData.init(session) resizable.tag = "Resizable" resizable.systemClass = "ruiGridLayout" resizable.content = []View{} @@ -108,11 +108,11 @@ func (resizable *resizableData) remove(tag string) { } } -func (resizable *resizableData) Set(tag string, value interface{}) bool { +func (resizable *resizableData) Set(tag string, value any) bool { return resizable.set(strings.ToLower(tag), value) } -func (resizable *resizableData) set(tag string, value interface{}) bool { +func (resizable *resizableData) set(tag string, value any) bool { if value == nil { resizable.remove(tag) return true @@ -183,7 +183,7 @@ func (resizable *resizableData) set(tag string, value interface{}) bool { return resizable.viewData.set(tag, value) } -func (resizable *resizableData) Get(tag string) interface{} { +func (resizable *resizableData) Get(tag string) any { return resizable.get(strings.ToLower(tag)) } @@ -235,7 +235,7 @@ func (resizable *resizableData) getSide() int { return AllSides } -func (resizable *resizableData) setSide(value interface{}) bool { +func (resizable *resizableData) setSide(value any) bool { switch value := value.(type) { case string: if n, err := strconv.Atoi(value); err == nil { @@ -340,7 +340,7 @@ func (resizable *resizableData) updateResizeBorderWidth() { } func (resizable *resizableData) cellSizeCSS() (string, string) { - w := resizable.resizeBorderWidth().cssString("4px") + w := resizable.resizeBorderWidth().cssString("4px", resizable.Session()) side := resizable.getSide() column := "1fr" row := "1fr" @@ -384,7 +384,7 @@ func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder) top := 1 leftSide := (side & LeftSide) != 0 rightSide := (side & RightSide) != 0 - w := resizable.resizeBorderWidth().cssString("4px") + w := resizable.resizeBorderWidth().cssString("4px", resizable.Session()) if leftSide { left = 2 diff --git a/resizeEvent.go b/resizeEvent.go index bee0466..ee53b62 100644 --- a/resizeEvent.go +++ b/resizeEvent.go @@ -3,9 +3,12 @@ package rui // ResizeEvent is the constant for "resize-event" property tag. // The "resize-event" is fired when the view changes its size. // The main listener format: -// func(View, Frame). +// +// func(View, Frame). +// // The additional listener formats: -// func(Frame), func(View), and func(). +// +// func(Frame), func(View), and func(). const ResizeEvent = "resize-event" func (view *viewData) onResize(self View, x, y, width, height float64) { @@ -13,7 +16,7 @@ func (view *viewData) onResize(self View, x, y, width, height float64) { view.frame.Top = y view.frame.Width = width view.frame.Height = height - for _, listener := range GetResizeListeners(view, "") { + for _, listener := range GetResizeListeners(view) { listener(self, view.frame) } } @@ -21,147 +24,19 @@ 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) setFrameListener(tag string, value interface{}) bool { - if value == nil { - delete(view.properties, tag) - return true - } - - switch value := value.(type) { - case func(View, Frame): - view.properties[tag] = []func(View, Frame){value} - - case []func(View, Frame): - if len(value) > 0 { - view.properties[tag] = value - } else { - delete(view.properties, tag) - return true - } - - case func(Frame): - fn := func(_ View, frame Frame) { - value(frame) - } - view.properties[tag] = []func(View, Frame){fn} - - case []func(Frame): - count := len(value) - if count == 0 { - delete(view.properties, tag) - return true - } - - listeners := make([]func(View, Frame), count) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(_ View, frame Frame) { - val(frame) - } - } - view.properties[tag] = listeners - - case func(View): - fn := func(view View, _ Frame) { - value(view) - } - view.properties[tag] = []func(View, Frame){fn} - - case []func(View): - count := len(value) - if count == 0 { - delete(view.properties, tag) - return true - } - - listeners := make([]func(View, Frame), count) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(view View, _ Frame) { - val(view) - } - } - view.properties[tag] = listeners - - case func(): - fn := func(View, Frame) { - value() - } - view.properties[tag] = []func(View, Frame){fn} - - case []func(): - count := len(value) - if count == 0 { - delete(view.properties, tag) - return true - } - - listeners := make([]func(View, Frame), count) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(View, Frame) { - val() - } - } - view.properties[tag] = listeners - - case []interface{}: - count := len(value) - if count == 0 { - delete(view.properties, tag) - return true - } - - listeners := make([]func(View, Frame), count) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - switch val := val.(type) { - case func(View, Frame): - listeners[i] = val - - case func(Frame): - listeners[i] = func(_ View, frame Frame) { - val(frame) - } - - case func(View): - listeners[i] = func(view View, _ Frame) { - val(view) - } - - case func(): - listeners[i] = func(View, Frame) { - val() - } - - default: - notCompatibleType(tag, val) - return false - } - } - view.properties[tag] = listeners - - default: +func (view *viewData) setFrameListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, Frame](value) + if !ok { notCompatibleType(tag, value) return false } + if listeners == nil { + delete(view.properties, tag) + } else { + view.properties[tag] = listeners + } + view.propertyChangedEvent(tag) return true } @@ -190,10 +65,10 @@ func (view *viewData) Frame() Frame { } // GetViewFrame returns the size and location of view's viewport. -// If the second argument (subviewID) is "" then the value of the first argument (view) is returned -func GetViewFrame(view View, subviewID string) Frame { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return Frame{} @@ -202,17 +77,7 @@ func GetViewFrame(view View, subviewID string) Frame { } // GetResizeListeners returns the list of "resize-event" listeners. If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then the listeners list of the first argument (view) is returned -func GetResizeListeners(view View, subviewID string) []func(View, Frame) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ResizeEvent); value != nil { - if result, ok := value.([]func(View, Frame)); ok { - return result - } - } - } - return []func(View, Frame){} +// If the second argument (subviewID) is not specified or it is "" then the listeners list of the first argument (view) is returned +func GetResizeListeners(view View, subviewID ...string) []func(View, Frame) { + return getEventListeners[View, Frame](view, subviewID, ResizeEvent) } diff --git a/resources.go b/resources.go index 6f1761a..16ec8f1 100644 --- a/resources.go +++ b/resources.go @@ -3,7 +3,6 @@ package rui import ( "embed" "io" - "io/ioutil" "net/http" "os" "path/filepath" @@ -171,7 +170,7 @@ func registerImage(fs *embed.FS, path, filename string) { } func scanImagesDirectory(path, filePrefix string) { - if files, err := ioutil.ReadDir(path); err == nil { + if files, err := os.ReadDir(path); err == nil { for _, file := range files { filename := file.Name() if filename[0] != '.' { @@ -189,7 +188,7 @@ func scanImagesDirectory(path, filePrefix string) { } func scanThemesDir(path string) { - if files, err := ioutil.ReadDir(path); err == nil { + if files, err := os.ReadDir(path); err == nil { for _, file := range files { filename := file.Name() if filename[0] != '.' { @@ -197,7 +196,7 @@ func scanThemesDir(path string) { if file.IsDir() { scanThemesDir(newPath) } else if strings.ToLower(filepath.Ext(newPath)) == ".rui" { - if data, err := ioutil.ReadFile(newPath); err == nil { + if data, err := os.ReadFile(newPath); err == nil { registerThemeText(string(data)) } else { ErrorLog(err.Error()) @@ -380,7 +379,7 @@ func AllRawResources() []string { } if resources.path != "" { - if files, err := ioutil.ReadDir(resources.path + rawDir); err == nil { + if files, err := os.ReadDir(resources.path + rawDir); err == nil { for _, file := range files { result = append(result, file.Name()) } @@ -388,7 +387,7 @@ func AllRawResources() []string { } if exe, err := os.Executable(); err == nil { - if files, err := ioutil.ReadDir(filepath.Dir(exe) + "/resources/" + rawDir); err == nil { + if files, err := os.ReadDir(filepath.Dir(exe) + "/resources/" + rawDir); err == nil { for _, file := range files { result = append(result, file.Name()) } diff --git a/ruiWriter.go b/ruiWriter.go index bc5471e..7fd47b5 100644 --- a/ruiWriter.go +++ b/ruiWriter.go @@ -13,7 +13,7 @@ type ruiWriter interface { endObject() startArrayProperty(tag string) endObArray() - writeProperty(tag string, value interface{}) + writeProperty(tag string, value any) finish() string } @@ -112,7 +112,7 @@ func (writer *ruiWriterData) endObArray() { writer.buffer.WriteString("],\n") } -func (writer *ruiWriterData) writeValue(value interface{}) { +func (writer *ruiWriterData) writeValue(value any) { switch value := value.(type) { case string: @@ -175,7 +175,7 @@ func (writer *ruiWriterData) writeValue(value interface{}) { writer.buffer.WriteRune(']') } - case []interface{}: + case []any: switch len(value) { case 0: writer.buffer.WriteString("[]\n") @@ -205,7 +205,7 @@ func (writer *ruiWriterData) writeValue(value interface{}) { writer.buffer.WriteString(",\n") } -func (writer *ruiWriterData) writeProperty(tag string, value interface{}) { +func (writer *ruiWriterData) writeProperty(tag string, value any) { writer.writeIndent() writer.writeString(tag) writer.buffer.WriteString(" = ") diff --git a/scrollEvent.go b/scrollEvent.go index 134bdea..7afa29f 100644 --- a/scrollEvent.go +++ b/scrollEvent.go @@ -3,11 +3,14 @@ package rui import "fmt" // ScrollEvent is the constant for "scroll-event" property tag. -// The "resize-event" is fired when the content of the view is scrolled. +// The "scroll-event" is fired when the content of the view is scrolled. // The main listener format: -// func(View, Frame). +// +// func(View, Frame). +// // The additional listener formats: -// func(Frame), func(View), and func(). +// +// func(Frame), func(View), and func(). const ScrollEvent = "scroll-event" func (view *viewData) onScroll(self View, x, y, width, height float64) { @@ -15,7 +18,7 @@ func (view *viewData) onScroll(self View, x, y, width, height float64) { view.scroll.Top = y view.scroll.Width = width view.scroll.Height = height - for _, listener := range GetScrollListeners(view, "") { + for _, listener := range GetScrollListeners(view) { listener(self, view.scroll) } } @@ -32,10 +35,10 @@ func (view *viewData) setScroll(x, y, width, height float64) { } // GetViewScroll returns ... -// If the second argument (subviewID) is "" then a value of the first argument (view) is returned -func GetViewScroll(view View, subviewID string) Frame { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return Frame{} @@ -44,19 +47,9 @@ func GetViewScroll(view View, subviewID string) Frame { } // GetScrollListeners returns the list of "scroll-event" listeners. If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then the listeners list of the first argument (view) is returned -func GetScrollListeners(view View, subviewID string) []func(View, Frame) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(ScrollEvent); value != nil { - if result, ok := value.([]func(View, Frame)); ok { - return result - } - } - } - return []func(View, Frame){} +// If the second argument (subviewID) is not specified or it is "" then the listeners list of the first argument (view) is returned +func GetScrollListeners(view View, subviewID ...string) []func(View, Frame) { + return getEventListeners[View, Frame](view, subviewID, ResizeEvent) } // ScrollTo scrolls the view's content to the given position. @@ -71,10 +64,10 @@ func ScrollViewTo(view View, subviewID string, x, y float64) { } // ScrollViewToEnd scrolls the view's content to the start of view. -// If the second argument (subviewID) is "" then the first argument (view) is used -func ScrollViewToStart(view View, subviewID string) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used +func ScrollViewToStart(view View, subviewID ...string) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { view.Session().runScript(`scrollToStart("` + view.htmlID() + `")`) @@ -82,10 +75,10 @@ func ScrollViewToStart(view View, subviewID string) { } // ScrollViewToEnd scrolls the view's content to the end of view. -// If the second argument (subviewID) is "" then the first argument (view) is used -func ScrollViewToEnd(view View, subviewID string) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then the first argument (view) is used +func ScrollViewToEnd(view View, subviewID ...string) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { view.Session().runScript(`scrollToEnd("` + view.htmlID() + `")`) diff --git a/session.go b/session.go index 21257b8..38fe98f 100644 --- a/session.go +++ b/session.go @@ -60,11 +60,11 @@ type Session interface { RootView() View // 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. - Get(viewID, tag string) interface{} + Get(viewID, tag string) any // 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 // a description of the error is written to the log - Set(viewID, tag string, value interface{}) bool + Set(viewID, tag string, value any) bool // DownloadFile downloads (saves) on the client side the file located at the specified path on the server. DownloadFile(path string) @@ -83,7 +83,7 @@ type Session interface { viewByHTMLID(id string) View nextViewID() string - styleProperty(styleTag, property string) interface{} + styleProperty(styleTag, property string) any setBrige(events chan DataObject, brige WebBrige) writeInitScript(writer *strings.Builder) @@ -222,7 +222,7 @@ func (session *sessionData) close() { } } -func (session *sessionData) styleProperty(styleTag, propertyTag string) interface{} { +func (session *sessionData) styleProperty(styleTag, propertyTag string) any { if style := session.getCurrentTheme().style(styleTag); style != nil { return style.getRaw(propertyTag) } @@ -312,14 +312,14 @@ func (session *sessionData) setIgnoreViewUpdates(ignore bool) { session.ignoreUpdates = ignore } -func (session *sessionData) Get(viewID, tag string) interface{} { +func (session *sessionData) Get(viewID, tag string) any { if view := ViewByID(session.RootView(), viewID); view != nil { return view.Get(tag) } return nil } -func (session *sessionData) Set(viewID, tag string, value interface{}) bool { +func (session *sessionData) Set(viewID, tag string, value any) bool { if view := ViewByID(session.RootView(), viewID); view != nil { return view.Set(tag, value) } @@ -434,7 +434,7 @@ func (session *sessionData) handleViewEvent(command string, data DataObject) { if view := session.viewByHTMLID(viewID); view != nil { view.handleCommand(view, command, data) } - } else { + } else if command != "clickOutsidePopup" { ErrorLog(`"id" property not found. Event: ` + command) } } diff --git a/session_test.go b/session_test.go index d79469c..314ef85 100644 --- a/session_test.go +++ b/session_test.go @@ -4,8 +4,8 @@ import ( "testing" ) -var stopTestLogFlag = false -var testLogDone chan int +// var stopTestLogFlag = false +// var testLogDone chan int var ignoreTestLog = false func createTestLog(t *testing.T, ignore bool) { diff --git a/shadow.go b/shadow.go index 216f460..3d0541a 100644 --- a/shadow.go +++ b/shadow.go @@ -111,7 +111,7 @@ func (shadow *viewShadowData) Remove(tag string) { delete(shadow.properties, strings.ToLower(tag)) } -func (shadow *viewShadowData) Set(tag string, value interface{}) bool { +func (shadow *viewShadowData) Set(tag string, value any) bool { if value == nil { shadow.Remove(tag) return true @@ -127,7 +127,7 @@ func (shadow *viewShadowData) Set(tag string, value interface{}) bool { return false } -func (shadow *viewShadowData) Get(tag string) interface{} { +func (shadow *viewShadowData) Get(tag string) any { return shadow.propertyList.Get(strings.ToLower(tag)) } @@ -151,13 +151,13 @@ func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session, buffer.WriteString("inset ") } - buffer.WriteString(offsetX.cssString("0")) + buffer.WriteString(offsetX.cssString("0", session)) buffer.WriteByte(' ') - buffer.WriteString(offsetY.cssString("0")) + buffer.WriteString(offsetY.cssString("0", session)) buffer.WriteByte(' ') - buffer.WriteString(blurRadius.cssString("0")) + buffer.WriteString(blurRadius.cssString("0", session)) buffer.WriteByte(' ') - buffer.WriteString(spreadRadius.cssString("0")) + buffer.WriteString(spreadRadius.cssString("0", session)) buffer.WriteByte(' ') buffer.WriteString(color.cssString()) return true @@ -177,11 +177,11 @@ func (shadow *viewShadowData) cssTextStyle(buffer *strings.Builder, session Sess } buffer.WriteString(lead) - buffer.WriteString(offsetX.cssString("0")) + buffer.WriteString(offsetX.cssString("0", session)) buffer.WriteByte(' ') - buffer.WriteString(offsetY.cssString("0")) + buffer.WriteString(offsetY.cssString("0", session)) buffer.WriteByte(' ') - buffer.WriteString(blurRadius.cssString("0")) + buffer.WriteString(blurRadius.cssString("0", session)) buffer.WriteByte(' ') buffer.WriteString(color.cssString()) return true @@ -225,7 +225,7 @@ func (shadow *viewShadowData) writeString(buffer *strings.Builder, indent string buffer.WriteString(" }") } -func (properties *propertyList) setShadow(tag string, value interface{}) bool { +func (properties *propertyList) setShadow(tag string, value any) bool { if value == nil { delete(properties.properties, tag) diff --git a/sizeFunc.go b/sizeFunc.go new file mode 100644 index 0000000..d56b101 --- /dev/null +++ b/sizeFunc.go @@ -0,0 +1,373 @@ +package rui + +import ( + "fmt" + "strconv" + "strings" +) + +// SizeFunc describes a function that calculates the SizeUnit size. +// Used as the value of the SizeUnit properties. +// "min", "max", "clamp", "sum", "sub", "mul", and "div" functions are available. +type SizeFunc interface { + fmt.Stringer + // Name() returns the function name: "min", "max", "clamp", "sum", "sub", "mul", or "div" + Name() string + // Args() returns a list of function arguments + Args() []any + cssString(session Session) string + writeCSS(topFunc string, buffer *strings.Builder, session Session) + writeString(topFunc string, buffer *strings.Builder) +} + +type sizeFuncData struct { + tag string + args []any +} + +func parseSizeFunc(text string) SizeFunc { + text = strings.Trim(text, " ") + + for _, tag := range []string{"min", "max", "sum", "sub", "mul", "div", "clamp"} { + if strings.HasPrefix(text, tag) { + text = strings.Trim(strings.TrimPrefix(text, tag), " ") + last := len(text) - 1 + if text[0] == '(' && text[last] == ')' { + text = text[1:last] + bracket := 0 + start := 0 + args := []any{} + for i, ch := range text { + switch ch { + case ',': + if bracket == 0 { + args = append(args, text[start:i]) + start = i + 1 + } + + case '(': + bracket++ + + case ')': + bracket-- + } + } + if bracket != 0 { + ErrorLogF(`Invalid "%s" function`, tag) + return nil + } + + args = append(args, text[start:]) + switch tag { + case "sub", "mul", "div": + if len(args) != 2 { + ErrorLogF(`"%s" function needs 2 arguments`, tag) + return nil + } + case "clamp": + if len(args) != 3 { + ErrorLog(`"clamp" function needs 3 arguments`) + return nil + } + } + + data := new(sizeFuncData) + data.tag = tag + if data.parseArgs(args, tag == "mul" || tag == "div") { + return data + } + } + + ErrorLogF(`Invalid "%s" function`, tag) + return nil + } + } + + return nil +} + +func (data *sizeFuncData) parseArgs(args []any, allowNumber bool) bool { + data.args = []any{} + + numberArg := func(index int, value float64) bool { + if allowNumber { + if index == 1 { + if value == 0 && data.tag == "div" { + ErrorLog(`Division by 0 in div function`) + return false + } + data.args = append(data.args, value) + return true + } else { + ErrorLogF(`Only the second %s function argument can be a number`, data.tag) + return false + } + } + + ErrorLogF(`The %s function argument cann't be a number`, data.tag) + return false + } + + for i, arg := range args { + switch arg := arg.(type) { + case string: + if arg = strings.Trim(arg, " \t\n"); arg == "" { + ErrorLogF(`Unsupported %s function argument #%d: ""`, data.tag, i) + return false + } + + if arg[0] == '@' { + data.args = append(data.args, arg) + } else if val, err := strconv.ParseFloat(arg, 64); err == nil { + return numberArg(i, val) + } else if fn := parseSizeFunc(arg); fn != nil { + data.args = append(data.args, fn) + } else if size, err := stringToSizeUnit(arg); err == nil { + data.args = append(data.args, size) + } else { + ErrorLogF(`Unsupported %s function argument #%d: "%s"`, data.tag, i, arg) + return false + } + + case SizeFunc: + data.args = append(data.args, arg) + + case SizeUnit: + if arg.Type == Auto { + ErrorLogF(`Unsupported %s function argument #%d: "auto"`, data.tag, i) + } + data.args = append(data.args, arg) + + case float64: + return numberArg(i, arg) + + case float32: + return numberArg(i, float64(arg)) + + default: + if n, ok := isInt(arg); ok { + return numberArg(i, float64(n)) + } + ErrorLogF(`Unsupported %s function argument #%d: %v`, data.tag, i, arg) + return false + } + } + return true +} + +func (data *sizeFuncData) String() string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + data.writeString("", buffer) + return buffer.String() +} + +func (data *sizeFuncData) Name() string { + return data.tag +} + +func (data *sizeFuncData) Args() []any { + args := make([]any, len(data.args)) + copy(args, data.args) + return args +} + +func (data *sizeFuncData) writeString(topFunc string, buffer *strings.Builder) { + buffer.WriteString(data.tag) + buffer.WriteRune('(') + for i, arg := range data.args { + if i > 0 { + buffer.WriteString(", ") + } + switch arg := arg.(type) { + case string: + buffer.WriteString(arg) + + case SizeFunc: + arg.writeString(data.tag, buffer) + + case SizeUnit: + buffer.WriteString(arg.String()) + + case fmt.Stringer: + buffer.WriteString(arg.String()) + + case float64: + buffer.WriteString(fmt.Sprintf("%g", arg)) + } + + } + buffer.WriteRune(')') +} + +func (data *sizeFuncData) cssString(session Session) string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + data.writeCSS("", buffer, session) + return buffer.String() +} + +func (data *sizeFuncData) writeCSS(topFunc string, buffer *strings.Builder, session Session) { + bracket := true + sep := ", " + + mathFunc := func(s string) { + sep = s + switch topFunc { + case "": + buffer.WriteString("calc(") + + case "min", "max", "clamp": + bracket = false + + default: + buffer.WriteRune('(') + } + } + + switch data.tag { + case "min", "max", "clamp": + buffer.WriteString(data.tag) + buffer.WriteRune('(') + + case "sum": + mathFunc(" + ") + + case "sub": + mathFunc(" - ") + + case "mul": + mathFunc(" * ") + + case "div": + mathFunc(" / ") + + default: + return + } + + for i, arg := range data.args { + if i > 0 { + buffer.WriteString(sep) + } + switch arg := arg.(type) { + case string: + if arg, ok := session.resolveConstants(arg); ok { + if fn := parseSizeFunc(arg); fn != nil { + fn.writeCSS(data.tag, buffer, session) + } else if size, err := stringToSizeUnit(arg); err == nil { + buffer.WriteString(size.cssString("0", session)) + } else { + buffer.WriteString("0") + } + } else { + buffer.WriteString("0") + } + + case SizeFunc: + arg.writeCSS(data.tag, buffer, session) + + case SizeUnit: + buffer.WriteString(arg.cssString("0", session)) + + case fmt.Stringer: + buffer.WriteString(arg.String()) + + case float64: + buffer.WriteString(fmt.Sprintf("%g", arg)) + } + + } + + if bracket { + buffer.WriteRune(')') + } +} + +// MaxSize creates a SizeUnit function that calculates the maximum argument. +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc +func MaxSize(arg0, arg1 any, args ...any) SizeFunc { + data := new(sizeFuncData) + data.tag = "max" + if !data.parseArgs(append([]any{arg0, arg1}, args...), false) { + return nil + } + return data +} + +// MinSize creates a SizeUnit function that calculates the minimum argument. +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +func MinSize(arg0, arg1 any, args ...any) SizeFunc { + data := new(sizeFuncData) + data.tag = "min" + if !data.parseArgs(append([]any{arg0, arg1}, args...), false) { + return nil + } + return data +} + +// SumSize creates a SizeUnit function that calculates the sum of arguments. +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +func SumSize(arg0, arg1 any, args ...any) SizeFunc { + data := new(sizeFuncData) + data.tag = "sum" + if !data.parseArgs(append([]any{arg0, arg1}, args...), false) { + return nil + } + return data +} + +// SumSize creates a SizeUnit function that calculates the result of subtracting the arguments (arg1 - arg2). +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +func SubSize(arg0, arg1 any) SizeFunc { + data := new(sizeFuncData) + data.tag = "sub" + if !data.parseArgs([]any{arg0, arg1}, false) { + return nil + } + return data +} + +// MulSize creates a SizeUnit function that calculates the result of multiplying the arguments (arg1 * arg2). +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +// The second argument can also be a number (float32, float32, int, int8...int64, uint, uint8...unit64) +// or a string which is a text representation of a number. +func MulSize(arg0, arg1 any) SizeFunc { + data := new(sizeFuncData) + data.tag = "mul" + if !data.parseArgs([]any{arg0, arg1}, true) { + return nil + } + return data +} + +// DivSize creates a SizeUnit function that calculates the result of dividing the arguments (arg1 / arg2). +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +// The second argument can also be a number (float32, float32, int, int8...int64, uint, uint8...unit64) +// or a string which is a text representation of a number. +func DivSize(arg0, arg1 any) SizeFunc { + data := new(sizeFuncData) + data.tag = "div" + if !data.parseArgs([]any{arg0, arg1}, true) { + return nil + } + return data +} + +// ClampSize creates a SizeUnit function whose the result is calculated as follows: +// +// min ≤ value ≤ max -> value; +// value < min -> min; +// max < value -> max; +// +// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc. +func ClampSize(min, value, max any) SizeFunc { + data := new(sizeFuncData) + data.tag = "clamp" + if !data.parseArgs([]any{min, value, max}, false) { + return nil + } + return data +} diff --git a/sizeFunc_test.go b/sizeFunc_test.go new file mode 100644 index 0000000..3b508a6 --- /dev/null +++ b/sizeFunc_test.go @@ -0,0 +1,50 @@ +package rui + +import ( + "testing" +) + +func TestSizeFunc(t *testing.T) { + session := new(sessionData) + session.getCurrentTheme().SetConstant("a1", "120px", "120px") + + SetErrorLog(func(text string) { + t.Error(text) + }) + SetDebugLog(func(text string) { + t.Log(text) + }) + + testFunc := func(fn SizeFunc, str, css string) { + if fn != nil { + if text := fn.String(); str != text { + t.Error("String() error.\nResult: \"" + text + "\"\nExpected: \"" + str + `"`) + } + if text := fn.cssString(session); css != text { + t.Error("cssString() error.\nResult: \"" + text + "\"\nExpected: \"" + css + `"`) + } + } + } + + testFunc(MinSize("100%", Px(10)), `min(100%, 10px)`, `min(100%, 10px)`) + testFunc(MaxSize(Percent(100), "@a1"), `max(100%, @a1)`, `max(100%, 120px)`) + testFunc(SumSize(Percent(100), "@a1"), `sum(100%, @a1)`, `calc(100% + 120px)`) + testFunc(SubSize(Percent(100), "@a1"), `sub(100%, @a1)`, `calc(100% - 120px)`) + testFunc(MulSize(Percent(100), "@a1"), `mul(100%, @a1)`, `calc(100% * 120px)`) + testFunc(DivSize(Percent(100), "@a1"), `div(100%, @a1)`, `calc(100% / 120px)`) + testFunc(ClampSize(Percent(20), "@a1", Percent(40)), `clamp(20%, @a1, 40%)`, `clamp(20%, 120px, 40%)`) + + testFunc(MaxSize(SubSize(Percent(100), "@a1"), "@a1"), `max(sub(100%, @a1), @a1)`, `max(100% - 120px, 120px)`) + + testParse := func(str, css string) { + if fn := parseSizeFunc(str); fn != nil { + testFunc(fn, str, css) + } + } + + testParse(`min(100%, 10px)`, `min(100%, 10px)`) + testParse(`max(100%, @a1)`, `max(100%, 120px)`) + testParse(`max(sub(100%, @a1), @a1)`, `max(100% - 120px, 120px)`) + testParse(`mul(sub(100%, @a1), @a1)`, `calc((100% - 120px) * 120px)`) + testParse(`mul(sub(100%, @a1), div(mul(@a1, 3), 2))`, `calc((100% - 120px) * ((120px * 3) / 2))`) +} diff --git a/sizeUnit.go b/sizeUnit.go index 07b7973..3106b63 100644 --- a/sizeUnit.go +++ b/sizeUnit.go @@ -14,89 +14,94 @@ import ( type SizeUnitType uint8 const ( - // Auto - default value. + // Auto is the SizeUnit type: default value. Auto SizeUnitType = 0 - // SizeInPixel - size in pixels. + // SizeInPixel is the SizeUnit type: the Value field specifies the size in pixels. SizeInPixel SizeUnitType = 1 - // SizeInEM - size in em. + // SizeInEM is the SizeUnit type: the Value field specifies the size in em. SizeInEM SizeUnitType = 2 - // SizeInEX - size in em. + // SizeInEX is the SizeUnit type: the Value field specifies the size in em. SizeInEX SizeUnitType = 3 - // SizeInPercent - size in percents of a parant size. + // SizeInPercent is the SizeUnit type: the Value field specifies the size in percents of the parent size. SizeInPercent SizeUnitType = 4 - // SizeInPt - size in pt (1/72 inch). + // SizeInPt is the SizeUnit type: the Value field specifies the size in pt (1/72 inch). SizeInPt SizeUnitType = 5 - // SizeInPc - size in pc (1pc = 12pt). + // SizeInPc is the SizeUnit type: the Value field specifies the size in pc (1pc = 12pt). SizeInPc SizeUnitType = 6 - // SizeInInch - size in inches. + // SizeInInch is the SizeUnit type: the Value field specifies the size in inches. SizeInInch SizeUnitType = 7 - // SizeInMM - size in millimeters. + // SizeInMM is the SizeUnit type: the Value field specifies the size in millimeters. SizeInMM SizeUnitType = 8 - // SizeInCM - size in centimeters. + // SizeInCM is the SizeUnit type: the Value field specifies the size in centimeters. SizeInCM SizeUnitType = 9 - // SizeInFraction - size in fraction. Used only for "cell-width" and "cell-height" property + // SizeInFraction is the SizeUnit type: the Value field specifies the size in fraction. + // Used only for "cell-width" and "cell-height" property. SizeInFraction SizeUnitType = 10 + // SizeFunction is the SizeUnit type: the Function field specifies the size function. + // "min", "max", "clamp", "sum", "sub", "mul", and "div" functions are available. + SizeFunction = 11 ) // SizeUnit describe a size (Value field) and size unit (Type field). type SizeUnit struct { - Type SizeUnitType - Value float64 + Type SizeUnitType + Value float64 + Function SizeFunc } // AutoSize creates SizeUnit with Auto type func AutoSize() SizeUnit { - return SizeUnit{Auto, 0} + return SizeUnit{Auto, 0, nil} } // Px creates SizeUnit with SizeInPixel type func Px(value float64) SizeUnit { - return SizeUnit{SizeInPixel, value} + return SizeUnit{SizeInPixel, value, nil} } // Em creates SizeUnit with SizeInEM type func Em(value float64) SizeUnit { - return SizeUnit{SizeInEM, value} + return SizeUnit{SizeInEM, value, nil} } // Ex creates SizeUnit with SizeInEX type func Ex(value float64) SizeUnit { - return SizeUnit{SizeInEX, value} + return SizeUnit{SizeInEX, value, nil} } // Percent creates SizeUnit with SizeInDIP type func Percent(value float64) SizeUnit { - return SizeUnit{SizeInPercent, value} + return SizeUnit{SizeInPercent, value, nil} } // Pt creates SizeUnit with SizeInPt type func Pt(value float64) SizeUnit { - return SizeUnit{SizeInPt, value} + return SizeUnit{SizeInPt, value, nil} } // Pc creates SizeUnit with SizeInPc type func Pc(value float64) SizeUnit { - return SizeUnit{SizeInPc, value} + return SizeUnit{SizeInPc, value, nil} } // Mm creates SizeUnit with SizeInMM type func Mm(value float64) SizeUnit { - return SizeUnit{SizeInMM, value} + return SizeUnit{SizeInMM, value, nil} } // Cm creates SizeUnit with SizeInCM type func Cm(value float64) SizeUnit { - return SizeUnit{SizeInCM, value} + return SizeUnit{SizeInCM, value, nil} } // Inch creates SizeUnit with SizeInInch type func Inch(value float64) SizeUnit { - return SizeUnit{SizeInInch, value} + return SizeUnit{SizeInInch, value, nil} } // Fr creates SizeUnit with SizeInFraction type func Fr(value float64) SizeUnit { - return SizeUnit{SizeInFraction, value} + return SizeUnit{SizeInFraction, value, nil} } // Equal compare two SizeUnit. Return true if SizeUnit are equal @@ -152,13 +157,24 @@ func stringToSizeUnit(value string) (SizeUnit, error) { } } + if val, err := strconv.ParseFloat(value, 64); err == nil { + return SizeUnit{Type: SizeInPixel, Value: val}, nil + } + return SizeUnit{Type: Auto, Value: 0}, errors.New(`Invalid SizeUnit value: "` + value + `"`) } // String - convert SizeUnit to string func (size SizeUnit) String() string { - if size.Type == Auto { + switch size.Type { + case Auto: return "auto" + + case SizeFunction: + if size.Function == nil { + return "auto" + } + return size.Function.String() } if suffix, ok := sizeUnitSuffixes()[size.Type]; ok { return fmt.Sprintf("%g%s", size.Value, suffix) @@ -167,13 +183,19 @@ func (size SizeUnit) String() string { } // cssString - convert SizeUnit to string -func (size SizeUnit) cssString(textForAuto string) string { +func (size SizeUnit) cssString(textForAuto string, session Session) string { switch size.Type { case Auto: return textForAuto case SizeInEM: return fmt.Sprintf("%grem", size.Value) + + case SizeFunction: + if size.Function == nil { + return textForAuto + } + return size.Function.cssString(session) } if size.Value == 0 { diff --git a/stackLayout.go b/stackLayout.go index 4ecb343..5f09800 100644 --- a/stackLayout.go +++ b/stackLayout.go @@ -41,7 +41,7 @@ type stackLayoutData struct { // NewStackLayout create new StackLayout object and return it func NewStackLayout(session Session, params Params) StackLayout { view := new(stackLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -51,8 +51,8 @@ func newStackLayout(session Session) View { } // Init initialize fields of ViewsContainer by default values -func (layout *stackLayoutData) Init(session Session) { - layout.viewsContainerData.Init(session) +func (layout *stackLayoutData) init(session Session) { + layout.viewsContainerData.init(session) layout.tag = "StackLayout" layout.systemClass = "ruiStackLayout" layout.properties[TransitionEndEvent] = []func(View, string){layout.pushFinished, layout.popFinished} @@ -97,11 +97,11 @@ func (layout *stackLayoutData) popFinished(view View, tag string) { } } -func (layout *stackLayoutData) Set(tag string, value interface{}) bool { +func (layout *stackLayoutData) Set(tag string, value any) bool { return layout.set(strings.ToLower(tag), value) } -func (layout *stackLayoutData) set(tag string, value interface{}) bool { +func (layout *stackLayoutData) set(tag string, value any) bool { if value == nil { layout.remove(tag) return true @@ -109,8 +109,8 @@ func (layout *stackLayoutData) set(tag string, value interface{}) bool { switch tag { case TransitionEndEvent: - listeners, ok := valueToAnimationListeners(value) - if ok { + listeners, ok := valueToEventListeners[View, string](value) + if ok && listeners != nil { listeners = append(listeners, layout.pushFinished) listeners = append(listeners, layout.popFinished) layout.properties[TransitionEndEvent] = listeners @@ -179,11 +179,11 @@ func (layout *stackLayoutData) remove(tag string) { } } -func (layout *stackLayoutData) Get(tag string) interface{} { +func (layout *stackLayoutData) Get(tag string) any { return layout.get(strings.ToLower(tag)) } -func (layout *stackLayoutData) get(tag string) interface{} { +func (layout *stackLayoutData) get(tag string) any { if tag == Current { return layout.peek } @@ -351,6 +351,8 @@ func (layout *stackLayoutData) Pop(animation int, onPopFinished func(View)) bool buffer.WriteString(htmlID) buffer.WriteString(`pop" class="ruiStackPageLayout" ontransitionend="stackTransitionEndEvent(\'`) buffer.WriteString(htmlID) + buffer.WriteString(`\', \'ruiPop\', event)" ontransitioncancel="stackTransitionEndEvent(\'`) + buffer.WriteString(htmlID) buffer.WriteString(`\', \'ruiPop\', event)" style="transition: transform 1s ease;">`) viewHTML(layout.popView, buffer) buffer.WriteString(`
`) diff --git a/strings.go b/strings.go index ad97646..46bf99b 100644 --- a/strings.go +++ b/strings.go @@ -2,7 +2,7 @@ package rui import ( "embed" - "io/ioutil" + "os" "path/filepath" "strings" ) @@ -28,7 +28,7 @@ func scanEmbedStringsDir(fs *embed.FS, dir string) { } func scanStringsDir(path string) { - if files, err := ioutil.ReadDir(path); err == nil { + if files, err := os.ReadDir(path); err == nil { for _, file := range files { filename := file.Name() if filename[0] != '.' { @@ -36,7 +36,7 @@ func scanStringsDir(path string) { if file.IsDir() { scanStringsDir(newPath) } else if strings.ToLower(filepath.Ext(newPath)) == ".rui" { - if data, err := ioutil.ReadFile(newPath); err == nil { + if data, err := os.ReadFile(newPath); err == nil { loadStringResources(string(data)) } else { ErrorLog(err.Error()) diff --git a/tableAdapter.go b/tableAdapter.go index 4704b20..c3a773f 100644 --- a/tableAdapter.go +++ b/tableAdapter.go @@ -18,7 +18,7 @@ type TableAdapter interface { // * rui.View // * fmt.Stringer // * rui.VerticalTableJoin, rui.HorizontalTableJoin - Cell(row, column int) interface{} + Cell(row, column int) any } // TableColumnStyle describes the style of TableView columns. @@ -59,15 +59,15 @@ type TableAllowRowSelection interface { } // SimpleTableAdapter is implementation of TableAdapter where the content -// defines as [][]interface{}. -// When you assign [][]interface{} value to the "content" property, it is converted to SimpleTableAdapter +// defines as [][]any. +// When you assign [][]any value to the "content" property, it is converted to SimpleTableAdapter type SimpleTableAdapter interface { TableAdapter TableCellStyle } type simpleTableAdapter struct { - content [][]interface{} + content [][]any columnCount int } @@ -94,7 +94,7 @@ type HorizontalTableJoin struct { } // NewSimpleTableAdapter creates the new SimpleTableAdapter -func NewSimpleTableAdapter(content [][]interface{}) SimpleTableAdapter { +func NewSimpleTableAdapter(content [][]any) SimpleTableAdapter { if content == nil { return nil } @@ -125,7 +125,7 @@ func (adapter *simpleTableAdapter) ColumnCount() int { return adapter.columnCount } -func (adapter *simpleTableAdapter) Cell(row, column int) interface{} { +func (adapter *simpleTableAdapter) Cell(row, column int) any { if adapter.content != nil && row >= 0 && row < len(adapter.content) && adapter.content[row] != nil && column >= 0 && column < len(adapter.content[row]) { return adapter.content[row][column] @@ -220,7 +220,7 @@ func (adapter *textTableAdapter) ColumnCount() int { return adapter.columnCount } -func (adapter *textTableAdapter) Cell(row, column int) interface{} { +func (adapter *textTableAdapter) Cell(row, column int) any { if adapter.content != nil && row >= 0 && row < len(adapter.content) && adapter.content[row] != nil && column >= 0 && column < len(adapter.content[row]) { return adapter.content[row][column] @@ -242,7 +242,7 @@ func (style *simpleTableRowStyle) RowStyle(row int) Params { return nil } -func (table *tableViewData) setRowStyle(value interface{}) bool { +func (table *tableViewData) setRowStyle(value any) bool { newSimpleTableRowStyle := func(params []Params) TableRowStyle { if len(params) == 0 { return nil @@ -319,7 +319,7 @@ func (style *simpleTableColumnStyle) ColumnStyle(row int) Params { return nil } -func (table *tableViewData) setColumnStyle(value interface{}) bool { +func (table *tableViewData) setColumnStyle(value any) bool { newSimpleTableColumnStyle := func(params []Params) TableColumnStyle { if len(params) == 0 { return nil diff --git a/tableView.go b/tableView.go index 0fabc97..8d68849 100644 --- a/tableView.go +++ b/tableView.go @@ -245,7 +245,7 @@ type tableCellView struct { // NewTableView create new TableView object and return it func NewTableView(session Session, params Params) TableView { view := new(tableViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -255,8 +255,8 @@ func newTableView(session Session) View { } // Init initialize fields of TableView by default values -func (table *tableViewData) Init(session Session) { - table.viewData.Init(session) +func (table *tableViewData) init(session Session) { + table.viewData.init(session) table.tag = "TableView" table.cellViews = []View{} table.cellFrame = []Frame{} @@ -290,10 +290,10 @@ func (table *tableViewData) normalizeTag(tag string) string { } func (table *tableViewData) Focusable() bool { - return GetTableSelectionMode(table, "") != NoneSelection + return GetTableSelectionMode(table) != NoneSelection } -func (table *tableViewData) Get(tag string) interface{} { +func (table *tableViewData) Get(tag string) any { return table.get(table.normalizeTag(tag)) } @@ -340,11 +340,11 @@ func (table *tableViewData) remove(tag string) { } } -func (table *tableViewData) Set(tag string, value interface{}) bool { +func (table *tableViewData) Set(tag string, value any) bool { return table.set(table.normalizeTag(tag), value) } -func (table *tableViewData) set(tag string, value interface{}) bool { +func (table *tableViewData) set(tag string, value any) bool { if value == nil { table.remove(tag) return true @@ -356,7 +356,7 @@ func (table *tableViewData) set(tag string, value interface{}) bool { case TableAdapter: table.properties[Content] = value - case [][]interface{}: + case [][]any: table.properties[Content] = NewSimpleTableAdapter(val) case [][]string: @@ -384,20 +384,24 @@ func (table *tableViewData) set(tag string, value interface{}) bool { table.cellSelectedListener = listeners case TableRowClickedEvent: - listeners := table.valueToRowListeners(value) - if listeners == nil { + listeners, ok := valueToEventListeners[TableView, int](value) + if !ok { notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(TableView, int){} } table.rowClickedListener = listeners case TableRowSelectedEvent: - listeners := table.valueToRowListeners(value) - if listeners == nil { + listeners, ok := valueToEventListeners[TableView, int](value) + if !ok { notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(TableView, int){} } - table.rowSelectedListener = []func(TableView, int){} + table.rowSelectedListener = listeners case CellStyle: if style, ok := value.(TableCellStyle); ok { @@ -450,21 +454,23 @@ func (table *tableViewData) set(tag string, value interface{}) bool { delete(table.properties, tag) } + case DataObject: + params := Params{} + for k := 0; k < value.PropertyCount(); k++ { + if prop := value.Property(k); prop != nil && prop.Type() == TextNode { + params[prop.Tag()] = prop.Text() + } + } + if len(params) > 0 { + table.properties[tag] = params + } else { + delete(table.properties, tag) + } + case DataNode: switch value.Type() { case ObjectNode: - obj := value.Object() - params := Params{} - for k := 0; k < obj.PropertyCount(); k++ { - if prop := obj.Property(k); prop != nil && prop.Type() == TextNode { - params[prop.Tag()] = prop.Text() - } - } - if len(params) > 0 { - table.properties[tag] = params - } else { - delete(table.properties, tag) - } + return table.set(tag, value.Object()) case TextNode: table.properties[tag] = value.Text() @@ -590,7 +596,7 @@ func (table *tableViewData) propertyChanged(tag string) { updateCSSProperty(htmlID, "border-spacing", "0", session) updateCSSProperty(htmlID, "border-collapse", "collapse", session) } else { - updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session) + updateCSSProperty(htmlID, "border-spacing", gap.cssString("0", session), session) updateCSSProperty(htmlID, "border-collapse", "separate", session) } @@ -598,7 +604,7 @@ func (table *tableViewData) propertyChanged(tag string) { htmlID := table.htmlID() session := table.Session() - switch GetTableSelectionMode(table, "") { + switch GetTableSelectionMode(table) { case CellSelection: updateProperty(htmlID, "tabindex", "0", session) updateProperty(htmlID, "onfocus", "tableViewFocusEvent(this, event)", session) @@ -676,7 +682,7 @@ func (table *tableViewData) currentInactiveStyle() string { return "ruiCurrentTableCell" } -func (table *tableViewData) valueToCellListeners(value interface{}) []func(TableView, int, int) { +func (table *tableViewData) valueToCellListeners(value any) []func(TableView, int, int) { if value == nil { return []func(TableView, int, int){} } @@ -706,7 +712,7 @@ func (table *tableViewData) valueToCellListeners(value interface{}) []func(Table } return listeners - case []interface{}: + case []any: listeners := make([]func(TableView, int, int), len(value)) for i, val := range value { if val == nil { @@ -731,61 +737,6 @@ func (table *tableViewData) valueToCellListeners(value interface{}) []func(Table return nil } -func (table *tableViewData) valueToRowListeners(value interface{}) []func(TableView, int) { - if value == nil { - return []func(TableView, int){} - } - - switch value := value.(type) { - case func(TableView, int): - return []func(TableView, int){value} - - case func(int): - fn := func(_ TableView, index int) { - value(index) - } - return []func(TableView, int){fn} - - case []func(TableView, int): - return value - - case []func(int): - listeners := make([]func(TableView, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - listeners[i] = func(_ TableView, index int) { - val(index) - } - } - return listeners - - case []interface{}: - listeners := make([]func(TableView, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - switch val := val.(type) { - case func(TableView, int): - listeners[i] = val - - case func(int): - listeners[i] = func(_ TableView, index int) { - val(index) - } - - default: - return nil - } - } - return listeners - } - - return nil -} - func (table *tableViewData) htmlTag() string { return "table" } @@ -808,7 +759,7 @@ func (table *tableViewData) htmlProperties(self View, buffer *strings.Builder) { buffer.WriteRune('"') } - if selectionMode := GetTableSelectionMode(table, ""); selectionMode != NoneSelection { + if selectionMode := GetTableSelectionMode(table); selectionMode != NoneSelection { buffer.WriteString(` onfocus="tableViewFocusEvent(this, event)" onblur="tableViewBlurEvent(this, event)" data-focusitemstyle="`) buffer.WriteString(table.currentStyle()) buffer.WriteString(`" data-bluritemstyle="`) @@ -879,10 +830,10 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { defer freeStringBuilder(cssBuilder.buffer) var view tableCellView - view.Init(session) + view.init(session) ignorCells := []struct{ row, column int }{} - selectionMode := GetTableSelectionMode(table, "") + selectionMode := GetTableSelectionMode(table) var allowCellSelection TableAllowCellSelection = nil if allow, ok := adapter.(TableAllowCellSelection); ok { @@ -905,7 +856,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { } vAlignCss := enumProperties[TableVerticalAlign].cssValues - vAlignValue := GetTableVerticalAlign(table, "") + vAlignValue := GetTableVerticalAlign(table) if vAlignValue < 0 || vAlignValue >= len(vAlignCss) { vAlignValue = 0 } @@ -1160,8 +1111,8 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) { buffer.WriteString("") } - headHeight := GetTableHeadHeight(table, "") - footHeight := GetTableFootHeight(table, "") + headHeight := GetTableHeadHeight(table) + footHeight := GetTableFootHeight(table) cellBorder := table.getCellBorder() cellPadding := table.boundsProperty(CellPadding) if cellPadding == nil { @@ -1370,24 +1321,26 @@ func (table *tableViewData) getCurrent() CellIndex { } func (table *tableViewData) cssStyle(self View, builder cssBuilder) { - table.viewData.cssViewStyle(builder, table.Session()) + session := table.Session() + table.viewData.cssViewStyle(builder, session) - gap, ok := sizeProperty(table, Gap, table.Session()) + gap, ok := sizeProperty(table, Gap, session) if !ok || gap.Type == Auto || gap.Value <= 0 { builder.add("border-spacing", "0") builder.add("border-collapse", "collapse") } else { - builder.add("border-spacing", gap.cssString("0")) + builder.add("border-spacing", gap.cssString("0", session)) builder.add("border-collapse", "separate") } } func (table *tableViewData) ReloadTableData() { + session := table.Session() if content := table.content(); content != nil { - updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), table.Session()) - updateProperty(table.htmlID(), "data-columns", strconv.Itoa(content.ColumnCount()), table.Session()) + updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), session) + updateProperty(table.htmlID(), "data-columns", strconv.Itoa(content.ColumnCount()), session) } - updateInnerHTML(table.htmlID(), table.Session()) + updateInnerHTML(table.htmlID(), session) } func (table *tableViewData) onItemResize(self View, index string, x, y, width, height float64) { diff --git a/tableViewUtils.go b/tableViewUtils.go index 73663fe..3c2e829 100644 --- a/tableViewUtils.go +++ b/tableViewUtils.go @@ -2,11 +2,11 @@ package rui import "strings" -func (cell *tableCellView) Set(tag string, value interface{}) bool { +func (cell *tableCellView) Set(tag string, value any) bool { return cell.set(strings.ToLower(tag), value) } -func (cell *tableCellView) set(tag string, value interface{}) bool { +func (cell *tableCellView) set(tag string, value any) bool { switch tag { case VerticalAlign: tag = TableVerticalAlign @@ -24,10 +24,10 @@ func (cell *tableCellView) cssStyle(self View, builder cssBuilder) { } // GetTableContent returns a TableAdapter which defines the TableView content. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableContent(view View, subviewID string) TableAdapter { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -40,10 +40,10 @@ func GetTableContent(view View, subviewID string) TableAdapter { } // GetTableRowStyle returns a TableRowStyle which defines styles of TableView rows. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableRowStyle(view View, subviewID string) TableRowStyle { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -56,10 +56,10 @@ func GetTableRowStyle(view View, subviewID string) TableRowStyle { } // GetTableColumnStyle returns a TableColumnStyle which defines styles of TableView columns. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableColumnStyle(view View, subviewID string) TableColumnStyle { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -72,10 +72,10 @@ func GetTableColumnStyle(view View, subviewID string) TableColumnStyle { } // GetTableCellStyle returns a TableCellStyle which defines styles of TableView cells. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableCellStyle(view View, subviewID string) TableCellStyle { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -89,72 +89,42 @@ func GetTableCellStyle(view View, subviewID string) TableCellStyle { // GetTableSelectionMode returns the mode of the TableView elements selection. // Valid values are NoneSelection (0), CellSelection (1), and RowSelection (2). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableSelectionMode(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, SelectionMode, NoneSelection); ok { - return result - } - } - return NoneSelection +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableSelectionMode(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, SelectionMode, NoneSelection, false) } // GetTableVerticalAlign returns a vertical align in a TavleView cell. Returns one of next values: // TopAlign (0), BottomAlign (1), CenterAlign (2), and BaselineAlign (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableVerticalAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, TableVerticalAlign, TopAlign); ok { - return result - } - } - return TopAlign +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableVerticalAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TableVerticalAlign, TopAlign, false) } // GetTableHeadHeight returns the number of rows in the table header. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableHeadHeight(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - headHeight, _ := intStyledProperty(view, HeadHeight, 0) - return headHeight - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableHeadHeight(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, HeadHeight, 0) } // GetTableFootHeight returns the number of rows in the table footer. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableFootHeight(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - headHeight, _ := intStyledProperty(view, FootHeight, 0) - return headHeight - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableFootHeight(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, FootHeight, 0) } // GetTableCurrent returns the row and column index of the TableView selected cell/row. // If there is no selected cell/row or the selection mode is NoneSelection (0), // then a value of the row and column index less than 0 is returned. // If the selection mode is RowSelection (2) then the returned column index is less than 0. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableCurrent(view View, subviewID string) CellIndex { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + 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 tableView.getCurrent() } @@ -165,10 +135,10 @@ func GetTableCurrent(view View, subviewID string) CellIndex { // GetTableCellClickedListeners returns listeners of event which occurs when the user clicks on a table cell. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableCellClickedListeners(view View, subviewID string) []func(TableView, int, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if value := view.Get(TableCellClickedEvent); value != nil { @@ -182,10 +152,10 @@ func GetTableCellClickedListeners(view View, subviewID string) []func(TableView, // GetTableCellSelectedListeners returns listeners of event which occurs when a table cell becomes selected. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableCellSelectedListeners(view View, subviewID string) []func(TableView, int, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if value := view.Get(TableCellSelectedEvent); value != nil { @@ -199,43 +169,23 @@ func GetTableCellSelectedListeners(view View, subviewID string) []func(TableView // GetTableRowClickedListeners returns listeners of event which occurs when the user clicks on a table row. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableRowClickedListeners(view View, subviewID string) []func(TableView, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(TableRowClickedEvent); value != nil { - if result, ok := value.([]func(TableView, int)); ok { - return result - } - } - } - return []func(TableView, int){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableRowClickedListeners(view View, subviewID ...string) []func(TableView, int) { + return getEventListeners[TableView, int](view, subviewID, TableRowClickedEvent) } // GetTableRowSelectedListeners returns listeners of event which occurs when a table row becomes selected. // If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTableRowSelectedListeners(view View, subviewID string) []func(TableView, int) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(TableRowSelectedEvent); value != nil { - if result, ok := value.([]func(TableView, int)); ok { - return result - } - } - } - return []func(TableView, int){} +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTableRowSelectedListeners(view View, subviewID ...string) []func(TableView, int) { + return getEventListeners[TableView, int](view, subviewID, TableRowSelectedEvent) } // ReloadTableViewData updates TableView -func ReloadTableViewData(view View, subviewID string) bool { +func ReloadTableViewData(view View, subviewID ...string) bool { var tableView TableView - if subviewID != "" { - if tableView = TableViewByID(view, subviewID); tableView == nil { + if len(subviewID) > 0 && subviewID[0] != "" { + if tableView = TableViewByID(view, subviewID[0]); tableView == nil { return false } } else { diff --git a/tabsLayout.go b/tabsLayout.go index 3291503..ca069a2 100644 --- a/tabsLayout.go +++ b/tabsLayout.go @@ -81,7 +81,7 @@ type tabsLayoutData struct { // NewTabsLayout create new TabsLayout object and return it func NewTabsLayout(session Session, params Params) TabsLayout { view := new(tabsLayoutData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -91,8 +91,8 @@ func newTabsLayout(session Session) View { } // Init initialize fields of ViewsContainer by default values -func (tabsLayout *tabsLayoutData) Init(session Session) { - tabsLayout.viewsContainerData.Init(session) +func (tabsLayout *tabsLayoutData) init(session Session) { + tabsLayout.viewsContainerData.init(session) tabsLayout.tag = "TabsLayout" tabsLayout.systemClass = "ruiTabsLayout" tabsLayout.tabListener = []func(TabsLayout, int, int){} @@ -108,11 +108,11 @@ func (tabsLayout *tabsLayoutData) currentItem(defaultValue int) int { return result } -func (tabsLayout *tabsLayoutData) Get(tag string) interface{} { +func (tabsLayout *tabsLayoutData) Get(tag string) any { return tabsLayout.get(strings.ToLower(tag)) } -func (tabsLayout *tabsLayoutData) get(tag string) interface{} { +func (tabsLayout *tabsLayoutData) get(tag string) any { switch tag { case CurrentTabChangedEvent: return tabsLayout.tabListener @@ -190,11 +190,11 @@ func (tabsLayout *tabsLayoutData) remove(tag string) { tabsLayout.propertyChangedEvent(tag) } -func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool { +func (tabsLayout *tabsLayoutData) Set(tag string, value any) bool { return tabsLayout.set(strings.ToLower(tag), value) } -func (tabsLayout *tabsLayoutData) set(tag string, value interface{}) bool { +func (tabsLayout *tabsLayoutData) set(tag string, value any) bool { if value == nil { tabsLayout.remove(tag) return true @@ -210,10 +210,12 @@ func (tabsLayout *tabsLayoutData) set(tag string, value interface{}) bool { tabsLayout.tabListener = listeners case TabCloseEvent: - listeners := tabsLayout.valueToCloseListeners(value) - if listeners == nil { + listeners, ok := valueToEventListeners[TabsLayout, int](value) + if !ok { notCompatibleType(tag, value) return false + } else if listeners == nil { + listeners = []func(TabsLayout, int){} } tabsLayout.tabCloseListener = listeners @@ -286,7 +288,7 @@ func (tabsLayout *tabsLayoutData) set(tag string, value interface{}) bool { return true } -func (tabsLayout *tabsLayoutData) valueToTabListeners(value interface{}) []func(TabsLayout, int, int) { +func (tabsLayout *tabsLayoutData) valueToTabListeners(value any) []func(TabsLayout, int, int) { if value == nil { return []func(TabsLayout, int, int){} } @@ -388,7 +390,7 @@ func (tabsLayout *tabsLayoutData) valueToTabListeners(value interface{}) []func( } return listeners - case []interface{}: + case []any: listeners := make([]func(TabsLayout, int, int), len(value)) for i, val := range value { if val == nil { @@ -433,61 +435,6 @@ func (tabsLayout *tabsLayoutData) valueToTabListeners(value interface{}) []func( return nil } -func (tabsLayout *tabsLayoutData) valueToCloseListeners(value interface{}) []func(TabsLayout, int) { - if value == nil { - return []func(TabsLayout, int){} - } - - switch value := value.(type) { - case func(TabsLayout, int): - return []func(TabsLayout, int){value} - - case func(int): - fn := func(_ TabsLayout, index int) { - value(index) - } - return []func(TabsLayout, int){fn} - - case []func(TabsLayout, int): - return value - - case []func(int): - listeners := make([]func(TabsLayout, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - listeners[i] = func(_ TabsLayout, index int) { - val(index) - } - } - return listeners - - case []interface{}: - listeners := make([]func(TabsLayout, int), len(value)) - for i, val := range value { - if val == nil { - return nil - } - switch val := val.(type) { - case func(TabsLayout, int): - listeners[i] = val - - case func(int): - listeners[i] = func(_ TabsLayout, index int) { - val(index) - } - - default: - return nil - } - } - return listeners - } - - return nil -} - func (tabsLayout *tabsLayoutData) tabsLocation() int { tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0) return tabs @@ -576,7 +523,7 @@ func (tabsLayout *tabsLayoutData) ListItem(index int, session Session) View { if !ok || title == "" { title = "No title" } - if !GetNotTranslate(tabsLayout, "") { + if !GetNotTranslate(tabsLayout) { title, _ = tabsLayout.Session().GetString(title) } @@ -776,7 +723,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde inactiveStyle := tabsLayout.inactiveTabStyle() activeStyle := tabsLayout.activeTabStyle() - notTranslate := GetNotTranslate(tabsLayout, "") + notTranslate := GetNotTranslate(tabsLayout) closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session) var tabStyle, titleDiv string diff --git a/textView.go b/textView.go index d885d2a..dac9f9c 100644 --- a/textView.go +++ b/textView.go @@ -17,7 +17,7 @@ type textViewData struct { // NewTextView create new TextView object and return it func NewTextView(session Session, params Params) TextView { view := new(textViewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -27,8 +27,8 @@ func newTextView(session Session) View { } // Init initialize fields of TextView by default values -func (textView *textViewData) Init(session Session) { - textView.viewData.Init(session) +func (textView *textViewData) init(session Session) { + textView.viewData.init(session) textView.tag = "TextView" } @@ -36,7 +36,7 @@ func (textView *textViewData) String() string { return getViewString(textView) } -func (textView *textViewData) Get(tag string) interface{} { +func (textView *textViewData) Get(tag string) any { return textView.get(strings.ToLower(tag)) } @@ -57,11 +57,11 @@ func (textView *textViewData) remove(tag string) { } } -func (textView *textViewData) Set(tag string, value interface{}) bool { +func (textView *textViewData) Set(tag string, value any) bool { return textView.set(strings.ToLower(tag), value) } -func (textView *textViewData) set(tag string, value interface{}) bool { +func (textView *textViewData) set(tag string, value any) bool { switch tag { case Text: switch value := value.(type) { @@ -155,7 +155,7 @@ func textToJS(text string) string { func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) { if value := textView.getRaw(Text); value != nil { if text, ok := value.(string); ok { - if !GetNotTranslate(textView, "") { + if !GetNotTranslate(textView) { text, _ = textView.session.GetString(text) } buffer.WriteString(textToJS(text)) @@ -165,14 +165,7 @@ func (textView *textViewData) htmlSubviews(self View, buffer *strings.Builder) { // GetTextOverflow returns a value of the "text-overflow" property: // TextOverflowClip (0) or TextOverflowEllipsis (1). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextOverflow(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return SingleLineText - } - t, _ := enumStyledProperty(view, TextOverflow, SingleLineText) - return t +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextOverflow(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TextOverflow, SingleLineText, false) } diff --git a/timePicker.go b/timePicker.go index ace0a60..9a603ba 100644 --- a/timePicker.go +++ b/timePicker.go @@ -29,7 +29,7 @@ type timePickerData struct { // NewTimePicker create new TimePicker object and return it func NewTimePicker(session Session, params Params) TimePicker { view := new(timePickerData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } @@ -38,8 +38,8 @@ func newTimePicker(session Session) View { return NewTimePicker(session, nil) } -func (picker *timePickerData) Init(session Session) { - picker.viewData.Init(session) +func (picker *timePickerData) init(session Session) { + picker.viewData.init(session) picker.tag = "TimePicker" picker.timeChangedListeners = []func(TimePicker, time.Time){} } @@ -96,7 +96,7 @@ func (picker *timePickerData) remove(tag string) { case TimePickerValue: if _, ok := picker.properties[TimePickerValue]; ok { delete(picker.properties, TimePickerValue) - time := GetTimePickerValue(picker, "") + time := GetTimePickerValue(picker) if picker.created { picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), time.Format(timeFormat))) } @@ -114,11 +114,11 @@ func (picker *timePickerData) remove(tag string) { picker.propertyChangedEvent(tag) } -func (picker *timePickerData) Set(tag string, value interface{}) bool { +func (picker *timePickerData) Set(tag string, value any) bool { return picker.set(picker.normalizeTag(tag), value) } -func (picker *timePickerData) set(tag string, value interface{}) bool { +func (picker *timePickerData) set(tag string, value any) bool { if value == nil { picker.remove(tag) return true @@ -192,9 +192,9 @@ func (picker *timePickerData) set(tag string, value interface{}) bool { } case TimePickerStep: - oldStep := GetTimePickerStep(picker, "") + oldStep := GetTimePickerStep(picker) if picker.setIntProperty(TimePickerStep, value) { - if step := GetTimePickerStep(picker, ""); oldStep != step { + if step := GetTimePickerStep(picker); oldStep != step { if picker.created { if step > 0 { updateProperty(picker.htmlID(), Step, strconv.Itoa(step), picker.session) @@ -208,7 +208,7 @@ func (picker *timePickerData) set(tag string, value interface{}) bool { } case TimePickerValue: - oldTime := GetTimePickerValue(picker, "") + oldTime := GetTimePickerValue(picker) if time, ok := setTimeValue(TimePickerValue); ok { if time != oldTime { if picker.created { @@ -223,57 +223,14 @@ func (picker *timePickerData) set(tag string, value interface{}) bool { } case TimeChangedEvent: - switch value := value.(type) { - case func(TimePicker, time.Time): - picker.timeChangedListeners = []func(TimePicker, time.Time){value} - - case func(time.Time): - fn := func(_ TimePicker, time time.Time) { - value(time) - } - picker.timeChangedListeners = []func(TimePicker, time.Time){fn} - - case []func(TimePicker, time.Time): - picker.timeChangedListeners = value - - case []func(time.Time): - listeners := make([]func(TimePicker, time.Time), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - listeners[i] = func(_ TimePicker, time time.Time) { - val(time) - } - } - picker.timeChangedListeners = listeners - - case []interface{}: - listeners := make([]func(TimePicker, time.Time), len(value)) - for i, val := range value { - if val == nil { - notCompatibleType(tag, val) - return false - } - - switch val := val.(type) { - case func(TimePicker, time.Time): - listeners[i] = val - - case func(time.Time): - listeners[i] = func(_ TimePicker, time time.Time) { - val(time) - } - - default: - notCompatibleType(tag, val) - return false - } - } - picker.timeChangedListeners = listeners + listeners, ok := valueToEventListeners[TimePicker, time.Time](value) + if !ok { + notCompatibleType(tag, value) + return false + } else if listeners == nil { + listeners = []func(TimePicker, time.Time){} } + picker.timeChangedListeners = listeners picker.propertyChangedEvent(tag) return true @@ -283,11 +240,11 @@ func (picker *timePickerData) set(tag string, value interface{}) bool { return false } -func (picker *timePickerData) Get(tag string) interface{} { +func (picker *timePickerData) Get(tag string) any { return picker.get(picker.normalizeTag(tag)) } -func (picker *timePickerData) get(tag string) interface{} { +func (picker *timePickerData) get(tag string) any { switch tag { case TimeChangedEvent: return picker.timeChangedListeners @@ -325,7 +282,7 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) } buffer.WriteString(` value="`) - buffer.WriteString(GetTimePickerValue(picker, "").Format(timeFormat)) + buffer.WriteString(GetTimePickerValue(picker).Format(timeFormat)) buffer.WriteByte('"') buffer.WriteString(` oninput="editViewInputEvent(this)"`) @@ -335,7 +292,7 @@ func (picker *timePickerData) htmlProperties(self View, buffer *strings.Builder) } func (picker *timePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` disabled`) } picker.viewData.htmlDisabledProperties(self, buffer) @@ -346,7 +303,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data case "textChanged": if text, ok := data.PropertyValue("text"); ok { if value, err := time.Parse(timeFormat, text); err == nil { - oldValue := GetTimePickerValue(picker, "") + oldValue := GetTimePickerValue(picker) picker.properties[TimePickerValue] = value if value != oldValue { for _, listener := range picker.timeChangedListeners { @@ -362,7 +319,7 @@ func (picker *timePickerData) handleCommand(self View, command string, data Data } func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) { - valueToTime := func(value interface{}) (time.Time, bool) { + valueToTime := func(value any) (time.Time, bool) { if value != nil { switch value := value.(type) { case time.Time: @@ -396,10 +353,10 @@ func getTimeProperty(view View, mainTag, shortTag string) (time.Time, bool) { // GetTimePickerMin returns the min time of TimePicker subview and "true" as the second value if the min time is set, // "false" as the second value otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTimePickerMin(view View, subviewID string) (time.Time, bool) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return getTimeProperty(view, TimePickerMin, Min) @@ -409,10 +366,10 @@ func GetTimePickerMin(view View, subviewID string) (time.Time, bool) { // GetTimePickerMax returns the max time of TimePicker subview and "true" as the second value if the min time is set, // "false" as the second value otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTimePickerMax(view View, subviewID string) (time.Time, bool) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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) { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { return getTimeProperty(view, TimePickerMax, Max) @@ -421,31 +378,16 @@ func GetTimePickerMax(view View, subviewID string) (time.Time, bool) { } // GetTimePickerStep returns the time changing step in seconds of TimePicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTimePickerStep(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 60 - } - - result, ok := intStyledProperty(view, TimePickerStep, 60) - if !ok { - result, _ = intStyledProperty(view, Step, 60) - } - - if result < 0 { - return 60 - } - return result +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTimePickerStep(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, TimePickerStep, 60) } // GetTimePickerValue returns the time of TimePicker subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTimePickerValue(view View, subviewID string) time.Time { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return time.Now() @@ -456,17 +398,7 @@ func GetTimePickerValue(view View, subviewID string) time.Time { // GetTimeChangedListeners returns the TimeChangedListener list of an TimePicker subview. // If there are no listeners then the empty list is returned -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTimeChangedListeners(view View, subviewID string) []func(TimePicker, time.Time) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(TimeChangedEvent); value != nil { - if listeners, ok := value.([]func(TimePicker, time.Time)); ok { - return listeners - } - } - } - return []func(TimePicker, time.Time){} +// 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) { + return getEventListeners[TimePicker, time.Time](view, subviewID, TimeChangedEvent) } diff --git a/touchEvents.go b/touchEvents.go index bcb3ede..09d4788 100644 --- a/touchEvents.go +++ b/touchEvents.go @@ -90,131 +90,6 @@ type TouchEvent struct { MetaKey bool } -func valueToTouchListeners(value interface{}) ([]func(View, TouchEvent), bool) { - if value == nil { - return nil, true - } - - switch value := value.(type) { - case func(View, TouchEvent): - return []func(View, TouchEvent){value}, true - - case func(TouchEvent): - fn := func(_ View, event TouchEvent) { - value(event) - } - return []func(View, TouchEvent){fn}, true - - case func(View): - fn := func(view View, _ TouchEvent) { - value(view) - } - return []func(View, TouchEvent){fn}, true - - case func(): - fn := func(View, TouchEvent) { - value() - } - return []func(View, TouchEvent){fn}, true - - case []func(View, TouchEvent): - if len(value) == 0 { - return nil, true - } - for _, fn := range value { - if fn == nil { - return nil, false - } - } - return value, true - - case []func(TouchEvent): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, TouchEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(_ View, event TouchEvent) { - v(event) - } - } - return listeners, true - - case []func(View): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, TouchEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(view View, _ TouchEvent) { - v(view) - } - } - return listeners, true - - case []func(): - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, TouchEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - listeners[i] = func(View, TouchEvent) { - v() - } - } - return listeners, true - - case []interface{}: - count := len(value) - if count == 0 { - return nil, true - } - listeners := make([]func(View, TouchEvent), count) - for i, v := range value { - if v == nil { - return nil, false - } - switch v := v.(type) { - case func(View, TouchEvent): - listeners[i] = v - - case func(TouchEvent): - listeners[i] = func(_ View, event TouchEvent) { - v(event) - } - - case func(View): - listeners[i] = func(view View, _ TouchEvent) { - v(view) - } - - case func(): - listeners[i] = func(View, TouchEvent) { - v() - } - - default: - return nil, false - } - } - return listeners, true - } - - return nil, false -} - var touchEvents = map[string]struct{ jsEvent, jsFunc string }{ TouchStart: {jsEvent: "ontouchstart", jsFunc: "touchStartEvent"}, TouchEnd: {jsEvent: "ontouchend", jsFunc: "touchEndEvent"}, @@ -222,8 +97,8 @@ var touchEvents = map[string]struct{ jsEvent, jsFunc string }{ TouchCancel: {jsEvent: "ontouchcancel", jsFunc: "touchCancelEvent"}, } -func (view *viewData) setTouchListener(tag string, value interface{}) bool { - listeners, ok := valueToTouchListeners(value) +func (view *viewData) setTouchListener(tag string, value any) bool { + listeners, ok := valueToEventListeners[View, TouchEvent](value) if !ok { notCompatibleType(tag, value) return false @@ -251,20 +126,6 @@ func (view *viewData) removeTouchListener(tag string) { } } -func getTouchListeners(view View, subviewID string, tag string) []func(View, TouchEvent) { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if value := view.Get(tag); value != nil { - if result, ok := value.([]func(View, TouchEvent)); ok { - return result - } - } - } - return []func(View, TouchEvent){} -} - func touchEventsHtml(view View, buffer *strings.Builder) { for tag, js := range touchEvents { if value := view.getRaw(tag); value != nil { @@ -309,7 +170,7 @@ func (event *TouchEvent) init(data DataObject) { } func handleTouchEvents(view View, tag string, data DataObject) { - listeners := getTouchListeners(view, "", tag) + listeners := getEventListeners[View, TouchEvent](view, nil, tag) if len(listeners) == 0 { return } @@ -323,25 +184,25 @@ 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. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTouchStartListeners(view View, subviewID string) []func(View, TouchEvent) { - return getTouchListeners(view, subviewID, TouchStart) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTouchStartListeners(view View, subviewID ...string) []func(View, TouchEvent) { + return getEventListeners[View, TouchEvent](view, subviewID, TouchStart) } // GetTouchEndListeners returns the "touch-end" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTouchEndListeners(view View, subviewID string) []func(View, TouchEvent) { - return getTouchListeners(view, subviewID, TouchEnd) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTouchEndListeners(view View, subviewID ...string) []func(View, TouchEvent) { + return getEventListeners[View, TouchEvent](view, subviewID, TouchEnd) } // GetTouchMoveListeners returns the "touch-move" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTouchMoveListeners(view View, subviewID string) []func(View, TouchEvent) { - return getTouchListeners(view, subviewID, TouchMove) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTouchMoveListeners(view View, subviewID ...string) []func(View, TouchEvent) { + return getEventListeners[View, TouchEvent](view, subviewID, TouchMove) } // GetTouchCancelListeners returns the "touch-cancel" listener list. If there are no listeners then the empty list is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTouchCancelListeners(view View, subviewID string) []func(View, TouchEvent) { - return getTouchListeners(view, subviewID, TouchCancel) +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTouchCancelListeners(view View, subviewID ...string) []func(View, TouchEvent) { + return getEventListeners[View, TouchEvent](view, subviewID, TouchCancel) } diff --git a/videoPlayer.go b/videoPlayer.go index 9fa129b..94592e0 100644 --- a/videoPlayer.go +++ b/videoPlayer.go @@ -1,7 +1,6 @@ package rui import ( - "fmt" "strings" ) @@ -30,7 +29,7 @@ type videoPlayerData struct { // NewVideoPlayer create new MediaPlayer object and return it func NewVideoPlayer(session Session, params Params) VideoPlayer { view := new(videoPlayerData) - view.Init(session) + view.init(session) view.tag = "VideoPlayer" setInitParams(view, params) return view @@ -40,8 +39,8 @@ func newVideoPlayer(session Session) View { return NewVideoPlayer(session, nil) } -func (player *videoPlayerData) Init(session Session) { - player.mediaPlayerData.Init(session) +func (player *videoPlayerData) init(session Session) { + player.mediaPlayerData.init(session) player.tag = "VideoPlayer" } @@ -77,11 +76,11 @@ func (player *videoPlayerData) remove(tag string) { } } -func (player *videoPlayerData) Set(tag string, value interface{}) bool { +func (player *videoPlayerData) Set(tag string, value any) bool { return player.set(strings.ToLower(tag), value) } -func (player *videoPlayerData) set(tag string, value interface{}) bool { +func (player *videoPlayerData) set(tag string, value any) bool { if value == nil { player.remove(tag) return true @@ -90,9 +89,9 @@ func (player *videoPlayerData) set(tag string, value interface{}) bool { if player.mediaPlayerData.set(tag, value) { session := player.Session() updateSize := func(cssTag string) { - if size, ok := floatProperty(player, tag, session, 0); ok { - if size > 0 { - updateProperty(player.htmlID(), cssTag, fmt.Sprintf("%g", size), session) + if size, ok := floatTextProperty(player, tag, session, 0); ok { + if size != "0" { + updateProperty(player.htmlID(), cssTag, size, session) } else { removeProperty(player.htmlID(), cssTag, session) } @@ -122,12 +121,16 @@ func (player *videoPlayerData) htmlProperties(self View, buffer *strings.Builder session := player.Session() - if size, ok := floatProperty(player, VideoWidth, session, 0); ok && size > 0 { - buffer.WriteString(fmt.Sprintf(` width="%g"`, size)) + if size, ok := floatTextProperty(player, VideoWidth, session, 0); ok && size != "0" { + buffer.WriteString(` width="`) + buffer.WriteString(size) + buffer.WriteString(`"`) } - if size, ok := floatProperty(player, VideoHeight, session, 0); ok && size > 0 { - buffer.WriteString(fmt.Sprintf(` height="%g"`, size)) + if size, ok := floatTextProperty(player, VideoHeight, session, 0); ok && size != "0" { + buffer.WriteString(` height="`) + buffer.WriteString(size) + buffer.WriteString(`"`) } if url, ok := stringProperty(player, Poster, session); ok && url != "" { diff --git a/view.go b/view.go index 070caa9..c29a8cb 100644 --- a/view.go +++ b/view.go @@ -33,14 +33,10 @@ type View interface { ViewStyle fmt.Stringer - // Init initializes fields of View by default values - Init(session Session) // Session returns the current Session interface Session() Session // Parent returns the parent view Parent() View - parentHTMLID() string - setParentID(parentID string) // Tag returns the tag of View interface Tag() string // ID returns the id of the view @@ -54,7 +50,7 @@ type View interface { // 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 // a description of the error is written to the log - SetAnimated(tag string, value interface{}, animation Animation) bool + SetAnimated(tag string, value any, animation Animation) bool // SetChangeListener set the function to track the change of the View property SetChangeListener(tag string, listener func(View, string)) // HasFocus returns 'true' if the view has focus @@ -65,14 +61,14 @@ type View interface { htmlTag() string closeHTMLTag() bool htmlID() string + parentHTMLID() string + setParentID(parentID string) htmlSubviews(self View, buffer *strings.Builder) htmlProperties(self View, buffer *strings.Builder) htmlDisabledProperties(self View, buffer *strings.Builder) cssStyle(self View, builder cssBuilder) addToCSSStyle(addCSS map[string]string) - getTransitions() Params - onResize(self View, x, y, width, height float64) onItemResize(self View, index string, x, y, width, height float64) setNoResizeEvent() @@ -102,7 +98,7 @@ type viewData struct { func newView(session Session) View { view := new(viewData) - view.Init(session) + view.init(session) return view } @@ -124,12 +120,12 @@ func setInitParams(view View, params Params) { // NewView create new View object and return it func NewView(session Session, params Params) View { view := new(viewData) - view.Init(session) + view.init(session) setInitParams(view, params) return view } -func (view *viewData) Init(session Session) { +func (view *viewData) init(session Session) { view.viewStyle.init() view.tag = "View" view.session = session @@ -197,7 +193,7 @@ func (view *viewData) remove(tag string) { case Style, StyleDisabled: if _, ok := view.properties[tag]; ok { delete(view.properties, tag) - updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view, "")), view.session) + updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view)), view.session) } case FocusEvent, LostFocusEvent: @@ -290,11 +286,11 @@ func (view *viewData) propertyChangedEvent(tag string) { } -func (view *viewData) Set(tag string, value interface{}) bool { +func (view *viewData) Set(tag string, value any) bool { return view.set(strings.ToLower(tag), value) } -func (view *viewData) set(tag string, value interface{}) bool { +func (view *viewData) set(tag string, value any) bool { if value == nil { view.remove(tag) return true @@ -327,7 +323,7 @@ func (view *viewData) set(tag string, value interface{}) bool { } view.properties[tag] = text if view.created { - updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view, "")), view.session) + updateProperty(view.htmlID(), "class", view.htmlClass(IsDisabled(view)), view.session) } case FocusEvent, LostFocusEvent: @@ -381,7 +377,7 @@ func viewPropertyChanged(view *viewData, tag string) { return case Visibility: - switch GetVisibility(view, "") { + switch GetVisibility(view) { case Invisible: updateCSSProperty(htmlID, Visibility, "hidden", session) updateCSSProperty(htmlID, "display", "", session) @@ -450,7 +446,7 @@ func viewPropertyChanged(view *viewData, tag string) { return case Outline, OutlineColor, OutlineStyle, OutlineWidth: - updateCSSProperty(htmlID, Outline, GetOutline(view, "").cssString(), session) + updateCSSProperty(htmlID, Outline, GetOutline(view).cssString(session), session) return case Shadow: @@ -465,20 +461,20 @@ func viewPropertyChanged(view *viewData, tag string) { RadiusTopRight, RadiusTopRightX, RadiusTopRightY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: - radius := GetRadius(view, "") - updateCSSProperty(htmlID, "border-radius", radius.cssString(), session) + radius := GetRadius(view) + updateCSSProperty(htmlID, "border-radius", radius.cssString(session), session) return case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft, "top-margin", "right-margin", "bottom-margin", "left-margin": - margin := GetMargin(view, "") - updateCSSProperty(htmlID, Margin, margin.cssString(), session) + margin := GetMargin(view) + updateCSSProperty(htmlID, Margin, margin.cssString(session), session) return case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, "top-padding", "right-padding", "bottom-padding", "left-padding": - padding := GetPadding(view, "") - updateCSSProperty(htmlID, Padding, padding.cssString(), session) + padding := GetPadding(view) + updateCSSProperty(htmlID, Padding, padding.cssString(session), session) return case AvoidBreak: @@ -593,9 +589,9 @@ func viewPropertyChanged(view *viewData, tag string) { } return - case ZIndex: - if i, ok := intProperty(view, ZIndex, session, 0); ok { - updateCSSProperty(htmlID, ZIndex, strconv.Itoa(i), session) + case ZIndex, TabSize: + if i, ok := intProperty(view, tag, session, 0); ok { + updateCSSProperty(htmlID, tag, strconv.Itoa(i), session) } return @@ -630,7 +626,7 @@ func viewPropertyChanged(view *viewData, tag string) { if cssTag, ok := sizeProperties[tag]; ok { size, _ := sizeProperty(view, tag, session) - updateCSSProperty(htmlID, cssTag, size.cssString(""), session) + updateCSSProperty(htmlID, cssTag, size.cssString("", session), session) return } @@ -639,6 +635,7 @@ func viewPropertyChanged(view *viewData, tag string) { TextColor: "color", TextLineColor: "text-decoration-color", CaretColor: CaretColor, + AccentColor: AccentColor, } if cssTag, ok := colorTags[tag]; ok { if color, ok := colorProperty(view, tag, session); ok { @@ -657,19 +654,19 @@ func viewPropertyChanged(view *viewData, tag string) { for _, floatTag := range []string{Opacity, ScaleX, ScaleY, ScaleZ, RotateX, RotateY, RotateZ} { if tag == floatTag { - if f, ok := floatProperty(view, floatTag, session, 0); ok { - updateCSSProperty(htmlID, floatTag, strconv.FormatFloat(f, 'g', -1, 64), session) + if f, ok := floatTextProperty(view, floatTag, session, 0); ok { + updateCSSProperty(htmlID, floatTag, f, session) } return } } } -func (view *viewData) Get(tag string) interface{} { +func (view *viewData) Get(tag string) any { return view.get(strings.ToLower(tag)) } -func (view *viewData) get(tag string) interface{} { +func (view *viewData) get(tag string) any { if tag == ID { if view.viewID != "" { return view.viewID @@ -681,7 +678,7 @@ func (view *viewData) get(tag string) interface{} { } func (view *viewData) htmlTag() string { - if semantics := GetSemantics(view, ""); semantics > DefaultSemantics { + if semantics := GetSemantics(view); semantics > DefaultSemantics { values := enumProperties[Semantics].cssValues if semantics < len(values) { return values[semantics] @@ -710,7 +707,7 @@ func (view *viewData) addToCSSStyle(addCSS map[string]string) { func (view *viewData) cssStyle(self View, builder cssBuilder) { view.viewStyle.cssViewStyle(builder, view.session) - switch GetVisibility(view, "") { + switch GetVisibility(view) { case Invisible: builder.add(`visibility`, `hidden`) @@ -734,7 +731,7 @@ func (view *viewData) htmlProperties(self View, buffer *strings.Builder) { } func (view *viewData) htmlDisabledProperties(self View, buffer *strings.Builder) { - if IsDisabled(self, "") { + if IsDisabled(self) { buffer.WriteString(` data-disabled="1"`) } else { buffer.WriteString(` data-disabled="0"`) @@ -749,7 +746,7 @@ func viewHTML(view View, buffer *strings.Builder) { buffer.WriteString(view.htmlID()) buffer.WriteRune('"') - disabled := IsDisabled(view, "") + disabled := IsDisabled(view) if cls := view.htmlClass(disabled); cls != "" { buffer.WriteString(` class="`) @@ -826,7 +823,7 @@ func (view *viewData) handleCommand(self View, command string, data DataObject) switch command { case KeyDownEvent, KeyUpEvent: - if !IsDisabled(self, "") { + if !IsDisabled(self) { handleKeyEvents(self, command, data) } @@ -841,13 +838,13 @@ func (view *viewData) handleCommand(self View, command string, data DataObject) case FocusEvent: view.hasFocus = true - for _, listener := range getFocusListeners(view, "", command) { + for _, listener := range getFocusListeners(view, nil, command) { listener(self) } case LostFocusEvent: view.hasFocus = false - for _, listener := range getFocusListeners(view, "", command) { + for _, listener := range getFocusListeners(view, nil, command) { listener(self) } diff --git a/viewClip.go b/viewClip.go index 0fb37ce..d8074ea 100644 --- a/viewClip.go +++ b/viewClip.go @@ -27,7 +27,7 @@ type circleClip struct { } type polygonClip struct { - points []interface{} + points []any } // InsetClip creates a rectangle View clipping area. @@ -73,9 +73,9 @@ func EllipseClip(x, y, rx, ry SizeUnit) ClipShape { // 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 []interface{}) ClipShape { +func PolygonClip(points []any) ClipShape { clip := new(polygonClip) - clip.points = []interface{}{} + clip.points = []any{} if clip.Set(Points, points) { return clip } @@ -85,14 +85,14 @@ func PolygonClip(points []interface{}) ClipShape { // PolygonPointsClip creates a polygon View clipping area. func PolygonPointsClip(points []SizeUnit) ClipShape { clip := new(polygonClip) - clip.points = []interface{}{} + clip.points = []any{} if clip.Set(Points, points) { return clip } return nil } -func (clip *insetClip) Set(tag string, value interface{}) bool { +func (clip *insetClip) Set(tag string, value any) bool { switch strings.ToLower(tag) { case Top, Right, Bottom, Left: if value == nil { @@ -146,13 +146,13 @@ func (clip *insetClip) cssStyle(session Session) string { for _, tag := range []string{Top, Right, Bottom, Left} { value, _ := sizeProperty(clip, tag, session) buffer.WriteString(leadText) - buffer.WriteString(value.cssString("0px")) + buffer.WriteString(value.cssString("0px", session)) leadText = " " } if radius := getRadiusProperty(clip); radius != nil { buffer.WriteString(" round ") - buffer.WriteString(radius.BoxRadius(session).cssString()) + buffer.WriteString(radius.BoxRadius(session).cssString(session)) } buffer.WriteRune(')') @@ -168,7 +168,7 @@ func (clip *insetClip) valid(session Session) bool { return false } -func (clip *circleClip) Set(tag string, value interface{}) bool { +func (clip *circleClip) Set(tag string, value any) bool { if value == nil { clip.Remove(tag) } @@ -211,15 +211,15 @@ func (clip *circleClip) cssStyle(session Session) string { buffer.WriteString("circle(") r, _ := sizeProperty(clip, Radius, session) - buffer.WriteString(r.cssString("50%")) + buffer.WriteString(r.cssString("50%", session)) buffer.WriteString(" at ") x, _ := sizeProperty(clip, X, session) - buffer.WriteString(x.cssString("50%")) + buffer.WriteString(x.cssString("50%", session)) buffer.WriteRune(' ') y, _ := sizeProperty(clip, Y, session) - buffer.WriteString(y.cssString("50%")) + buffer.WriteString(y.cssString("50%", session)) buffer.WriteRune(')') return buffer.String() @@ -232,7 +232,7 @@ func (clip *circleClip) valid(session Session) bool { return true } -func (clip *ellipseClip) Set(tag string, value interface{}) bool { +func (clip *ellipseClip) Set(tag string, value any) bool { if value == nil { clip.Remove(tag) } @@ -280,17 +280,17 @@ func (clip *ellipseClip) cssStyle(session Session) string { rx, _ := sizeProperty(clip, RadiusX, session) ry, _ := sizeProperty(clip, RadiusX, session) buffer.WriteString("ellipse(") - buffer.WriteString(rx.cssString("50%")) + buffer.WriteString(rx.cssString("50%", session)) buffer.WriteRune(' ') - buffer.WriteString(ry.cssString("50%")) + buffer.WriteString(ry.cssString("50%", session)) buffer.WriteString(" at ") x, _ := sizeProperty(clip, X, session) - buffer.WriteString(x.cssString("50%")) + buffer.WriteString(x.cssString("50%", session)) buffer.WriteRune(' ') y, _ := sizeProperty(clip, Y, session) - buffer.WriteString(y.cssString("50%")) + buffer.WriteString(y.cssString("50%", session)) buffer.WriteRune(')') return buffer.String() @@ -302,23 +302,23 @@ func (clip *ellipseClip) valid(session Session) bool { return rx.Value != 0 && ry.Value != 0 } -func (clip *polygonClip) Get(tag string) interface{} { +func (clip *polygonClip) Get(tag string) any { if Points == strings.ToLower(tag) { return clip.points } return nil } -func (clip *polygonClip) getRaw(tag string) interface{} { +func (clip *polygonClip) getRaw(tag string) any { return clip.Get(tag) } -func (clip *polygonClip) Set(tag string, value interface{}) bool { +func (clip *polygonClip) Set(tag string, value any) bool { if Points == strings.ToLower(tag) { switch value := value.(type) { - case []interface{}: + case []any: result := true - clip.points = make([]interface{}, len(value)) + clip.points = make([]any, len(value)) for i, val := range value { switch val := val.(type) { case string: @@ -343,7 +343,7 @@ func (clip *polygonClip) Set(tag string, value interface{}) bool { return result case []SizeUnit: - clip.points = make([]interface{}, len(value)) + clip.points = make([]any, len(value)) for i, point := range value { clip.points[i] = point } @@ -352,7 +352,7 @@ func (clip *polygonClip) Set(tag string, value interface{}) bool { case string: result := true values := strings.Split(value, ",") - clip.points = make([]interface{}, len(values)) + clip.points = make([]any, len(values)) for i, val := range values { val = strings.Trim(val, " \t\n\r") if isConstantName(val) { @@ -370,18 +370,18 @@ func (clip *polygonClip) Set(tag string, value interface{}) bool { return false } -func (clip *polygonClip) setRaw(tag string, value interface{}) { +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 = []interface{}{} + clip.points = []any{} } } func (clip *polygonClip) Clear() { - clip.points = []interface{}{} + clip.points = []any{} } func (clip *polygonClip) AllTags() []string { @@ -422,18 +422,18 @@ func (clip *polygonClip) cssStyle(session Session) string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - writePoint := func(value interface{}) { + 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")) + buffer.WriteString(size.cssString("0px", session)) return } } case SizeUnit: - buffer.WriteString(value.cssString("0px")) + buffer.WriteString(value.cssString("0px", session)) return } @@ -501,7 +501,7 @@ func parseClipShape(obj DataObject) ClipShape { return nil } -func (style *viewStyle) setClipShape(tag string, value interface{}) bool { +func (style *viewStyle) setClipShape(tag string, value any) bool { switch value := value.(type) { case ClipShape: style.properties[tag] = value @@ -558,10 +558,10 @@ func getClipShape(prop Properties, tag string, session Session) ClipShape { } // GetClip returns a View clipping area. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetClip(view View, subviewID string) ClipShape { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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()) @@ -571,10 +571,10 @@ func GetClip(view View, subviewID string) ClipShape { } // GetShapeOutside returns a shape around which adjacent inline content. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetShapeOutside(view View, subviewID string) ClipShape { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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()) diff --git a/viewFilter.go b/viewFilter.go index 09225f7..7b35939 100644 --- a/viewFilter.go +++ b/viewFilter.go @@ -130,7 +130,7 @@ func newViewFilter(obj DataObject) ViewFilter { return nil } -func (filter *viewFilter) Set(tag string, value interface{}) bool { +func (filter *viewFilter) Set(tag string, value any) bool { if value == nil { filter.Remove(tag) return true @@ -180,20 +180,22 @@ func (filter *viewFilter) cssStyle(session Session) string { buffer := allocStringBuilder() defer freeStringBuilder(buffer) - if value, ok := floatProperty(filter, Blur, session, 0); ok { - size := SizeUnit{Type: SizeInPixel, Value: value} + if value, ok := floatTextProperty(filter, Blur, session, 0); ok { buffer.WriteString(Blur) buffer.WriteRune('(') - buffer.WriteString(size.cssString("0px")) - buffer.WriteRune(')') + buffer.WriteString(value) + buffer.WriteString("px)") } for _, tag := range []string{Brightness, Contrast, Saturate, Grayscale, Invert, Opacity, Sepia} { - if value, ok := floatProperty(filter, tag, session, 0); ok { + if value, ok := floatTextProperty(filter, tag, session, 0); ok { if buffer.Len() > 0 { buffer.WriteRune(' ') } - buffer.WriteString(fmt.Sprintf("%s(%g%%)", tag, value)) + buffer.WriteString(tag) + buffer.WriteRune('(') + buffer.WriteString(value) + buffer.WriteString("%)") } } @@ -224,7 +226,7 @@ func (filter *viewFilter) cssStyle(session Session) string { return buffer.String() } -func (style *viewStyle) setFilter(tag string, value interface{}) bool { +func (style *viewStyle) setFilter(tag string, value any) bool { switch value := value.(type) { case ViewFilter: style.properties[tag] = value @@ -257,34 +259,46 @@ func (style *viewStyle) setFilter(tag string, value interface{}) bool { } // GetFilter returns a View graphical effects like blur or color shift. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetFilter(view View, subviewID string) ViewFilter { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 "" then a top position of the first argument (view) is returned -func GetBackdropFilter(view View, subviewID string) ViewFilter { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 diff --git a/viewStyle.go b/viewStyle.go index 9052fe3..1abd273 100644 --- a/viewStyle.go +++ b/viewStyle.go @@ -10,6 +10,16 @@ import ( // ViewStyle interface of the style of view type ViewStyle interface { Properties + + // Transition returns the transition animation of the property. Returns nil is there is no transition animation. + Transition(tag string) Animation + // Transitions returns the map of transition animations. The result is always non-nil. + Transitions() map[string]Animation + // 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. + // The "tag" argument is the property name. + SetTransition(tag string, animation Animation) + cssViewStyle(buffer cssBuilder, session Session) } @@ -163,11 +173,11 @@ func (style *viewStyle) backgroundCSS(session Session) string { func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { if margin, ok := boundsProperty(style, Margin, session); ok { - margin.cssValue(Margin, builder) + margin.cssValue(Margin, builder, session) } if padding, ok := boundsProperty(style, Padding, session); ok { - padding.cssValue(Padding, builder) + padding.cssValue(Padding, builder, session) } if border := getBorder(style, Border); border != nil { @@ -177,10 +187,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { } radius := getRadius(style, session) - radius.cssValue(builder) + radius.cssValue(builder, session) if outline := getOutline(style); outline != nil { - outline.ViewOutline(session).cssValue(builder) + outline.ViewOutline(session).cssValue(builder, session) } if z, ok := intProperty(style, ZIndex, session, 0); ok { @@ -198,14 +208,14 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { for _, tag := range []string{ Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom, TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness, - GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} { + ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} { if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto { cssTag, ok := sizeProperties[tag] if !ok { cssTag = tag } - builder.add(cssTag, size.cssString("")) + builder.add(cssTag, size.cssString("", session)) } } @@ -214,6 +224,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { {TextColor, "color"}, {TextLineColor, "text-decoration-color"}, {CaretColor, CaretColor}, + {AccentColor, AccentColor}, } for _, p := range colorProperties { if color, ok := colorProperty(style, p.property, session); ok && color != 0 { @@ -235,7 +246,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { writingMode := 0 for _, tag := range []string{ - TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection, + Overflow, TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection, VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, GridAutoFlow, Cursor, WhiteSpace, WordBreak, TextOverflow, Float, TableVerticalAlign, Resize} { @@ -268,6 +279,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { } } + if tabSize, ok := intProperty(style, TabSize, session, 8); ok && tabSize > 0 { + builder.add(TabSize, strconv.Itoa(tabSize)) + } + if text := style.cssTextDecoration(session); text != "" { builder.add("text-decoration", text) } @@ -384,12 +399,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { } if r, ok := rangeProperty(style, Row, session); ok { - builder.add("grid-row-start", strconv.Itoa(r.First+1)) - builder.add("grid-row-end", strconv.Itoa(r.Last+2)) + builder.add("grid-row", fmt.Sprintf("%d / %d", r.First+1, r.Last+2)) } if r, ok := rangeProperty(style, Column, session); ok { - builder.add("grid-column-start", strconv.Itoa(r.First+1)) - builder.add("grid-column-end", strconv.Itoa(r.Last+2)) + builder.add("grid-column", fmt.Sprintf("%d / %d", r.First+1, r.Last+2)) } if text := style.gridCellSizesCSS(CellWidth, session); text != "" { builder.add(`grid-template-columns`, text) @@ -442,7 +455,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { } } -func valueToOrientation(value interface{}, session Session) (int, bool) { +func valueToOrientation(value any, session Session) (int, bool) { if value != nil { switch value := value.(type) { case int: @@ -471,11 +484,11 @@ func valueToOrientation(value interface{}, session Session) (int, bool) { return 0, false } -func (style *viewStyle) Get(tag string) interface{} { +func (style *viewStyle) Get(tag string) any { return style.get(strings.ToLower(tag)) } -func (style *viewStyle) get(tag string) interface{} { +func (style *viewStyle) get(tag string) any { switch tag { case Border, CellBorder: return getBorder(&style.propertyList, tag) @@ -539,7 +552,7 @@ func (style *viewStyle) AllTags() []string { return result } -func supportedPropertyValue(value interface{}) bool { +func supportedPropertyValue(value any) bool { switch value.(type) { case string: case []string: @@ -551,7 +564,7 @@ func supportedPropertyValue(value interface{}) bool { case fmt.Stringer: case []ViewShadow: case []View: - case []interface{}: + case []any: case map[string]Animation: default: return false @@ -559,7 +572,7 @@ func supportedPropertyValue(value interface{}) bool { return true } -func writePropertyValue(buffer *strings.Builder, tag string, value interface{}, indent string) { +func writePropertyValue(buffer *strings.Builder, tag string, value any, indent string) { writeString := func(text string) { simple := (tag != Text && tag != Title && tag != Summary) @@ -569,7 +582,8 @@ func writePropertyValue(buffer *strings.Builder, tag string, value interface{}, } else { for _, ch := range text { if (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || - ch == '+' || ch == '-' || ch == '@' || ch == '/' || ch == '_' || ch == ':' { + ch == '+' || ch == '-' || ch == '@' || ch == '/' || ch == '_' || ch == ':' || + ch == '#' || ch == '%' || ch == 'π' || ch == '°' { } else { simple = false break @@ -654,7 +668,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value interface{}, value.writeString(buffer, indent+"\t") case fmt.Stringer: - buffer.WriteString(value.String()) + writeString(value.String()) case []ViewShadow: switch len(value) { @@ -697,7 +711,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value interface{}, buffer.WriteRune(']') } - case []interface{}: + case []any: switch count := len(value); count { case 0: buffer.WriteString("[]") @@ -741,7 +755,7 @@ func writePropertyValue(buffer *strings.Builder, tag string, value interface{}, if animation := value[tag]; animation != nil { buffer.WriteString(indent2) animation.writeTransitionString(tag, buffer) - buffer.WriteString("\n") + buffer.WriteString(",\n") } } buffer.WriteString(indent) @@ -755,7 +769,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent buffer.WriteString(" {\n") indent += "\t" - writeProperty := func(tag string, value interface{}) { + writeProperty := func(tag string, value any) { if supportedPropertyValue(value) { buffer.WriteString(indent) buffer.WriteString(tag) @@ -786,7 +800,7 @@ func writeViewStyle(name string, view ViewStyle, buffer *strings.Builder, indent Opacity, ZIndex, Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Margin, Padding, BackgroundClip, BackgroundColor, Background, Border, Radius, Outline, Shadow, Orientation, ListWrap, VerticalAlign, HorizontalAlign, CellWidth, CellHeight, - CellVerticalAlign, CellHorizontalAlign, GridRowGap, GridColumnGap, + CellVerticalAlign, CellHorizontalAlign, ListRowGap, ListColumnGap, GridRowGap, GridColumnGap, ColumnCount, ColumnWidth, ColumnSeparator, ColumnGap, AvoidBreak, Current, Expanded, Side, ResizeBorderWidth, EditViewType, MaxLength, Hint, Text, EditWrap, TextOverflow, FontName, TextSize, TextColor, TextWeight, Italic, SmallCaps, diff --git a/viewStyleSet.go b/viewStyleSet.go index 7f70c9a..a35d33f 100644 --- a/viewStyleSet.go +++ b/viewStyleSet.go @@ -4,7 +4,7 @@ import ( "strings" ) -func (style *viewStyle) setRange(tag string, value interface{}) bool { +func (style *viewStyle) setRange(tag string, value any) bool { switch value := value.(type) { case string: if strings.Contains(value, "@") { @@ -31,7 +31,7 @@ func (style *viewStyle) setRange(tag string, value interface{}) bool { return true } -func (style *viewStyle) setBackground(value interface{}) bool { +func (style *viewStyle) setBackground(value any) bool { switch value := value.(type) { case BackgroundElement: style.properties[Background] = []BackgroundElement{value} @@ -122,11 +122,11 @@ func (style *viewStyle) remove(tag string) { } } -func (style *viewStyle) Set(tag string, value interface{}) bool { +func (style *viewStyle) Set(tag string, value any) bool { return style.set(strings.ToLower(tag), value) } -func (style *viewStyle) set(tag string, value interface{}) bool { +func (style *viewStyle) set(tag string, value any) bool { if value == nil { style.remove(tag) return true diff --git a/viewTransform.go b/viewTransform.go index a69e876..d13f03a 100644 --- a/viewTransform.go +++ b/viewTransform.go @@ -1,9 +1,5 @@ package rui -import ( - "strconv" -) - const ( // Perspective is the name of the SizeUnit property that determines the distance between the z = 0 plane // and the user in order to give a 3D-positioned element some perspective. Each 3D element @@ -104,20 +100,6 @@ func getTranslate(style Properties, session Session) (SizeUnit, SizeUnit, SizeUn return x, y, z } -func getScale(style Properties, session Session) (float64, float64, float64, bool) { - scaleX, okX := floatProperty(style, ScaleX, session, 1) - scaleY, okY := floatProperty(style, ScaleY, session, 1) - scaleZ, okZ := floatProperty(style, ScaleZ, session, 1) - return scaleX, scaleY, scaleZ, okX || okY || okZ -} - -func getRotateVector(style Properties, session Session) (float64, float64, float64) { - rotateX, _ := floatProperty(style, RotateX, session, 1) - rotateY, _ := floatProperty(style, RotateY, session, 1) - rotateZ, _ := floatProperty(style, RotateZ, session, 1) - return rotateX, rotateY, rotateZ -} - func (style *viewStyle) transform(session Session) string { buffer := allocStringBuilder() @@ -133,45 +115,52 @@ func (style *viewStyle) transform(session Session) string { } x, y, z := getTranslate(style, session) - scaleX, scaleY, scaleZ, scaleOK := getScale(style, session) + + scaleX, okScaleX := floatTextProperty(style, ScaleX, session, 1) + scaleY, okScaleY := floatTextProperty(style, ScaleY, session, 1) + if getTransform3D(style, session) { if x.Type != Auto || y.Type != Auto || z.Type != Auto { if buffer.Len() > 0 { buffer.WriteRune(' ') } buffer.WriteString(`translate3d(`) - buffer.WriteString(x.cssString("0")) + buffer.WriteString(x.cssString("0", session)) buffer.WriteRune(',') - buffer.WriteString(y.cssString("0")) + buffer.WriteString(y.cssString("0", session)) buffer.WriteRune(',') - buffer.WriteString(z.cssString("0")) + buffer.WriteString(z.cssString("0", session)) buffer.WriteRune(')') } - if scaleOK { + scaleZ, okScaleZ := floatTextProperty(style, ScaleZ, session, 1) + if okScaleX || okScaleY || okScaleZ { if buffer.Len() > 0 { buffer.WriteRune(' ') } buffer.WriteString(`scale3d(`) - buffer.WriteString(strconv.FormatFloat(scaleX, 'g', -1, 64)) + buffer.WriteString(scaleX) buffer.WriteRune(',') - buffer.WriteString(strconv.FormatFloat(scaleY, 'g', -1, 64)) + buffer.WriteString(scaleY) buffer.WriteRune(',') - buffer.WriteString(strconv.FormatFloat(scaleZ, 'g', -1, 64)) + buffer.WriteString(scaleZ) buffer.WriteRune(')') } if angle, ok := angleProperty(style, Rotate, session); ok { - rotateX, rotateY, rotateZ := getRotateVector(style, session) + rotateX, _ := floatTextProperty(style, RotateX, session, 1) + rotateY, _ := floatTextProperty(style, RotateY, session, 1) + rotateZ, _ := floatTextProperty(style, RotateZ, session, 1) + if buffer.Len() > 0 { buffer.WriteRune(' ') } buffer.WriteString(`rotate3d(`) - buffer.WriteString(strconv.FormatFloat(rotateX, 'g', -1, 64)) + buffer.WriteString(rotateX) buffer.WriteRune(',') - buffer.WriteString(strconv.FormatFloat(rotateY, 'g', -1, 64)) + buffer.WriteString(rotateY) buffer.WriteRune(',') - buffer.WriteString(strconv.FormatFloat(rotateZ, 'g', -1, 64)) + buffer.WriteString(rotateZ) buffer.WriteRune(',') buffer.WriteString(angle.cssString()) buffer.WriteRune(')') @@ -183,20 +172,20 @@ func (style *viewStyle) transform(session Session) string { buffer.WriteRune(' ') } buffer.WriteString(`translate(`) - buffer.WriteString(x.cssString("0")) + buffer.WriteString(x.cssString("0", session)) buffer.WriteRune(',') - buffer.WriteString(y.cssString("0")) + buffer.WriteString(y.cssString("0", session)) buffer.WriteRune(')') } - if scaleOK { + if okScaleX || okScaleY { if buffer.Len() > 0 { buffer.WriteRune(' ') } buffer.WriteString(`scale(`) - buffer.WriteString(strconv.FormatFloat(scaleX, 'g', -1, 64)) + buffer.WriteString(scaleX) buffer.WriteRune(',') - buffer.WriteString(strconv.FormatFloat(scaleY, 'g', -1, 64)) + buffer.WriteString(scaleY) buffer.WriteRune(')') } @@ -216,12 +205,12 @@ func (style *viewStyle) transform(session Session) string { func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) { if getTransform3D(style, session) { if perspective, ok := sizeProperty(style, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 { - builder.add(`perspective`, perspective.cssString("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%"), y.cssString("50%")) + builder.addValues(`perspective-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session)) } if backfaceVisible, ok := boolProperty(style, BackfaceVisible, session); ok { @@ -234,12 +223,12 @@ func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Sessio x, y, z := getOrigin(style, session) if x.Type != Auto || y.Type != Auto || z.Type != Auto { - builder.addValues(`transform-origin`, ` `, x.cssString("50%"), y.cssString("50%"), z.cssString("0")) + 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%"), y.cssString("50%")) + builder.addValues(`transform-origin`, ` `, x.cssString("50%", session), y.cssString("50%", session)) } } @@ -256,17 +245,17 @@ func (view *viewData) updateTransformProperty(tag string) bool { case PerspectiveOriginX, PerspectiveOriginY: if getTransform3D(view, session) { - x, y := GetPerspectiveOrigin(view, "") + x, y := GetPerspectiveOrigin(view) value := "" if x.Type != Auto || y.Type != Auto { - value = x.cssString("50%") + " " + y.cssString("50%") + value = x.cssString("50%", session) + " " + y.cssString("50%", session) } updateCSSProperty(htmlID, "perspective-origin", value, session) } case BackfaceVisible: if getTransform3D(view, session) { - if GetBackfaceVisible(view, "") { + if GetBackfaceVisible(view) { updateCSSProperty(htmlID, BackfaceVisible, "visible", session) } else { updateCSSProperty(htmlID, BackfaceVisible, "hidden", session) @@ -278,11 +267,11 @@ func (view *viewData) updateTransformProperty(tag string) bool { value := "" if getTransform3D(view, session) { if x.Type != Auto || y.Type != Auto || z.Type != Auto { - value = x.cssString("50%") + " " + y.cssString("50%") + " " + z.cssString("50%") + 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%") + " " + y.cssString("50%") + value = x.cssString("50%", session) + " " + y.cssString("50%", session) } } updateCSSProperty(htmlID, "transform-origin", value, session) diff --git a/viewUtils.go b/viewUtils.go index e49d3e8..9cf0f8d 100644 --- a/viewUtils.go +++ b/viewUtils.go @@ -3,7 +3,7 @@ package rui // 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. // If the subview don't exists or the property is not set then nil is returned. -func Get(rootView View, viewID, tag string) interface{} { +func Get(rootView View, viewID, tag string) any { var view View if viewID != "" { view = ViewByID(rootView, viewID) @@ -19,7 +19,7 @@ func Get(rootView View, viewID, tag string) interface{} { // Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result: // true - success, // false - error (incompatible type or invalid format of a string value, see AppLog). -func Set(rootView View, viewID, tag string, value interface{}) bool { +func Set(rootView View, viewID, tag string, value any) bool { var view View if viewID != "" { view = ViewByID(rootView, viewID) @@ -33,7 +33,7 @@ func Set(rootView View, viewID, tag string, value interface{}) bool { } // SetChangeListener sets a listener for changing a subview property value. -// If the second argument (subviewID) is "" then a listener for the first argument (view) is set +// 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 viewID != "" { view = ViewByID(view, viewID) @@ -65,17 +65,17 @@ func SetParams(rootView View, viewID string, params Params) bool { } // IsDisabled returns "true" if the subview is disabled -// If the second argument (subviewID) is "" then a state of the first argument (view) is returned -func IsDisabled(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if disabled, _ := boolProperty(view, Disabled, view.Session()); disabled { return true } if parent := view.Parent(); parent != nil { - return IsDisabled(parent, "") + return IsDisabled(parent) } } return false @@ -86,39 +86,22 @@ func IsDisabled(view View, subviewID string) bool { // HeaderSemantics (4), MainSemantics (5), FooterSemantics (6), NavigationSemantics (7), // FigureSemantics (8), FigureCaptionSemantics (9), ButtonSemantics (10), ParagraphSemantics (11), // H1Semantics (12) - H6Semantics (17), BlockquoteSemantics (18), and CodeSemantics (19). -// If the second argument (subviewID) is "" then a semantics of the first argument (view) is returned -func GetSemantics(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if semantics, ok := enumStyledProperty(view, Semantics, DefaultSemantics); ok { - return semantics - } - } - - return DefaultSemantics +// If the second argument (subviewID) is not specified or it is "" then a semantics of the first argument (view) is returned +func GetSemantics(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, Semantics, DefaultSemantics, false) } // GetOpacity returns the subview opacity. -// If the second argument (subviewID) is "" then an opacity of the first argument (view) is returned -func GetOpacity(view View, subviewID string) float64 { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if style, ok := floatStyledProperty(view, Opacity, 1); ok { - return style - } - } - return 1 +// If the second argument (subviewID) is not specified or it is "" then an opacity of the first argument (view) is returned +func GetOpacity(view View, subviewID ...string) float64 { + return floatStyledProperty(view, subviewID, Opacity, 1) } // GetStyle returns the subview style id. -// If the second argument (subviewID) is "" then a style of the first argument (view) is returned -func GetStyle(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if style, ok := stringProperty(view, Style, view.Session()); ok { @@ -129,10 +112,10 @@ func GetStyle(view View, subviewID string) string { } // GetDisabledStyle returns the disabled subview style id. -// If the second argument (subviewID) is "" then a style of the first argument (view) is returned -func GetDisabledStyle(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if style, ok := stringProperty(view, StyleDisabled, view.Session()); ok { @@ -144,184 +127,114 @@ func GetDisabledStyle(view View, subviewID string) string { // GetVisibility returns the subview visibility. One of the following values is returned: // Visible (0), Invisible (1), or Gone (2) -// If the second argument (subviewID) is "" then a visibility of the first argument (view) is returned -func GetVisibility(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// If the second argument (subviewID) is not specified or it is "" then a visibility of the first argument (view) is returned +func GetVisibility(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, Visibility, Visible, false) +} + +// GetOverflow returns a value of the subview "overflow" property. Returns one of next values: +// 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 +func GetOverflow(view View, subviewID ...string) int { + defaultOverflow := OverflowHidden + view2 := view + if len(subviewID) > 0 && subviewID[0] != "" { + view2 = ViewByID(view, subviewID[0]) } - if view == nil { - return Visible + if view2 != nil { + switch view.(type) { + case EditView: + defaultOverflow = OverflowAuto + + case ListView: + defaultOverflow = OverflowAuto + } } - result, _ := enumStyledProperty(view, Visibility, Visible) - return result + return enumStyledProperty(view, subviewID, Overflow, defaultOverflow, false) } // GetZIndex returns the subview z-order. -// If the second argument (subviewID) is "" then a z-order of the first argument (view) is returned -func GetZIndex(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - result, _ := intStyledProperty(view, Visibility, 0) - return result +// If the second argument (subviewID) is not specified or it is "" then a z-order of the first argument (view) is returned +func GetZIndex(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, ZIndex, 0) } // GetWidth returns the subview width. -// If the second argument (subviewID) is "" then a width of the first argument (view) is returned -func GetWidth(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Width) - return result +// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned +func GetWidth(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, Width, false) } // GetHeight returns the subview height. -// If the second argument (subviewID) is "" then a height of the first argument (view) is returned -func GetHeight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Height) - return result +// 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 { + return sizeStyledProperty(view, subviewID, Height, false) } // GetMinWidth returns a minimal subview width. -// If the second argument (subviewID) is "" then a minimal width of the first argument (view) is returned -func GetMinWidth(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, MinWidth) - return result +// If the second argument (subviewID) is not specified or it is "" then a minimal width of the first argument (view) is returned +func GetMinWidth(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, MinWidth, false) } // GetMinHeight returns a minimal subview height. -// If the second argument (subviewID) is "" then a minimal height of the first argument (view) is returned -func GetMinHeight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, MinHeight) - return result +// If the second argument (subviewID) is not specified or it is "" then a minimal height of the first argument (view) is returned +func GetMinHeight(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, MinHeight, false) } // GetMaxWidth returns a maximal subview width. -// If the second argument (subviewID) is "" then a maximal width of the first argument (view) is returned -func GetMaxWidth(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, MaxWidth) - return result +// If the second argument (subviewID) is not specified or it is "" then a maximal width of the first argument (view) is returned +func GetMaxWidth(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, MaxWidth, false) } // GetMaxHeight returns a maximal subview height. -// If the second argument (subviewID) is "" then a maximal height of the first argument (view) is returned -func GetMaxHeight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, MaxHeight) - return result +// If the second argument (subviewID) is not specified or it is "" then a maximal height of the first argument (view) is returned +func GetMaxHeight(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, MaxHeight, false) } // GetResize returns the "resize" property value if the subview. One of the following values is returned: // NoneResize (0), BothResize (1), HorizontalResize (2), or VerticalResize (3) -// If the second argument (subviewID) is "" then a value of the first argument (view) is returned -func GetResize(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - result, _ := enumStyledProperty(view, Resize, 0) - return result +// If the second argument (subviewID) is not specified or it is "" then a value of the first argument (view) is returned +func GetResize(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, Resize, NoneResize, false) } // GetLeft returns a left position of the subview in an AbsoluteLayout container. // If a parent view is not an AbsoluteLayout container then this value is ignored. -// If the second argument (subviewID) is "" then a left position of the first argument (view) is returned -func GetLeft(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Left) - return result +// If the second argument (subviewID) is not specified or it is "" then a left position of the first argument (view) is returned +func GetLeft(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, Left, false) } // GetRight returns a right position of the subview in an AbsoluteLayout container. // If a parent view is not an AbsoluteLayout container then this value is ignored. -// If the second argument (subviewID) is "" then a right position of the first argument (view) is returned -func GetRight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Right) - return result +// If the second argument (subviewID) is not specified or it is "" then a right position of the first argument (view) is returned +func GetRight(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, Right, false) } // GetTop returns a top position of the subview in an AbsoluteLayout container. // If a parent view is not an AbsoluteLayout container then this value is ignored. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetTop(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Top) - return result +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetTop(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, Top, false) } // GetBottom returns a top position of the subview in an AbsoluteLayout container. // If a parent view is not an AbsoluteLayout container then this value is ignored. -// If the second argument (subviewID) is "" then a bottom position of the first argument (view) is returned -func GetBottom(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Bottom) - return result +// If the second argument (subviewID) is not specified or it is "" then a bottom position of the first argument (view) is returned +func GetBottom(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, Bottom, false) } // Margin returns the subview margin. -// If the second argument (subviewID) is "" then a margin of the first argument (view) is returned -func GetMargin(view View, subviewID string) Bounds { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } var bounds Bounds if view != nil { @@ -331,10 +244,10 @@ func GetMargin(view View, subviewID string) Bounds { } // GetPadding returns the subview padding. -// If the second argument (subviewID) is "" then a padding of the first argument (view) is returned -func GetPadding(view View, subviewID string) Bounds { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } var bounds Bounds if view != nil { @@ -344,10 +257,10 @@ func GetPadding(view View, subviewID string) Bounds { } // GetBorder returns ViewBorders of the subview. -// If the second argument (subviewID) is "" then a ViewBorders of the first argument (view) is returned. -func GetBorder(view View, subviewID string) ViewBorders { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if border := getBorder(view, Border); border != nil { @@ -358,10 +271,10 @@ func GetBorder(view View, subviewID string) ViewBorders { } // Radius returns the BoxRadius structure of the subview. -// If the second argument (subviewID) is "" then a BoxRadius of the first argument (view) is returned. -func GetRadius(view View, subviewID string) BoxRadius { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return BoxRadius{} @@ -370,10 +283,10 @@ func GetRadius(view View, subviewID string) BoxRadius { } // GetOutline returns ViewOutline of the subview. -// If the second argument (subviewID) is "" then a ViewOutline of the first argument (view) is returned. -func GetOutline(view View, subviewID string) ViewOutline { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if outline := getOutline(view); outline != nil { @@ -384,10 +297,10 @@ func GetOutline(view View, subviewID string) ViewOutline { } // GetViewShadows returns shadows of the subview. -// If the second argument (subviewID) is "" then shadows of the first argument (view) is returned. -func GetViewShadows(view View, subviewID string) []ViewShadow { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return []ViewShadow{} @@ -396,10 +309,10 @@ func GetViewShadows(view View, subviewID string) []ViewShadow { } // GetTextShadows returns text shadows of the subview. -// If the second argument (subviewID) is "" then shadows of the first argument (view) is returned. -func GetTextShadows(view View, subviewID string) []ViewShadow { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view == nil { return []ViewShadow{} @@ -408,23 +321,22 @@ func GetTextShadows(view View, subviewID string) []ViewShadow { } // GetBackgroundColor returns a background color of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetBackgroundColor(view View, subviewID string) Color { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return 0 - } - color, _ := colorStyledProperty(view, BackgroundColor) - return color +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetBackgroundColor(view View, subviewID ...string) Color { + return colorStyledProperty(view, subviewID, BackgroundColor, false) +} + +// GetAccentColor returns the accent color for UI controls generated by some elements. +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetAccentColor(view View, subviewID ...string) Color { + return colorStyledProperty(view, subviewID, AccentColor, false) } // GetFontName returns the subview font. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetFontName(view View, subviewID string) string { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { if font, ok := stringProperty(view, FontName, view.Session()); ok { @@ -436,370 +348,160 @@ func GetFontName(view View, subviewID string) string { } } if parent := view.Parent(); parent != nil { - return GetFontName(parent, "") + return GetFontName(parent) } } return "" } // GetTextColor returns a text color of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextColor(view View, subviewID string) Color { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if color, ok := colorStyledProperty(view, TextColor); ok { - return color - } - if parent := view.Parent(); parent != nil { - return GetTextColor(parent, "") - } - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextColor(view View, subviewID ...string) Color { + return colorStyledProperty(view, subviewID, TextColor, true) } // GetTextSize returns a text size of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextSize(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, TextSize); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextSize(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextSize(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, TextSize, true) +} + +// GetTabSize returns the subview width of tab characters (U+0009) in spaces. +// If the second argument (subviewID) is not specified or it is "" then a width of the first argument (view) is returned +func GetTabSize(view View, subviewID ...string) int { + return intStyledProperty(view, subviewID, TabSize, 8) } // GetTextWeight returns a text weight of the subview. Returns one of next values: // 1, 2, 3, 4 (normal text), 5, 6, 7 (bold text), 8 and 9 -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextWeight(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if weight, ok := enumStyledProperty(view, TextWeight, NormalFont); ok { - return weight - } - if parent := view.Parent(); parent != nil { - return GetTextWeight(parent, "") - } - } - return NormalFont +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextWeight(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TextWeight, NormalFont, true) } // GetTextAlign returns a text align of the subview. Returns one of next values: -// LeftAlign = 0, RightAlign = 1, CenterAlign = 2, JustifyAlign = 3 -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextAlign(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, TextAlign, LeftAlign); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextAlign(parent, "") - } - } - return LeftAlign +// +// LeftAlign = 0, RightAlign = 1, CenterAlign = 2, JustifyAlign = 3 +// +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextAlign(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TextAlign, LeftAlign, true) } // GetTextIndent returns a text indent of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextIndent(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, TextIndent); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextIndent(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextIndent(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, TextIndent, true) } // GetLetterSpacing returns a letter spacing of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetLetterSpacing(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, LetterSpacing); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetLetterSpacing(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetLetterSpacing(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, LetterSpacing, true) } // GetWordSpacing returns a word spacing of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetWordSpacing(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, WordSpacing); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetWordSpacing(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetWordSpacing(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, WordSpacing, true) } // GetLineHeight returns a height of a text line of the subview. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetLineHeight(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, LineHeight); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetLineHeight(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetLineHeight(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, LineHeight, true) } // IsItalic returns "true" if a text font of the subview is displayed in italics, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsItalic(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Italic); ok { - return result - } - if parent := view.Parent(); parent != nil { - return IsItalic(parent, "") - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsItalic(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Italic, true) } // IsSmallCaps returns "true" if a text font of the subview is displayed in small caps, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsSmallCaps(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, SmallCaps); ok { - return result - } - if parent := view.Parent(); parent != nil { - return IsSmallCaps(parent, "") - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsSmallCaps(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, SmallCaps, true) } // IsStrikethrough returns "true" if a text font of the subview is displayed strikethrough, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsStrikethrough(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Strikethrough); ok { - return result - } - if parent := view.Parent(); parent != nil { - return IsStrikethrough(parent, "") - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsStrikethrough(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Strikethrough, true) } // IsOverline returns "true" if a text font of the subview is displayed overlined, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsOverline(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Overline); ok { - return result - } - if parent := view.Parent(); parent != nil { - return IsOverline(parent, "") - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsOverline(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Overline, true) } // IsUnderline returns "true" if a text font of the subview is displayed underlined, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsUnderline(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, Underline); ok { - return result - } - if parent := view.Parent(); parent != nil { - return IsUnderline(parent, "") - } - } - return false +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func IsUnderline(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, Underline, true) } // GetTextLineThickness returns the stroke thickness of the decoration line that // is used on text in an element, such as a line-through, underline, or overline. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextLineThickness(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := sizeStyledProperty(view, TextLineThickness); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextLineThickness(parent, "") - } - } - return AutoSize() +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextLineThickness(view View, subviewID ...string) SizeUnit { + return sizeStyledProperty(view, subviewID, TextLineThickness, true) } // GetTextLineStyle returns the stroke style of the decoration line that // is used on text in an element, such as a line-through, underline, or overline. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextLineStyle(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, TextLineStyle, SolidLine); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextLineStyle(parent, "") - } - } - return SolidLine +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextLineStyle(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TextLineStyle, SolidLine, true) } // GetTextLineColor returns the stroke color of the decoration line that // is used on text in an element, such as a line-through, underline, or overline. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextLineColor(view View, subviewID string) Color { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if color, ok := colorStyledProperty(view, TextLineColor); ok { - return color - } - if parent := view.Parent(); parent != nil { - return GetTextLineColor(parent, "") - } - } - return 0 +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextLineColor(view View, subviewID ...string) Color { + return colorStyledProperty(view, subviewID, TextLineColor, true) } // GetTextTransform returns a text transform of the subview. Return one of next values: // NoneTextTransform (0), CapitalizeTextTransform (1), LowerCaseTextTransform (2) or UpperCaseTextTransform (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextTransform(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, TextTransform, NoneTextTransform); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextTransform(parent, "") - } - } - return NoneTextTransform +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextTransform(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, TextTransform, NoneTextTransform, true) } // GetWritingMode returns whether lines of text are laid out horizontally or vertically, as well as // the direction in which blocks progress. Valid values are HorizontalTopToBottom (0), // HorizontalBottomToTop (1), VerticalRightToLeft (2) and VerticalLeftToRight (3) -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetWritingMode(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, WritingMode, HorizontalTopToBottom); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetWritingMode(parent, "") - } - } - return HorizontalTopToBottom +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetWritingMode(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, WritingMode, HorizontalTopToBottom, true) } // GetTextDirection - returns a direction of text, table columns, and horizontal overflow. -// Valid values are Inherit (0), LeftToRightDirection (1), and RightToLeftDirection (2). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTextDirection(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// Valid values are SystemTextDirection (0), LeftToRightDirection (1), and RightToLeftDirection (2). +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetTextDirection(view View, subviewID ...string) int { + if view == nil { + return SystemTextDirection } - if view != nil { - if result, ok := enumStyledProperty(view, TextDirection, SystemTextDirection); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetTextDirection(parent, "") - } - } - return SystemTextDirection - // TODO return system text direction + defaultDirection := view.Session().TextDirection() + return enumStyledProperty(view, subviewID, TextDirection, defaultDirection, true) } // GetVerticalTextOrientation returns a orientation of the text characters in a line. It only affects text // in vertical mode (when "writing-mode" is "vertical-right-to-left" or "vertical-left-to-right"). // Valid values are MixedTextOrientation (0), UprightTextOrientation (1), and SidewaysTextOrientation (2). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetVerticalTextOrientation(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := enumStyledProperty(view, VerticalTextOrientation, MixedTextOrientation); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetVerticalTextOrientation(parent, "") - } - } - return MixedTextOrientation +// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned. +func GetVerticalTextOrientation(view View, subviewID ...string) int { + return enumStyledProperty(view, subviewID, VerticalTextOrientation, MixedTextOrientation, true) } // GetRow returns the range of row numbers of a GridLayout in which the subview is placed. -// If the second argument (subviewID) is "" then a values from the first argument (view) is returned. -func GetRow(view View, subviewID string) Range { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { session := view.Session() @@ -816,10 +518,10 @@ func GetRow(view View, subviewID string) Range { } // GetColumn returns the range of column numbers of a GridLayout in which the subview is placed. -// If the second argument (subviewID) is "" then a values from the first argument (view) is returned. -func GetColumn(view View, subviewID string) Range { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { session := view.Session() @@ -838,24 +540,17 @@ func GetColumn(view View, subviewID string) 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 "" then a value from the first argument (view) is returned. -func GetPerspective(view View, subviewID string) SizeUnit { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return AutoSize() - } - result, _ := sizeStyledProperty(view, Perspective) - return result +// 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 "" then a value from the first argument (view) is returned. -func GetPerspectiveOrigin(view View, subviewID string) (SizeUnit, SizeUnit) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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() @@ -867,25 +562,17 @@ func GetPerspectiveOrigin(view View, subviewID string) (SizeUnit, SizeUnit) { // visible when turned towards the user. Values: // true - the back face is visible when turned towards the user (default value). // false - the back face is hidden, effectively making the element invisible when turned away from the user. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetBackfaceVisible(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, BackfaceVisible); ok { - return result - } - } - return true +// 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 "" then a value from the first argument (view) is returned. -func GetOrigin(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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() @@ -894,10 +581,10 @@ func GetOrigin(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) { } // GetTranslate returns a x-, y-, and z-axis translation value of a 2D/3D translation -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetTranslate(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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() @@ -907,10 +594,10 @@ func GetTranslate(view View, subviewID string) (SizeUnit, SizeUnit, SizeUnit) { // GetSkew returns a angles to use to distort the element along the abscissa (x-axis) // and the ordinate (y-axis). The default value is 0. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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} @@ -920,65 +607,54 @@ func GetSkew(view View, subviewID string) (AngleUnit, AngleUnit) { } // GetScale returns a x-, y-, and z-axis scaling value of a 2D/3D scale. The default value is 1. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetScale(view View, subviewID string) (float64, float64, float64) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 } - x, y, z, _ := getScale(view, view.Session()) + + 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 "" then a value from the first argument (view) is returned. -func GetRotate(view View, subviewID string) (float64, float64, float64, AngleUnit) { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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, rotateY, rotateZ := getRotateVector(view, 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, // and "false" if allows, but does not force, any break to be inserted within the principal box. -// If the second argument (subviewID) is "" then a top position of the first argument (view) is returned -func GetAvoidBreak(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view == nil { - return false - } - result, _ := boolStyledProperty(view, AvoidBreak) - return result +// If the second argument (subviewID) is not specified or it is "" then a top position of the first argument (view) is returned +func GetAvoidBreak(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, AvoidBreak, true) } -func GetNotTranslate(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) - } - if view != nil { - if result, ok := boolStyledProperty(view, NotTranslate); ok { - return result - } - if parent := view.Parent(); parent != nil { - return GetNotTranslate(parent, "") - } - } - return false +func GetNotTranslate(view View, subviewID ...string) bool { + return boolStyledProperty(view, subviewID, NotTranslate, true) } -func valueFromStyle(view View, tag string) interface{} { +func valueFromStyle(view View, tag string) any { session := view.Session() - getValue := func(styleTag string) interface{} { + getValue := func(styleTag string) any { if style, ok := stringProperty(view, styleTag, session); ok { if style, ok := session.resolveConstants(style); ok { return session.styleProperty(style, tag) @@ -987,7 +663,7 @@ func valueFromStyle(view View, tag string) interface{} { return nil } - if IsDisabled(view, "") { + if IsDisabled(view) { if value := getValue(StyleDisabled); value != nil { return value } @@ -995,65 +671,132 @@ func valueFromStyle(view View, tag string) interface{} { return getValue(Style) } -func sizeStyledProperty(view View, tag string) (SizeUnit, bool) { - if value, ok := sizeProperty(view, tag, view.Session()); ok { - return value, true +func sizeStyledProperty(view View, subviewID []string, tag string, inherit bool) SizeUnit { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToSizeUnit(value, view.Session()) + + if view != nil { + if value, ok := sizeProperty(view, tag, view.Session()); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + if result, ok := valueToSizeUnit(value, view.Session()); ok { + return result + } + } + + if inherit { + if parent := view.Parent(); parent != nil { + return sizeStyledProperty(parent, []string{}, tag, true) + } + } } - return AutoSize(), false + return AutoSize() } -func enumStyledProperty(view View, tag string, defaultValue int) (int, bool) { - if value, ok := enumProperty(view, tag, view.Session(), defaultValue); ok { - return value, true +func enumStyledProperty(view View, subviewID []string, tag string, defaultValue int, inherit bool) int { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToEnum(value, tag, view.Session(), defaultValue) + + if view != nil { + if value, ok := enumProperty(view, tag, view.Session(), defaultValue); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + if result, ok := valueToEnum(value, tag, view.Session(), defaultValue); ok { + return result + } + } + + if inherit { + if parent := view.Parent(); parent != nil { + return enumStyledProperty(parent, []string{}, tag, defaultValue, true) + } + } } - return defaultValue, false + return defaultValue } -func boolStyledProperty(view View, tag string) (bool, bool) { - if value, ok := boolProperty(view, tag, view.Session()); ok { - return value, true +func boolStyledProperty(view View, subviewID []string, tag string, inherit bool) bool { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToBool(value, view.Session()) + + if view != nil { + if value, ok := boolProperty(view, tag, view.Session()); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + if b, ok := valueToBool(value, view.Session()); ok { + return b + } + } + + if inherit { + if parent := view.Parent(); parent != nil { + return boolStyledProperty(parent, []string{}, tag, inherit) + } + } } - return false, false + + return false } -func intStyledProperty(view View, tag string, defaultValue int) (int, bool) { - if value, ok := intProperty(view, tag, view.Session(), defaultValue); ok { - return value, true +func intStyledProperty(view View, subviewID []string, tag string, defaultValue int) int { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToInt(value, view.Session(), defaultValue) + + if view != nil { + if value, ok := intProperty(view, tag, view.Session(), defaultValue); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + n, _ := valueToInt(value, view.Session(), defaultValue) + return n + } } - return defaultValue, false + return defaultValue } -func floatStyledProperty(view View, tag string, defaultValue float64) (float64, bool) { - if value, ok := floatProperty(view, tag, view.Session(), defaultValue); ok { - return value, true +func floatStyledProperty(view View, subviewID []string, tag string, defaultValue float64) float64 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToFloat(value, view.Session(), defaultValue) + if view != nil { + if value, ok := floatProperty(view, tag, view.Session(), defaultValue); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + f, _ := valueToFloat(value, view.Session(), defaultValue) + return f + } } - - return defaultValue, false + return defaultValue } -func colorStyledProperty(view View, tag string) (Color, bool) { - if value, ok := colorProperty(view, tag, view.Session()); ok { - return value, true +func colorStyledProperty(view View, subviewID []string, tag string, inherit bool) Color { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } - if value := valueFromStyle(view, tag); value != nil { - return valueToColor(value, view.Session()) + if view != nil { + if value, ok := colorProperty(view, tag, view.Session()); ok { + return value + } + if value := valueFromStyle(view, tag); value != nil { + if color, ok := valueToColor(value, view.Session()); ok { + return color + } + } + if inherit { + if parent := view.Parent(); parent != nil { + return colorStyledProperty(parent, []string{}, tag, true) + } + } } - return Color(0), false + return Color(0) } // FocusView sets focus on the specified View, if it can be focused. @@ -1087,10 +830,10 @@ func BlurViewByID(viewID string, session Session) { } // GetCurrent returns the index of the selected item (<0 if there is no a selected item) or the current view index (StackLayout, TabsLayout). -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func GetCurrent(view View, subviewID string) int { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } defaultValue := -1 @@ -1105,10 +848,10 @@ func GetCurrent(view View, subviewID string) int { } // IsUserSelect returns "true" if the user can select text, "false" otherwise. -// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. -func IsUserSelect(view View, subviewID string) bool { - if subviewID != "" { - view = ViewByID(view, subviewID) +// 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 { + if len(subviewID) > 0 && subviewID[0] != "" { + view = ViewByID(view, subviewID[0]) } if view != nil { @@ -1120,29 +863,30 @@ func IsUserSelect(view View, subviewID string) bool { } func isUserSelect(view View) (bool, bool) { - result, ok := boolStyledProperty(view, UserSelect) - if ok { - return result, true + if value, ok := boolProperty(view, UserSelect, view.Session()); ok { + return value, true + } + if value := valueFromStyle(view, UserSelect); value != nil { + if b, ok := valueToBool(value, view.Session()); ok { + return b, true + } } if parent := view.Parent(); parent != nil { - result, ok = isUserSelect(parent) - if ok { + if result, ok := isUserSelect(parent); ok { return result, true } } - if !result { - switch GetSemantics(view, "") { - case ParagraphSemantics, H1Semantics, H2Semantics, H3Semantics, H4Semantics, H5Semantics, - H6Semantics, BlockquoteSemantics, CodeSemantics: - return true, false - } - - if _, ok := view.(TableView); ok { - return true, false - } + switch GetSemantics(view) { + case ParagraphSemantics, H1Semantics, H2Semantics, H3Semantics, H4Semantics, H5Semantics, + H6Semantics, BlockquoteSemantics, CodeSemantics: + return true, false } - return result, false + if _, ok := view.(TableView); ok { + return true, false + } + + return false, false } diff --git a/viewsContainer.go b/viewsContainer.go index 1e0b2d8..86bf757 100644 --- a/viewsContainer.go +++ b/viewsContainer.go @@ -25,8 +25,8 @@ type viewsContainerData struct { } // Init initialize fields of ViewsContainer by default values -func (container *viewsContainerData) Init(session Session) { - container.viewData.Init(session) +func (container *viewsContainerData) init(session Session) { + container.viewData.init(session) container.tag = "ViewsContainer" container.views = []View{} } @@ -47,8 +47,7 @@ func (container *viewsContainerData) setParentID(parentID string) { func (container *viewsContainerData) Views() []View { if container.views == nil { container.views = []View{} - } - if count := len(container.views); count > 0 { + } else if count := len(container.views); count > 0 { views := make([]View, count) copy(views, container.views) return views @@ -172,11 +171,11 @@ func (container *viewsContainerData) remove(tag string) { } } -func (container *viewsContainerData) Set(tag string, value interface{}) bool { +func (container *viewsContainerData) Set(tag string, value any) bool { return container.set(strings.ToLower(tag), value) } -func (container *viewsContainerData) set(tag string, value interface{}) bool { +func (container *viewsContainerData) set(tag string, value any) bool { if value == nil { container.remove(tag) return true @@ -187,9 +186,9 @@ func (container *viewsContainerData) set(tag string, value interface{}) bool { // do nothing case Disabled: - oldDisabled := IsDisabled(container, "") + oldDisabled := IsDisabled(container) if container.viewData.Set(Disabled, value) { - disabled := IsDisabled(container, "") + disabled := IsDisabled(container) if oldDisabled != disabled { if container.views != nil { for _, view := range container.views { @@ -224,7 +223,7 @@ func (container *viewsContainerData) set(tag string, value interface{}) bool { } container.views = views - case []interface{}: + case []any: views := []View{} for _, v := range value { switch v := v.(type) { @@ -279,11 +278,11 @@ func (container *viewsContainerData) set(tag string, value interface{}) bool { return true } -func (container *viewsContainerData) Get(tag string) interface{} { +func (container *viewsContainerData) Get(tag string) any { return container.get(strings.ToLower(tag)) } -func (container *viewsContainerData) get(tag string) interface{} { +func (container *viewsContainerData) get(tag string) any { switch tag { case Content: return container.views