forked from mbk-lab/rui_orig
Added SizeFunc interface, changed SizeUnit struct
This commit is contained in:
parent
dcf8240235
commit
2f7e1bbab3
|
@ -2,6 +2,8 @@
|
|||
|
||||
* 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
|
||||
|
|
58
README-ru.md
58
README-ru.md
|
@ -59,10 +59,10 @@ SizeUnit объявлена как
|
|||
type SizeUnit struct {
|
||||
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>, ...)"
|
||||
|
||||
где arg1, arg2, ... должны быть текстовым представлением SizeUnit или SizeFunc или константой. Например
|
||||
|
||||
"min(50%, 250px)"
|
||||
"min(75%, @a1)"
|
||||
|
||||
Интерфейс SizeFunc реализует интерфейс fmt.Stringer.
|
||||
Функция String() этого интерфейса возвращает текстовое представление SizeFunc.
|
||||
|
||||
Помимо min имеются следующие функции
|
||||
|
||||
| Текстовое представление | Функция для создания | Описание
|
||||
|------------------------------|--------------------------------------|-------------------------------------------------|
|
||||
| "min(<arg1>, <arg2>, ...)" | MaxSize(arg0, arg1 any, args ...any) | находит минимальное значение среди аргументов |
|
||||
| "sum(<arg1>, <arg2>, ...)" | SumSize(arg0, arg1 any, args ...any) | находит сумму значений аргументов |
|
||||
| "sub(<arg1>, <arg2>)" | SubSize(arg0, arg1 any) | находит разность значений аргументов |
|
||||
| "mul(<arg1>, <arg2>)" | MulSize(arg0, arg1 any) | находит результат умножения значений аргументов |
|
||||
| "div(<arg1>, <arg2>)" | DivSize(arg0, arg1 any) | находит результат деления значений аргументов |
|
||||
| "clamp(<min>, <val>, <max>)" | ClampSize(min, val, max any) | ограничивает значение заданным диапазоном |
|
||||
|
||||
Дополнительные пояснения к функции "clamp(<min>, <val>, <max>)": результат вычисляется следующим образом:
|
||||
|
||||
* if min ≤ val ≤ max then val;
|
||||
* if val < min then min;
|
||||
* if max < val then max;
|
||||
|
||||
### Color
|
||||
|
||||
Тип Color описывает 32-битный цвет в формате ARGB:
|
||||
|
|
56
README.md
56
README.md
|
@ -62,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:
|
||||
|
||||
|
@ -81,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 } |
|
||||
|
@ -100,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 |
|
||||
|:------:|----------------|
|
||||
|
@ -124,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(<arg1>, <arg2>, ...)"
|
||||
|
||||
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(<arg1>, <arg2>, ...)" | MaxSize(arg0, arg1 any, args ...any) | finds the minimum value among the arguments |
|
||||
| "sum(<arg1>, <arg2>, ...)" | SumSize(arg0, arg1 any, args ...any) | calculates the sum of the argument values |
|
||||
| "sub(<arg1>, <arg2>)" | SubSize(arg0, arg1 any) | calculates the subtraction of argument values |
|
||||
| "mul(<arg1>, <arg2>)" | MulSize(arg0, arg1 any) | calculates the result of multiplying the argument values |
|
||||
| "div(<arg1>, <arg2>)" | DivSize(arg0, arg1 any) | calculates the result of dividing the argument values |
|
||||
| "clamp(<min>, <val>, <max>)" | ClampSize(min, val, max any) | limits value to specified range |
|
||||
|
||||
Additional explanations for the function "clamp(<min>, <val>, <max>)": 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:
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 value, ok := session.resolveConstants(value); ok {
|
||||
if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(pos.cssString(""))
|
||||
buffer.WriteString(pos.cssString("", session))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case SizeUnit:
|
||||
if value.Type != Auto {
|
||||
buffer.WriteRune(' ')
|
||||
buffer.WriteString(value.cssString(""))
|
||||
buffer.WriteString(value.cssString("", session))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +552,7 @@ 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(" ")
|
||||
}
|
||||
|
||||
|
@ -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(", ")
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
15
bounds.go
15
bounds.go
|
@ -213,18 +213,21 @@ 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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -236,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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -145,7 +145,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 +161,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()
|
||||
}
|
||||
|
|
10
listView.go
10
listView.go
|
@ -563,13 +563,13 @@ 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 {
|
||||
buffer.WriteString(` min-width: `)
|
||||
buffer.WriteString(itemWidth.cssString(""))
|
||||
buffer.WriteString(itemWidth.cssString("", listView.Session()))
|
||||
buffer.WriteRune(';')
|
||||
}
|
||||
|
||||
if itemHeight := GetListItemHeight(listView); itemHeight.Type != Auto {
|
||||
buffer.WriteString(` min-height: `)
|
||||
buffer.WriteString(itemHeight.cssString(""))
|
||||
buffer.WriteString(itemHeight.cssString("", listView.Session()))
|
||||
buffer.WriteRune(';')
|
||||
}
|
||||
}
|
||||
|
@ -659,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(';')
|
||||
}
|
||||
|
||||
|
@ -896,13 +896,13 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
|
|||
|
||||
if gap := GetListRowGap(listView); gap.Type != Auto {
|
||||
buffer.WriteString(` row-gap: `)
|
||||
buffer.WriteString(gap.cssString("0"))
|
||||
buffer.WriteString(gap.cssString("0", listView.Session()))
|
||||
buffer.WriteRune(';')
|
||||
}
|
||||
|
||||
if gap := GetListColumnGap(listView); gap.Type != Auto {
|
||||
buffer.WriteString(` column-gap: `)
|
||||
buffer.WriteString(gap.cssString("0"))
|
||||
buffer.WriteString(gap.cssString("0", listView.Session()))
|
||||
buffer.WriteRune(';')
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ func valueToSizeUnit(value any, session Session) (SizeUnit, bool) {
|
|||
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)
|
||||
|
|
|
@ -539,13 +539,20 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
|
|||
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)
|
||||
|
|
24
radius.go
24
radius.go
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
14
shadow.go
14
shadow.go
|
@ -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
|
||||
|
|
|
@ -0,0 +1,359 @@
|
|||
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
|
||||
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) 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
|
||||
}
|
|
@ -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))`)
|
||||
}
|
68
sizeUnit.go
68
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
|
||||
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,7 +157,7 @@ func stringToSizeUnit(value string) (SizeUnit, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if val, err := strconv.ParseFloat(value, 64); err != nil {
|
||||
if val, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return SizeUnit{Type: SizeInPixel, Value: val}, nil
|
||||
}
|
||||
|
||||
|
@ -161,8 +166,15 @@ func stringToSizeUnit(value string) (SizeUnit, error) {
|
|||
|
||||
// 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)
|
||||
|
@ -171,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 {
|
||||
|
|
16
tableView.go
16
tableView.go
|
@ -594,7 +594,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)
|
||||
}
|
||||
|
||||
|
@ -1319,24 +1319,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) {
|
||||
|
|
10
view.go
10
view.go
|
@ -446,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:
|
||||
|
@ -462,19 +462,19 @@ func viewPropertyChanged(view *viewData, tag string) {
|
|||
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
|
||||
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
|
||||
radius := GetRadius(view)
|
||||
updateCSSProperty(htmlID, "border-radius", radius.cssString(), session)
|
||||
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)
|
||||
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)
|
||||
updateCSSProperty(htmlID, Padding, padding.cssString(session), session)
|
||||
return
|
||||
|
||||
case AvoidBreak:
|
||||
|
@ -626,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
|
||||
}
|
||||
|
||||
|
|
22
viewClip.go
22
viewClip.go
|
@ -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(')')
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -427,13 +427,13 @@ func (clip *polygonClip) cssStyle(session Session) string {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
10
viewStyle.go
10
viewStyle.go
|
@ -173,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 {
|
||||
|
@ -187,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 {
|
||||
|
@ -215,7 +215,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
|
|||
if !ok {
|
||||
cssTag = tag
|
||||
}
|
||||
builder.add(cssTag, size.cssString(""))
|
||||
builder.add(cssTag, size.cssString("", session))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,11 +125,11 @@ func (style *viewStyle) transform(session Session) string {
|
|||
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(')')
|
||||
}
|
||||
|
||||
|
@ -172,9 +172,9 @@ 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(')')
|
||||
}
|
||||
|
||||
|
@ -205,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 {
|
||||
|
@ -223,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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,7 +248,7 @@ func (view *viewData) updateTransformProperty(tag string) bool {
|
|||
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)
|
||||
}
|
||||
|
@ -267,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)
|
||||
|
|
Loading…
Reference in New Issue