forked from mbk-lab/rui_orig
2
0
Fork 0

Added SizeFunc interface, changed SizeUnit struct

This commit is contained in:
Alexei Anoshenko 2022-09-05 14:00:07 +03:00
parent dcf8240235
commit 2f7e1bbab3
27 changed files with 691 additions and 146 deletions

View File

@ -2,6 +2,8 @@
* Requires go 1.18 or higher * Requires go 1.18 or higher
* The "interface{}" type replaced by "any" * 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", * Added "list-row-gap", "list-column-gap", "accent-color", "tab-size", "overflow",
"arrow", "arrow-align", "arrow-size", "arrow-width", and "arrow-offset" properties "arrow", "arrow-align", "arrow-size", "arrow-width", and "arrow-offset" properties
* Added "@ruiArrowSize" and "@ruiArrowWidth" constants to the default theme * Added "@ruiArrowSize" and "@ruiArrowWidth" constants to the default theme

View File

@ -57,12 +57,12 @@
SizeUnit объявлена как SizeUnit объявлена как
type SizeUnit struct { type SizeUnit struct {
Type SizeUnitType Type SizeUnitType
Value float64 Value float64
Function SizeFunc
} }
где Type - тип размера; где Type - тип размера; Value - размер; Function - функция (используется только если Type == SizeFunction, в остальных случаях игнорируется).
Value - размер
Тип может принимать следующие значения: Тип может принимать следующие значения:
@ -79,12 +79,13 @@ Value - размер
| 8 | SizeInMM | поле Value определяет размер в миллиметрах. | | 8 | SizeInMM | поле Value определяет размер в миллиметрах. |
| 9 | SizeInCM | поле Value определяет размер в сантиметрах. | | 9 | SizeInCM | поле Value определяет размер в сантиметрах. |
| 10 | SizeInFraction | поле Value определяет размер в частях. Используется только для задания размеров ячеек в GridLayout. | | 10 | SizeInFraction | поле Value определяет размер в частях. Используется только для задания размеров ячеек в GridLayout. |
| 11 | SizeFunction | поле Function задает функцию для вычисления размера. Значение поля Value игнорируется |
Для более наглядного и простого задания переменных типа SizeUnit могут использоваться функции приведенные ниже Для более наглядного и простого задания переменных типа 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.Px(n) | rui.SizeUnit{ Type: rui.SizeInPixel, Value: n } |
| rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } | | rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } |
| rui.Ex(n) | rui.SizeUnit{ Type: rui.SizeInEX, 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.Cm(n) | rui.SizeUnit{ Type: rui.SizeInCM, Value: n } |
| rui.Fr(n) | rui.SizeUnit{ Type: rui.SizeInFraction, 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() Получить текстовое представление структуры можно свойством 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
Тип Color описывает 32-битный цвет в формате ARGB: Тип Color описывает 32-битный цвет в формате ARGB:

View File

@ -62,9 +62,10 @@ SizeUnit is declared as
type SizeUnit struct { type SizeUnit struct {
Type SizeUnitType Type SizeUnitType
Value float64 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: 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. | | 8 | SizeInMM | the Value field specifies the size in millimeters. |
| 9 | SizeInCM | the Value field defines the size in centimeters. | | 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. | | 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. For a more visual and simple setting of variables of the SizeUnit type, the functions below can be used.
| Function | Equivalent definition | | 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.Px(n) | rui.SizeUnit{ Type: rui.SizeInPixel, Value: n } |
| rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } | | rui.Em(n) | rui.SizeUnit{ Type: rui.SizeInEM, Value: n } |
| rui.Ex(n) | rui.SizeUnit{ Type: rui.SizeInEX, 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). 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 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”. a suffix defining the type.
The suffixes are listed in the following table: 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 | | 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 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 ### Color
The Color type describes a 32-bit ARGB color: The Color type describes a 32-bit ARGB color:

View File

@ -219,9 +219,9 @@ func (image *backgroundImage) cssStyle(session Session) string {
if width.Type != Auto || height.Type != Auto { if width.Type != Auto || height.Type != Auto {
buffer.WriteString(` / `) buffer.WriteString(` / `)
buffer.WriteString(width.cssString("auto")) buffer.WriteString(width.cssString("auto", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(height.cssString("auto")) buffer.WriteString(height.cssString("auto", session))
} }
} }

View File

@ -316,9 +316,9 @@ func (gradient *backgroundConicGradient) cssStyle(session Session) string {
buffer.WriteRune(' ') buffer.WriteRune(' ')
} }
buffer.WriteString("at ") buffer.WriteString("at ")
buffer.WriteString(x.cssString("50%")) buffer.WriteString(x.cssString("50%", session))
buffer.WriteString(" ") buffer.WriteString(" ")
buffer.WriteString(y.cssString("50%")) buffer.WriteString(y.cssString("50%", session))
comma = true comma = true
} }

View File

@ -261,19 +261,18 @@ func (gradient *backgroundGradient) writeGradient(session Session, buffer *strin
switch value := point.Pos.(type) { switch value := point.Pos.(type) {
case string: case string:
if value != "" { if value != "" {
if value[0] == '@' { if value, ok := session.resolveConstants(value); ok {
value, _ = session.Constant(value[1:]) if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto {
} buffer.WriteRune(' ')
if pos, ok := StringToSizeUnit(value); ok && pos.Type != Auto { buffer.WriteString(pos.cssString("", session))
buffer.WriteRune(' ') }
buffer.WriteString(pos.cssString(""))
} }
} }
case SizeUnit: case SizeUnit:
if value.Type != Auto { if value.Type != Auto {
buffer.WriteRune(' ') 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 { if r, ok := StringToSizeUnit(text); ok && r.Type != Auto {
buffer.WriteString("ellipse ") buffer.WriteString("ellipse ")
shapeText = "" shapeText = ""
buffer.WriteString(r.cssString("")) buffer.WriteString(r.cssString("", session))
buffer.WriteString(" ") buffer.WriteString(" ")
buffer.WriteString(r.cssString("")) buffer.WriteString(r.cssString("", session))
buffer.WriteString(" ") buffer.WriteString(" ")
} else { } else {
ErrorLog(`Invalid radial gradient radius: ` + text) ErrorLog(`Invalid radial gradient radius: ` + text)
@ -539,9 +538,9 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
if value.Type != Auto { if value.Type != Auto {
buffer.WriteString("ellipse ") buffer.WriteString("ellipse ")
shapeText = "" shapeText = ""
buffer.WriteString(value.cssString("")) buffer.WriteString(value.cssString("", session))
buffer.WriteString(" ") buffer.WriteString(" ")
buffer.WriteString(value.cssString("")) buffer.WriteString(value.cssString("", session))
buffer.WriteString(" ") buffer.WriteString(" ")
} }
@ -553,7 +552,7 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
buffer.WriteString("ellipse ") buffer.WriteString("ellipse ")
shapeText = "" shapeText = ""
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
buffer.WriteString(value[i].cssString("50%")) buffer.WriteString(value[i].cssString("50%", session))
buffer.WriteString(" ") buffer.WriteString(" ")
} }
@ -568,13 +567,13 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
if value[i] != nil { if value[i] != nil {
switch value := value[i].(type) { switch value := value[i].(type) {
case SizeUnit: case SizeUnit:
buffer.WriteString(value.cssString("50%")) buffer.WriteString(value.cssString("50%", session))
buffer.WriteString(" ") buffer.WriteString(" ")
case string: case string:
if text, ok := session.resolveConstants(value); ok { if text, ok := session.resolveConstants(value); ok {
if size, err := stringToSizeUnit(text); err == nil { if size, err := stringToSizeUnit(text); err == nil {
buffer.WriteString(size.cssString("50%")) buffer.WriteString(size.cssString("50%", session))
buffer.WriteString(" ") buffer.WriteString(" ")
} else { } else {
buffer.WriteString("50% ") buffer.WriteString("50% ")
@ -597,9 +596,9 @@ func (gradient *backgroundRadialGradient) cssStyle(session Session) string {
buffer.WriteString(shapeText) buffer.WriteString(shapeText)
} }
buffer.WriteString("at ") buffer.WriteString("at ")
buffer.WriteString(x.cssString("50%")) buffer.WriteString(x.cssString("50%", session))
buffer.WriteString(" ") buffer.WriteString(" ")
buffer.WriteString(y.cssString("50%")) buffer.WriteString(y.cssString("50%", session))
} }
buffer.WriteString(", ") buffer.WriteString(", ")

View File

@ -658,11 +658,14 @@ func (border *borderProperty) cssWidth(builder cssBuilder, session Session) {
borders.Top.Width == borders.Left.Width && borders.Top.Width == borders.Left.Width &&
borders.Top.Width == borders.Bottom.Width { borders.Top.Width == borders.Bottom.Width {
if borders.Top.Width.Type != Auto { 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 { } else {
builder.addValues("border-width", " ", borders.Top.Width.cssString("0"), builder.addValues("border-width", " ",
borders.Right.Width.cssString("0"), borders.Bottom.Width.cssString("0"), borders.Left.Width.cssString("0")) borders.Top.Width.cssString("0", session),
borders.Right.Width.cssString("0", session),
borders.Bottom.Width.cssString("0", session),
borders.Left.Width.cssString("0", session))
} }
} }

View File

@ -213,18 +213,21 @@ func (bounds *Bounds) String() string {
bounds.Bottom.String() + "," + bounds.Left.String() bounds.Bottom.String() + "," + bounds.Left.String()
} }
func (bounds *Bounds) cssValue(tag string, builder cssBuilder) { func (bounds *Bounds) cssValue(tag string, builder cssBuilder, session Session) {
if bounds.allFieldsEqual() { if bounds.allFieldsEqual() {
builder.add(tag, bounds.Top.cssString("0")) builder.add(tag, bounds.Top.cssString("0", session))
} else { } else {
builder.addValues(tag, " ", bounds.Top.cssString("0"), bounds.Right.cssString("0"), builder.addValues(tag, " ",
bounds.Bottom.cssString("0"), bounds.Left.cssString("0")) 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 var builder cssValueBuilder
bounds.cssValue("", &builder) bounds.cssValue("", &builder, session)
return builder.finish() return builder.finish()
} }

View File

@ -592,7 +592,7 @@ func (canvas *canvasData) writeFont(name string, script *strings.Builder) {
func (canvas *canvasData) SetFont(name string, size SizeUnit) { func (canvas *canvasData) SetFont(name string, size SizeUnit) {
canvas.script.WriteString("\nctx.font = '") 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) 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 { switch params.LineHeight.Type {
case Auto: case Auto:
@ -634,7 +634,7 @@ func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params F
default: default:
script.WriteString("/") script.WriteString("/")
script.WriteString(params.LineHeight.cssString("")) script.WriteString(params.LineHeight.cssString("", canvas.View().Session()))
} }
canvas.writeFont(name, script) canvas.writeFont(name, script)

View File

@ -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 { 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") builder.add("align-items", "stretch")

View File

@ -167,8 +167,9 @@ func (separator *columnSeparatorProperty) cssValue(session Session) string {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
if value.Width.Type != Auto && value.Width.Type != SizeInFraction && value.Width.Value > 0 { if value.Width.Type != Auto && value.Width.Type != SizeInFraction &&
buffer.WriteString(value.Width.cssString("")) (value.Width.Value > 0 || value.Width.Type == SizeFunction) {
buffer.WriteString(value.Width.cssString("", session))
} }
styles := enumProperties[BorderStyle].cssValues styles := enumProperties[BorderStyle].cssValues

View File

@ -145,7 +145,7 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string {
case 1: case 1:
if cellSize[0].Type != Auto { if cellSize[0].Type != Auto {
return `repeat(auto-fill, ` + cellSize[0].cssString(`auto`) + `)` return `repeat(auto-fill, ` + cellSize[0].cssString(`auto`, session) + `)`
} }
default: default:
@ -161,14 +161,14 @@ func (style *viewStyle) gridCellSizesCSS(tag string, session Session) string {
} }
if !allAuto { if !allAuto {
if allEqual { 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() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
for _, size := range cellSize { for _, size := range cellSize {
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(size.cssString(`auto`)) buffer.WriteString(size.cssString(`auto`, session))
} }
return buffer.String() return buffer.String()
} }

View File

@ -563,13 +563,13 @@ func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) {
func (listView *listViewData) itemSize(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(` min-width: `)
buffer.WriteString(itemWidth.cssString("")) buffer.WriteString(itemWidth.cssString("", listView.Session()))
buffer.WriteRune(';') buffer.WriteRune(';')
} }
if itemHeight := GetListItemHeight(listView); itemHeight.Type != Auto { if itemHeight := GetListItemHeight(listView); itemHeight.Type != Auto {
buffer.WriteString(` min-height: `) buffer.WriteString(` min-height: `)
buffer.WriteString(itemHeight.cssString("")) buffer.WriteString(itemHeight.cssString("", listView.Session()))
buffer.WriteRune(';') 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 { if gap, ok := sizeConstant(listView.session, "ruiCheckboxGap"); ok && gap.Type != Auto {
itemStyleBuilder.WriteString(` grid-gap: `) itemStyleBuilder.WriteString(` grid-gap: `)
itemStyleBuilder.WriteString(gap.cssString("auto")) itemStyleBuilder.WriteString(gap.cssString("auto", listView.Session()))
itemStyleBuilder.WriteRune(';') itemStyleBuilder.WriteRune(';')
} }
@ -896,13 +896,13 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
if gap := GetListRowGap(listView); gap.Type != Auto { if gap := GetListRowGap(listView); gap.Type != Auto {
buffer.WriteString(` row-gap: `) buffer.WriteString(` row-gap: `)
buffer.WriteString(gap.cssString("0")) buffer.WriteString(gap.cssString("0", listView.Session()))
buffer.WriteRune(';') buffer.WriteRune(';')
} }
if gap := GetListColumnGap(listView); gap.Type != Auto { if gap := GetListColumnGap(listView); gap.Type != Auto {
buffer.WriteString(` column-gap: `) buffer.WriteString(` column-gap: `)
buffer.WriteString(gap.cssString("0")) buffer.WriteString(gap.cssString("0", listView.Session()))
buffer.WriteRune(';') buffer.WriteRune(';')
} }

View File

@ -103,18 +103,18 @@ type ViewOutline struct {
Width SizeUnit Width SizeUnit
} }
func (outline ViewOutline) cssValue(builder cssBuilder) { func (outline ViewOutline) cssValue(builder cssBuilder, session Session) {
values := enumProperties[BorderStyle].cssValues values := enumProperties[BorderStyle].cssValues
if outline.Style > 0 && outline.Style < len(values) && outline.Color.Alpha() > 0 && if outline.Style > 0 && outline.Style < len(values) && outline.Color.Alpha() > 0 &&
outline.Width.Type != Auto && outline.Width.Type != SizeInFraction && outline.Width.Type != Auto && outline.Width.Type != SizeInFraction &&
outline.Width.Type != SizeInPercent && outline.Width.Value > 0 { 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 var builder cssValueBuilder
outline.cssValue(&builder) outline.cssValue(&builder, session)
return builder.finish() return builder.finish()
} }

View File

@ -38,6 +38,9 @@ func valueToSizeUnit(value any, session Session) (SizeUnit, bool) {
case SizeUnit: case SizeUnit:
return value, true return value, true
case SizeFunc:
return SizeUnit{Type: SizeFunction, Function: value}, true
case string: case string:
if text, ok := session.resolveConstants(value); ok { if text, ok := session.resolveConstants(value); ok {
return StringToSizeUnit(text) return StringToSizeUnit(text)

View File

@ -539,13 +539,20 @@ func (properties *propertyList) setSizeProperty(tag string, value any) bool {
switch value := value.(type) { switch value := value.(type) {
case string: case string:
var ok bool 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) invalidPropertyValue(tag, value)
return false return false
} }
case SizeUnit: case SizeUnit:
size = value size = value
case SizeFunc:
size.Type = SizeFunction
size.Function = value
case float32: case float32:
size.Type = SizeInPixel size.Type = SizeInPixel
size.Value = float64(value) size.Value = float64(value)

View File

@ -455,7 +455,7 @@ func (radius BoxRadius) String() string {
return buffer.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) && if (radius.TopLeftX.Type == Auto || radius.TopLeftX.Value == 0) &&
(radius.TopLeftY.Type == Auto || radius.TopLeftY.Value == 0) && (radius.TopLeftY.Type == Auto || radius.TopLeftY.Value == 0) &&
@ -471,23 +471,23 @@ func (radius BoxRadius) cssValue(builder cssBuilder) {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
buffer.WriteString(radius.TopLeftX.cssString("0")) buffer.WriteString(radius.TopLeftX.cssString("0", session))
if radius.AllAnglesIsEqual() { if radius.AllAnglesIsEqual() {
if !radius.TopLeftX.Equal(radius.TopLeftY) { if !radius.TopLeftX.Equal(radius.TopLeftY) {
buffer.WriteString(" / ") buffer.WriteString(" / ")
buffer.WriteString(radius.TopLeftY.cssString("0")) buffer.WriteString(radius.TopLeftY.cssString("0", session))
} }
} else { } else {
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.TopRightX.cssString("0")) buffer.WriteString(radius.TopRightX.cssString("0", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.BottomRightX.cssString("0")) buffer.WriteString(radius.BottomRightX.cssString("0", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.BottomLeftX.cssString("0")) buffer.WriteString(radius.BottomLeftX.cssString("0", session))
if !radius.TopLeftX.Equal(radius.TopLeftY) || if !radius.TopLeftX.Equal(radius.TopLeftY) ||
!radius.TopRightX.Equal(radius.TopRightY) || !radius.TopRightX.Equal(radius.TopRightY) ||
@ -495,22 +495,22 @@ func (radius BoxRadius) cssValue(builder cssBuilder) {
!radius.BottomRightX.Equal(radius.BottomRightY) { !radius.BottomRightX.Equal(radius.BottomRightY) {
buffer.WriteString(" / ") buffer.WriteString(" / ")
buffer.WriteString(radius.TopLeftY.cssString("0")) buffer.WriteString(radius.TopLeftY.cssString("0", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.TopRightY.cssString("0")) buffer.WriteString(radius.TopRightY.cssString("0", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.BottomRightY.cssString("0")) buffer.WriteString(radius.BottomRightY.cssString("0", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(radius.BottomLeftY.cssString("0")) buffer.WriteString(radius.BottomLeftY.cssString("0", session))
} }
} }
builder.add("border-radius", buffer.String()) builder.add("border-radius", buffer.String())
} }
func (radius BoxRadius) cssString() string { func (radius BoxRadius) cssString(session Session) string {
var builder cssValueBuilder var builder cssValueBuilder
radius.cssValue(&builder) radius.cssValue(&builder, session)
return builder.finish() return builder.finish()
} }

View File

@ -340,7 +340,7 @@ func (resizable *resizableData) updateResizeBorderWidth() {
} }
func (resizable *resizableData) cellSizeCSS() (string, string) { func (resizable *resizableData) cellSizeCSS() (string, string) {
w := resizable.resizeBorderWidth().cssString("4px") w := resizable.resizeBorderWidth().cssString("4px", resizable.Session())
side := resizable.getSide() side := resizable.getSide()
column := "1fr" column := "1fr"
row := "1fr" row := "1fr"
@ -384,7 +384,7 @@ func (resizable *resizableData) htmlSubviews(self View, buffer *strings.Builder)
top := 1 top := 1
leftSide := (side & LeftSide) != 0 leftSide := (side & LeftSide) != 0
rightSide := (side & RightSide) != 0 rightSide := (side & RightSide) != 0
w := resizable.resizeBorderWidth().cssString("4px") w := resizable.resizeBorderWidth().cssString("4px", resizable.Session())
if leftSide { if leftSide {
left = 2 left = 2

View File

@ -151,13 +151,13 @@ func (shadow *viewShadowData) cssStyle(buffer *strings.Builder, session Session,
buffer.WriteString("inset ") buffer.WriteString("inset ")
} }
buffer.WriteString(offsetX.cssString("0")) buffer.WriteString(offsetX.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(offsetY.cssString("0")) buffer.WriteString(offsetY.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(blurRadius.cssString("0")) buffer.WriteString(blurRadius.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(spreadRadius.cssString("0")) buffer.WriteString(spreadRadius.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(color.cssString()) buffer.WriteString(color.cssString())
return true return true
@ -177,11 +177,11 @@ func (shadow *viewShadowData) cssTextStyle(buffer *strings.Builder, session Sess
} }
buffer.WriteString(lead) buffer.WriteString(lead)
buffer.WriteString(offsetX.cssString("0")) buffer.WriteString(offsetX.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(offsetY.cssString("0")) buffer.WriteString(offsetY.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(blurRadius.cssString("0")) buffer.WriteString(blurRadius.cssString("0", session))
buffer.WriteByte(' ') buffer.WriteByte(' ')
buffer.WriteString(color.cssString()) buffer.WriteString(color.cssString())
return true return true

359
sizeFunc.go Normal file
View File

@ -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
}

50
sizeFunc_test.go Normal file
View File

@ -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))`)
}

View File

@ -14,89 +14,94 @@ import (
type SizeUnitType uint8 type SizeUnitType uint8
const ( const (
// Auto - default value. // Auto is the SizeUnit type: default value.
Auto SizeUnitType = 0 Auto SizeUnitType = 0
// SizeInPixel - size in pixels. // SizeInPixel is the SizeUnit type: the Value field specifies the size in pixels.
SizeInPixel SizeUnitType = 1 SizeInPixel SizeUnitType = 1
// SizeInEM - size in em. // SizeInEM is the SizeUnit type: the Value field specifies the size in em.
SizeInEM SizeUnitType = 2 SizeInEM SizeUnitType = 2
// SizeInEX - size in em. // SizeInEX is the SizeUnit type: the Value field specifies the size in em.
SizeInEX SizeUnitType = 3 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 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 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 SizeInPc SizeUnitType = 6
// SizeInInch - size in inches. // SizeInInch is the SizeUnit type: the Value field specifies the size in inches.
SizeInInch SizeUnitType = 7 SizeInInch SizeUnitType = 7
// SizeInMM - size in millimeters. // SizeInMM is the SizeUnit type: the Value field specifies the size in millimeters.
SizeInMM SizeUnitType = 8 SizeInMM SizeUnitType = 8
// SizeInCM - size in centimeters. // SizeInCM is the SizeUnit type: the Value field specifies the size in centimeters.
SizeInCM SizeUnitType = 9 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 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). // SizeUnit describe a size (Value field) and size unit (Type field).
type SizeUnit struct { type SizeUnit struct {
Type SizeUnitType Type SizeUnitType
Value float64 Value float64
Function SizeFunc
} }
// AutoSize creates SizeUnit with Auto type // AutoSize creates SizeUnit with Auto type
func AutoSize() SizeUnit { func AutoSize() SizeUnit {
return SizeUnit{Auto, 0} return SizeUnit{Auto, 0, nil}
} }
// Px creates SizeUnit with SizeInPixel type // Px creates SizeUnit with SizeInPixel type
func Px(value float64) SizeUnit { func Px(value float64) SizeUnit {
return SizeUnit{SizeInPixel, value} return SizeUnit{SizeInPixel, value, nil}
} }
// Em creates SizeUnit with SizeInEM type // Em creates SizeUnit with SizeInEM type
func Em(value float64) SizeUnit { func Em(value float64) SizeUnit {
return SizeUnit{SizeInEM, value} return SizeUnit{SizeInEM, value, nil}
} }
// Ex creates SizeUnit with SizeInEX type // Ex creates SizeUnit with SizeInEX type
func Ex(value float64) SizeUnit { func Ex(value float64) SizeUnit {
return SizeUnit{SizeInEX, value} return SizeUnit{SizeInEX, value, nil}
} }
// Percent creates SizeUnit with SizeInDIP type // Percent creates SizeUnit with SizeInDIP type
func Percent(value float64) SizeUnit { func Percent(value float64) SizeUnit {
return SizeUnit{SizeInPercent, value} return SizeUnit{SizeInPercent, value, nil}
} }
// Pt creates SizeUnit with SizeInPt type // Pt creates SizeUnit with SizeInPt type
func Pt(value float64) SizeUnit { func Pt(value float64) SizeUnit {
return SizeUnit{SizeInPt, value} return SizeUnit{SizeInPt, value, nil}
} }
// Pc creates SizeUnit with SizeInPc type // Pc creates SizeUnit with SizeInPc type
func Pc(value float64) SizeUnit { func Pc(value float64) SizeUnit {
return SizeUnit{SizeInPc, value} return SizeUnit{SizeInPc, value, nil}
} }
// Mm creates SizeUnit with SizeInMM type // Mm creates SizeUnit with SizeInMM type
func Mm(value float64) SizeUnit { func Mm(value float64) SizeUnit {
return SizeUnit{SizeInMM, value} return SizeUnit{SizeInMM, value, nil}
} }
// Cm creates SizeUnit with SizeInCM type // Cm creates SizeUnit with SizeInCM type
func Cm(value float64) SizeUnit { func Cm(value float64) SizeUnit {
return SizeUnit{SizeInCM, value} return SizeUnit{SizeInCM, value, nil}
} }
// Inch creates SizeUnit with SizeInInch type // Inch creates SizeUnit with SizeInInch type
func Inch(value float64) SizeUnit { func Inch(value float64) SizeUnit {
return SizeUnit{SizeInInch, value} return SizeUnit{SizeInInch, value, nil}
} }
// Fr creates SizeUnit with SizeInFraction type // Fr creates SizeUnit with SizeInFraction type
func Fr(value float64) SizeUnit { func Fr(value float64) SizeUnit {
return SizeUnit{SizeInFraction, value} return SizeUnit{SizeInFraction, value, nil}
} }
// Equal compare two SizeUnit. Return true if SizeUnit are equal // 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 return SizeUnit{Type: SizeInPixel, Value: val}, nil
} }
@ -161,8 +166,15 @@ func stringToSizeUnit(value string) (SizeUnit, error) {
// String - convert SizeUnit to string // String - convert SizeUnit to string
func (size SizeUnit) String() string { func (size SizeUnit) String() string {
if size.Type == Auto { switch size.Type {
case Auto:
return "auto" return "auto"
case SizeFunction:
if size.Function == nil {
return "auto"
}
return size.Function.String()
} }
if suffix, ok := sizeUnitSuffixes()[size.Type]; ok { if suffix, ok := sizeUnitSuffixes()[size.Type]; ok {
return fmt.Sprintf("%g%s", size.Value, suffix) return fmt.Sprintf("%g%s", size.Value, suffix)
@ -171,13 +183,19 @@ func (size SizeUnit) String() string {
} }
// cssString - convert SizeUnit to 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 { switch size.Type {
case Auto: case Auto:
return textForAuto return textForAuto
case SizeInEM: case SizeInEM:
return fmt.Sprintf("%grem", size.Value) return fmt.Sprintf("%grem", size.Value)
case SizeFunction:
if size.Function == nil {
return textForAuto
}
return size.Function.cssString(session)
} }
if size.Value == 0 { if size.Value == 0 {

View File

@ -594,7 +594,7 @@ func (table *tableViewData) propertyChanged(tag string) {
updateCSSProperty(htmlID, "border-spacing", "0", session) updateCSSProperty(htmlID, "border-spacing", "0", session)
updateCSSProperty(htmlID, "border-collapse", "collapse", session) updateCSSProperty(htmlID, "border-collapse", "collapse", session)
} else { } else {
updateCSSProperty(htmlID, "border-spacing", gap.cssString("0"), session) updateCSSProperty(htmlID, "border-spacing", gap.cssString("0", session), session)
updateCSSProperty(htmlID, "border-collapse", "separate", session) updateCSSProperty(htmlID, "border-collapse", "separate", session)
} }
@ -1319,24 +1319,26 @@ func (table *tableViewData) getCurrent() CellIndex {
} }
func (table *tableViewData) cssStyle(self View, builder cssBuilder) { 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 { if !ok || gap.Type == Auto || gap.Value <= 0 {
builder.add("border-spacing", "0") builder.add("border-spacing", "0")
builder.add("border-collapse", "collapse") builder.add("border-collapse", "collapse")
} else { } else {
builder.add("border-spacing", gap.cssString("0")) builder.add("border-spacing", gap.cssString("0", session))
builder.add("border-collapse", "separate") builder.add("border-collapse", "separate")
} }
} }
func (table *tableViewData) ReloadTableData() { func (table *tableViewData) ReloadTableData() {
session := table.Session()
if content := table.content(); content != nil { if content := table.content(); content != nil {
updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), table.Session()) updateProperty(table.htmlID(), "data-rows", strconv.Itoa(content.RowCount()), session)
updateProperty(table.htmlID(), "data-columns", strconv.Itoa(content.ColumnCount()), table.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) { func (table *tableViewData) onItemResize(self View, index string, x, y, width, height float64) {

10
view.go
View File

@ -446,7 +446,7 @@ func viewPropertyChanged(view *viewData, tag string) {
return return
case Outline, OutlineColor, OutlineStyle, OutlineWidth: case Outline, OutlineColor, OutlineStyle, OutlineWidth:
updateCSSProperty(htmlID, Outline, GetOutline(view).cssString(), session) updateCSSProperty(htmlID, Outline, GetOutline(view).cssString(session), session)
return return
case Shadow: case Shadow:
@ -462,19 +462,19 @@ func viewPropertyChanged(view *viewData, tag string) {
RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY, RadiusBottomLeft, RadiusBottomLeftX, RadiusBottomLeftY,
RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY: RadiusBottomRight, RadiusBottomRightX, RadiusBottomRightY:
radius := GetRadius(view) radius := GetRadius(view)
updateCSSProperty(htmlID, "border-radius", radius.cssString(), session) updateCSSProperty(htmlID, "border-radius", radius.cssString(session), session)
return return
case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft, case Margin, MarginTop, MarginRight, MarginBottom, MarginLeft,
"top-margin", "right-margin", "bottom-margin", "left-margin": "top-margin", "right-margin", "bottom-margin", "left-margin":
margin := GetMargin(view) margin := GetMargin(view)
updateCSSProperty(htmlID, Margin, margin.cssString(), session) updateCSSProperty(htmlID, Margin, margin.cssString(session), session)
return return
case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft, case Padding, PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
"top-padding", "right-padding", "bottom-padding", "left-padding": "top-padding", "right-padding", "bottom-padding", "left-padding":
padding := GetPadding(view) padding := GetPadding(view)
updateCSSProperty(htmlID, Padding, padding.cssString(), session) updateCSSProperty(htmlID, Padding, padding.cssString(session), session)
return return
case AvoidBreak: case AvoidBreak:
@ -626,7 +626,7 @@ func viewPropertyChanged(view *viewData, tag string) {
if cssTag, ok := sizeProperties[tag]; ok { if cssTag, ok := sizeProperties[tag]; ok {
size, _ := sizeProperty(view, tag, session) size, _ := sizeProperty(view, tag, session)
updateCSSProperty(htmlID, cssTag, size.cssString(""), session) updateCSSProperty(htmlID, cssTag, size.cssString("", session), session)
return return
} }

View File

@ -146,13 +146,13 @@ func (clip *insetClip) cssStyle(session Session) string {
for _, tag := range []string{Top, Right, Bottom, Left} { for _, tag := range []string{Top, Right, Bottom, Left} {
value, _ := sizeProperty(clip, tag, session) value, _ := sizeProperty(clip, tag, session)
buffer.WriteString(leadText) buffer.WriteString(leadText)
buffer.WriteString(value.cssString("0px")) buffer.WriteString(value.cssString("0px", session))
leadText = " " leadText = " "
} }
if radius := getRadiusProperty(clip); radius != nil { if radius := getRadiusProperty(clip); radius != nil {
buffer.WriteString(" round ") buffer.WriteString(" round ")
buffer.WriteString(radius.BoxRadius(session).cssString()) buffer.WriteString(radius.BoxRadius(session).cssString(session))
} }
buffer.WriteRune(')') buffer.WriteRune(')')
@ -211,15 +211,15 @@ func (clip *circleClip) cssStyle(session Session) string {
buffer.WriteString("circle(") buffer.WriteString("circle(")
r, _ := sizeProperty(clip, Radius, session) r, _ := sizeProperty(clip, Radius, session)
buffer.WriteString(r.cssString("50%")) buffer.WriteString(r.cssString("50%", session))
buffer.WriteString(" at ") buffer.WriteString(" at ")
x, _ := sizeProperty(clip, X, session) x, _ := sizeProperty(clip, X, session)
buffer.WriteString(x.cssString("50%")) buffer.WriteString(x.cssString("50%", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
y, _ := sizeProperty(clip, Y, session) y, _ := sizeProperty(clip, Y, session)
buffer.WriteString(y.cssString("50%")) buffer.WriteString(y.cssString("50%", session))
buffer.WriteRune(')') buffer.WriteRune(')')
return buffer.String() return buffer.String()
@ -280,17 +280,17 @@ func (clip *ellipseClip) cssStyle(session Session) string {
rx, _ := sizeProperty(clip, RadiusX, session) rx, _ := sizeProperty(clip, RadiusX, session)
ry, _ := sizeProperty(clip, RadiusX, session) ry, _ := sizeProperty(clip, RadiusX, session)
buffer.WriteString("ellipse(") buffer.WriteString("ellipse(")
buffer.WriteString(rx.cssString("50%")) buffer.WriteString(rx.cssString("50%", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
buffer.WriteString(ry.cssString("50%")) buffer.WriteString(ry.cssString("50%", session))
buffer.WriteString(" at ") buffer.WriteString(" at ")
x, _ := sizeProperty(clip, X, session) x, _ := sizeProperty(clip, X, session)
buffer.WriteString(x.cssString("50%")) buffer.WriteString(x.cssString("50%", session))
buffer.WriteRune(' ') buffer.WriteRune(' ')
y, _ := sizeProperty(clip, Y, session) y, _ := sizeProperty(clip, Y, session)
buffer.WriteString(y.cssString("50%")) buffer.WriteString(y.cssString("50%", session))
buffer.WriteRune(')') buffer.WriteRune(')')
return buffer.String() return buffer.String()
@ -427,13 +427,13 @@ func (clip *polygonClip) cssStyle(session Session) string {
case string: case string:
if val, ok := session.resolveConstants(value); ok { if val, ok := session.resolveConstants(value); ok {
if size, ok := StringToSizeUnit(val); ok { if size, ok := StringToSizeUnit(val); ok {
buffer.WriteString(size.cssString("0px")) buffer.WriteString(size.cssString("0px", session))
return return
} }
} }
case SizeUnit: case SizeUnit:
buffer.WriteString(value.cssString("0px")) buffer.WriteString(value.cssString("0px", session))
return return
} }

View File

@ -173,11 +173,11 @@ func (style *viewStyle) backgroundCSS(session Session) string {
func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) { func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if margin, ok := boundsProperty(style, Margin, session); ok { 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 { if padding, ok := boundsProperty(style, Padding, session); ok {
padding.cssValue(Padding, builder) padding.cssValue(Padding, builder, session)
} }
if border := getBorder(style, Border); border != nil { if border := getBorder(style, Border); border != nil {
@ -187,10 +187,10 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
} }
radius := getRadius(style, session) radius := getRadius(style, session)
radius.cssValue(builder) radius.cssValue(builder, session)
if outline := getOutline(style); outline != nil { 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 { if z, ok := intProperty(style, ZIndex, session, 0); ok {
@ -215,7 +215,7 @@ func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if !ok { if !ok {
cssTag = tag cssTag = tag
} }
builder.add(cssTag, size.cssString("")) builder.add(cssTag, size.cssString("", session))
} }
} }

View File

@ -125,11 +125,11 @@ func (style *viewStyle) transform(session Session) string {
buffer.WriteRune(' ') buffer.WriteRune(' ')
} }
buffer.WriteString(`translate3d(`) buffer.WriteString(`translate3d(`)
buffer.WriteString(x.cssString("0")) buffer.WriteString(x.cssString("0", session))
buffer.WriteRune(',') buffer.WriteRune(',')
buffer.WriteString(y.cssString("0")) buffer.WriteString(y.cssString("0", session))
buffer.WriteRune(',') buffer.WriteRune(',')
buffer.WriteString(z.cssString("0")) buffer.WriteString(z.cssString("0", session))
buffer.WriteRune(')') buffer.WriteRune(')')
} }
@ -172,9 +172,9 @@ func (style *viewStyle) transform(session Session) string {
buffer.WriteRune(' ') buffer.WriteRune(' ')
} }
buffer.WriteString(`translate(`) buffer.WriteString(`translate(`)
buffer.WriteString(x.cssString("0")) buffer.WriteString(x.cssString("0", session))
buffer.WriteRune(',') buffer.WriteRune(',')
buffer.WriteString(y.cssString("0")) buffer.WriteString(y.cssString("0", session))
buffer.WriteRune(')') buffer.WriteRune(')')
} }
@ -205,12 +205,12 @@ func (style *viewStyle) transform(session Session) string {
func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) { func (style *viewStyle) writeViewTransformCSS(builder cssBuilder, session Session) {
if getTransform3D(style, session) { if getTransform3D(style, session) {
if perspective, ok := sizeProperty(style, Perspective, session); ok && perspective.Type != Auto && perspective.Value != 0 { 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) x, y := getPerspectiveOrigin(style, session)
if x.Type != Auto || y.Type != Auto { 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 { 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) x, y, z := getOrigin(style, session)
if x.Type != Auto || y.Type != Auto || z.Type != Auto { 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 { } else {
x, y, _ := getOrigin(style, session) x, y, _ := getOrigin(style, session)
if x.Type != Auto || y.Type != Auto { 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) x, y := GetPerspectiveOrigin(view)
value := "" value := ""
if x.Type != Auto || y.Type != Auto { 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) updateCSSProperty(htmlID, "perspective-origin", value, session)
} }
@ -267,11 +267,11 @@ func (view *viewData) updateTransformProperty(tag string) bool {
value := "" value := ""
if getTransform3D(view, session) { if getTransform3D(view, session) {
if x.Type != Auto || y.Type != Auto || z.Type != Auto { 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 { } else {
if x.Type != Auto || y.Type != Auto { 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) updateCSSProperty(htmlID, "transform-origin", value, session)