diff --git a/README-ru.md b/README-ru.md index ed0831d..407bd75 100644 --- a/README-ru.md +++ b/README-ru.md @@ -990,16 +990,16 @@ RadiusProperty, а не структура BoxRadius. Получить стру Может принимать или значение типа AngleUnit (угол наклона линии относительно вертикали) или одно из следующих значений типа Int: -| Значение | Константа | Имя | Описание | -|:--------:|-----------------------|-------------------|------------------------------------------------| -| 0 | ToTopGradient | "to-top" | Линия идет снизу вверх (значение по умолчанию) | -| 1 | ToRightTopGradient | "to-right-top" | Из левого нижнего угла в правый верхний | -| 2 | ToRightGradient | "to-right" | Слева направо | -| 3 | ToRightBottomGradient | "to-right-bottom" | Из левого верхнего угла в правый нижний | -| 4 | ToBottomGradient | "to-bottom" | Сверху вних | -| 5 | ToLeftBottomGradient | "to-left-bottom" | Из правого верхнего угла в левый нижний | -| 6 | ToLeftGradient | "to-left" | Справа налево | -| 7 | ToLeftTopGradient | "to-left-top" | Из правого нижнего угла в левый верхний | +| Значение | Константа | Имя | Описание | +|:--------:|-----------------------|-------------------|-----------------------------------------| +| 0 | ToTopGradient | "to-top" | Линия идет снизу вверх | +| 1 | ToRightTopGradient | "to-right-top" | Из левого нижнего угла в правый верхний | +| 2 | ToRightGradient | "to-right" | Слева направо | +| 3 | ToRightBottomGradient | "to-right-bottom" | Из левого верхнего угла в правый нижний | +| 4 | ToBottomGradient | "to-bottom" | Сверху вниз (значение по умолчанию) | +| 5 | ToLeftBottomGradient | "to-left-bottom" | Из правого верхнего угла в левый нижний | +| 6 | ToLeftGradient | "to-left" | Справа налево | +| 7 | ToLeftTopGradient | "to-left-top" | Из правого нижнего угла в левый верхний | * Gradient ("gradient") - массив ключевых точек градиента (обязательный параметр). Каждая точка описывается структурой BackgroundGradientPoint, которая имеет два поля: Pos типа SizeUnit и Color. @@ -1010,12 +1010,12 @@ Pos определяет положение точки относительно Элементами этого массива могут быть BackgroundGradientPoint, Color, текстовое представление BackgroundGradientPoint или Color и имя константы -* Repeat ("repeat") - булево значение, определяющее будет ли повторяться градиент после последней +* Repeating ("repeating") - булево значение, определяющее будет ли повторяться градиент после последней ключевой точки. Необязательный параметр. Значение по умолчанию - false (не повторять) Текстовое представление линейного градиента имеет следующий вид: - linear-gradient { gradient = <значение> [, direction = <значение>] [, repeat = <значение>] } + linear-gradient { gradient = <значение> [, direction = <значение>] [, repeating = <значение>] } #### Радиальный градиент @@ -1028,7 +1028,7 @@ Pos определяет положение точки относительно * Gradient ("gradient") - массив ключевых точек градиента (обязательный параметр). Идентичен одноименному параметру линейного градиента. -* Repeat ("repeat") - булево значение, определяющее будет ли повторяться градиент после последней +* Repeating ("repeating") - булево значение, определяющее будет ли повторяться градиент после последней ключевой точки. Необязательный параметр. Значение по умолчанию - false (не повторять) * RadialGradientShape ("radial-gradient-shape") или Shape ("shape") - определяет форму градиента. @@ -1051,7 +1051,7 @@ Pos определяет положение точки относительно | FarthestSideGradient | 2 | "farthest-side" | Схоже с ClosestSideGradient, кроме того что, размер формы определяется самой дальней стороной от своего центра (или вертикальных и горизонтальных сторон) | | FarthestCornerGradient | 3 | "farthest-corner" | Конечная форма градиента определяется таким образом, чтобы он точно соответствовал самому дальнему углу прямоугольника от его центра | -Необязательный параметр. Значение по умолчанию ClosestSideGradient +Необязательный параметр. Значение по умолчанию FarthestCornerGradient * CenterX ("center-x"), CenterY ("center-y") - задает центр градиента относительно левого верхнего угла View. Принимает значение типа SizeUnit. Необязательный параметр. @@ -1059,9 +1059,46 @@ Pos определяет положение точки относительно Текстовое представление линейного градиента имеет следующий вид: - radial-gradient { gradient = <значение> [, repeat = <значение>] [, shape = <значение>] + radial-gradient { gradient = <значение> [, repeating = <значение>] [, shape = <значение>] [, radius = <значение>][, center-x = <значение>][, center-y = <значение>]} +#### Конический градиент + +Конический градиент создается с помощью функции + + func NewBackgroundConicGradient(params Params) BackgroundElement + +Радиальный градиент имеет следующие параметры: + +* Gradient ("gradient") - массив ключевых углов градиента (обязательный параметр). Каждая ключевой угол +описывается структурой BackgroundGradientAngle: + + type BackgroundGradientAngle struct { + Color interface{} + Angle interface{} + } + +где Color задает цвет ключевого угла и может принимать значение типа Color или string (цветовая константа +или текстовое описание цвета); +Angle задает угол относительно начального угла задаваемого параметром From и может принимать значение типа +AngleUnit или string (угловая константа или текстовое описание угла). + +Поле Color является обязательным и не может быть nil. Поле Angle опционально, если оно равно nil, то угол +задается как середина между соседними углами. Для первго элемента угол по умолчанию равен 0°, для последнего - 360°. + +* Repeating ("repeating") - булево значение, определяющее будет ли повторяться градиент после последнего +ключевого угла. Необязательный параметр. Значение по умолчанию - false (не повторять) + +* CenterX ("center-x"), CenterY ("center-y") - задает центр градиента относительно левого верхнего +угла View. Принимает значение типа SizeUnit. Необязательный параметр. +Значение по умолчанию "50%", т.е. центр градиента совпадает с центром View. + +Текстовое представление конического градиента имеет следующий вид: + + conic-gradient { gradient = <значение> [, repeating = <значение>] [, from = <значение>] + [, center-x = <значение>][, center-y = <значение>]} + + #### Изображение Изображение имеет следующие параметры: diff --git a/README.md b/README.md index ecb227c..c54be8a 100644 --- a/README.md +++ b/README.md @@ -972,11 +972,11 @@ Optional parameter. The default direction is from bottom to top. It can be eithe | Value | Constant | Name | Description | |:-----:|-----------------------|-------------------|-----------------------------------------------| -| 0 | ToTopGradient | "to-top" | Line goes from bottom to top (default) | +| 0 | ToTopGradient | "to-top" | Line goes from bottom to top | | 1 | ToRightTopGradient | "to-right-top" | From bottom left to top right | | 2 | ToRightGradient | "to-right" | From left to right | | 3 | ToRightBottomGradient | "to-right-bottom" | From top left to bottom right | -| 4 | ToBottomGradient | "to-bottom" | From top to bottom | +| 4 | ToBottomGradient | "to-bottom" | From top to bottom (default) | | 5 | ToLeftBottomGradient | "to-left-bottom" | From the upper right corner to the lower left | | 6 | ToLeftGradient | "to-left" | From right to left | | 7 | ToLeftTopGradient | "to-left-top" | From the bottom right corner to the top left | @@ -988,7 +988,7 @@ You can also pass a Color array as the gradient value. In this case, the points You can also use an array of []interface{} as an array of cue points. The elements of this array can be BackgroundGradientPoint, Color, BackgroundGradientPoint or Color text representation, and the name of the constant -* Repeat ("repeat") - a boolean value that determines whether the gradient will repeat after the last key point. +* Repeating ("repeating") - a boolean value that determines whether the gradient will repeat after the last key point. Optional parameter. The default is false (do not repeat) The linear gradient text representation is as follows: @@ -1005,7 +1005,7 @@ The radial gradient has the following parameters: * Gradient ("gradient") - array of gradient key points (required parameter). Identical to the linear gradient parameter of the same name. -* Repeat ("repeat") - a boolean value that determines whether the gradient will repeat after the last key point. +* Repeating ("repeating") - a boolean value that determines whether the gradient will repeat after the last key point. Optional parameter. The default is false (do not repeat) * RadialGradientShape ("radial-gradient-shape") or Shape ("shape") - defines the shape of the gradient. @@ -1028,7 +1028,7 @@ Can be either SizeUnit or one of the following int values: | 2 | FarthestSideGradient | "farthest-side" | Similar to ClosestSideGradient, except that the size of the shape is determined by the farthest side from its center (or vertical and horizontal sides) | | 3 | FarthestCornerGradient | "farthest-corner" | The final shape of the gradient is defined so that it exactly matches the farthest corner of the rectangle from its center | -Optional parameter. The default is ClosestSideGradient +Optional parameter. The default is FarthestCornerGradient * CenterX ("center-x"), CenterY ("center-y") - sets the center of the gradient relative to the upper left corner of the View. Takes in a SizeUnit value. Optional parameter. The default value is "50%", i.e. the center of the gradient is the center of the View. @@ -1038,6 +1038,45 @@ The linear gradient text representation is as follows: radial-gradient { gradient = [, repeat = ] [, shape = ] [, radius = ][, center-x = ][, center-y = ]} +#### Conic gradient + +The conic gradient is created using the function + + func NewBackgroundConicGradient(params Params) BackgroundElement + +The radial gradient has the following options: + +* Gradient ("gradient") - array of gradient key angles (required parameter). +Each key angle is described by a BackgroundGradientAngle structure: + + type BackgroundGradientAngle struct { + Color interface{} + Angle interface{} + } + +where Color specifies the color of the key corner and can take a value of Color type or +string (color constant or textual description of the color); +Angle sets the angle relative to the initial angle specified by the From parameter +and can take a value of the AngleUnit type or string (an angle constant or a textual description of the angle). + +The Color field is required and cannot be nil. + +The Angle field is optional. If it is nil, then the angle is set as the midpoint between adjacent corners. +For the first element, the default angle is 0°, for the last element it is 360°. + +* Repeating ("repeating") is a boolean value that determines whether the gradient will repeat after the last key corner. +Optional parameter. Default value is false (don't repeat) + +* CenterX ("center-x"), CenterY ("center-y") - sets the center of the gradient relative to the upper left corner of the View. +Takes a value of SizeUnit type. Optional parameter. +The default value is "50%", i.e. the center of the gradient is the same as the center of the View. + +The textual representation of a conic gradient looks like this: + + conic-gradient { gradient = [, repeating = ] [, from = ] + [, center-x = ][, center-y = ]} + + #### Image The image has the following parameters: diff --git a/background.go b/background.go index 4d08d0f..9aed11d 100644 --- a/background.go +++ b/background.go @@ -56,47 +56,6 @@ const ( // ContentBoxClip is value of the BackgroundClip property: // The background is painted within (clipped to) the content box. ContentBoxClip = 2 - - // ToTopGradient is value of the Direction property of a linear gradient. The value is equivalent to the 0deg angle - ToTopGradient = 0 - // ToRightTopGradient is value of the Direction property of a linear gradient. - ToRightTopGradient = 1 - // ToRightGradient is value of the Direction property of a linear gradient. The value is equivalent to the 90deg angle - ToRightGradient = 2 - // ToRightBottomGradient is value of the Direction property of a linear gradient. - ToRightBottomGradient = 3 - // ToBottomGradient is value of the Direction property of a linear gradient. The value is equivalent to the 180deg angle - ToBottomGradient = 4 - // ToLeftBottomGradient is value of the Direction property of a linear gradient. - ToLeftBottomGradient = 5 - // ToLeftGradient is value of the Direction property of a linear gradient. The value is equivalent to the 270deg angle - ToLeftGradient = 6 - // ToLeftTopGradient is value of the Direction property of a linear gradient. - ToLeftTopGradient = 7 - - // EllipseGradient is value of the Shape property of a radial gradient background: - // the shape is an axis-aligned ellipse - EllipseGradient = 0 - // CircleGradient is value of the Shape property of a radial gradient background: - // the gradient's shape is a circle with constant radius - CircleGradient = 1 - - // ClosestSideGradient is value of the Radius property of a radial gradient background: - // The gradient's ending shape meets the side of the box closest to its center (for circles) - // or meets both the vertical and horizontal sides closest to the center (for ellipses). - ClosestSideGradient = 0 - // ClosestCornerGradient is value of the Radius property of a radial gradient background: - // The gradient's ending shape is sized so that it exactly meets the closest corner - // of the box from its center. - ClosestCornerGradient = 1 - // FarthestSideGradient is value of the Radius property of a radial gradient background: - // Similar to closest-side, except the ending shape is sized to meet the side of the box - // farthest from its center (or vertical and horizontal sides). - FarthestSideGradient = 2 - // FarthestCornerGradient is value of the Radius property of a radial gradient background: - // The default value, the gradient's ending shape is sized so that it exactly meets - // the farthest corner of the box from its center. - FarthestCornerGradient = 3 ) // BackgroundElement describes the background element. @@ -104,6 +63,7 @@ type BackgroundElement interface { Properties cssStyle(session Session) string Tag() string + Clone() BackgroundElement } type backgroundElement struct { @@ -114,26 +74,6 @@ type backgroundImage struct { backgroundElement } -// BackgroundGradientPoint define point on gradient straight line -type BackgroundGradientPoint struct { - // Pos - the distance from the start of the gradient straight line - Pos SizeUnit - // Color - the color of the point - Color Color -} - -type backgroundGradient struct { - backgroundElement -} - -type backgroundLinearGradient struct { - backgroundGradient -} - -type backgroundRadialGradient struct { - backgroundGradient -} - // NewBackgroundImage creates the new background image func createBackground(obj DataObject) BackgroundElement { var result BackgroundElement = nil @@ -154,6 +94,11 @@ func createBackground(obj DataObject) BackgroundElement { gradient.properties = map[string]interface{}{} result = gradient + case "conic-gradient": + gradient := new(backgroundConicGradient) + gradient.properties = map[string]interface{}{} + result = gradient + default: return nil } @@ -180,30 +125,18 @@ func NewBackgroundImage(params Params) BackgroundElement { return result } -// NewBackgroundLinearGradient creates the new background linear gradient -func NewBackgroundLinearGradient(params Params) BackgroundElement { - result := new(backgroundLinearGradient) - result.properties = map[string]interface{}{} - for tag, value := range params { - result.Set(tag, value) - } - return result -} - -// NewBackgroundRadialGradient creates the new background radial gradient -func NewBackgroundRadialGradient(params Params) BackgroundElement { - result := new(backgroundRadialGradient) - result.properties = map[string]interface{}{} - for tag, value := range params { - result.Set(tag, value) - } - return result -} - func (image *backgroundImage) Tag() string { return "image" } +func (image *backgroundImage) Clone() BackgroundElement { + result := NewBackgroundImage(nil) + for tag, value := range image.properties { + result.setRaw(tag, value) + } + return result +} + func (image *backgroundImage) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { @@ -306,408 +239,3 @@ func (image *backgroundImage) cssStyle(session Session) string { return "" } - -func (gradient *backgroundGradient) Set(tag string, value interface{}) bool { - - switch tag = strings.ToLower(tag); tag { - case Repeat: - return gradient.setBoolProperty(tag, value) - - case Gradient: - switch value := value.(type) { - case string: - if value != "" { - elements := strings.Split(value, `,`) - if count := len(elements); count > 1 { - points := make([]interface{}, count) - for i, element := range elements { - if strings.Contains(element, "@") { - points[i] = element - } else { - var point BackgroundGradientPoint - if point.setValue(element) { - points[i] = point - } else { - ErrorLogF("Invalid gradient element #%d: %s", i, element) - return false - } - } - } - gradient.properties[Gradient] = points - return true - } - - text := strings.Trim(value, " \n\r\t") - if text[0] == '@' { - gradient.properties[Gradient] = text - return true - } - } - - case []BackgroundGradientPoint: - if len(value) >= 2 { - gradient.properties[Gradient] = value - return true - } - - case []Color: - count := len(value) - if count >= 2 { - points := make([]BackgroundGradientPoint, count) - for i, color := range value { - points[i].Color = color - points[i].Pos = AutoSize() - } - gradient.properties[Gradient] = points - return true - } - - case []GradientPoint: - count := len(value) - if count >= 2 { - points := make([]BackgroundGradientPoint, count) - for i, point := range value { - points[i].Color = point.Color - points[i].Pos = Percent(point.Offset * 100) - } - gradient.properties[Gradient] = points - return true - } - - case []interface{}: - if count := len(value); count > 1 { - points := make([]interface{}, count) - for i, element := range value { - switch element := element.(type) { - case string: - if strings.Contains(element, "@") { - points[i] = element - } else { - var point BackgroundGradientPoint - if !point.setValue(element) { - ErrorLogF("Invalid gradient element #%d: %s", i, element) - return false - } - points[i] = point - } - - case BackgroundGradientPoint: - points[i] = element - - case GradientPoint: - points[i] = BackgroundGradientPoint{Color: element.Color, Pos: Percent(element.Offset * 100)} - - case Color: - points[i] = BackgroundGradientPoint{Color: element, Pos: AutoSize()} - - default: - ErrorLogF("Invalid gradient element #%d: %v", i, element) - return false - } - } - gradient.properties[Gradient] = points - return true - } - } - - default: - ErrorLogF("Invalid gradient %v", value) - return false - } - - return gradient.backgroundElement.Set(tag, value) -} - -func (point *BackgroundGradientPoint) setValue(value string) bool { - var ok bool - - switch elements := strings.Split(value, `:`); len(elements) { - case 2: - if point.Color, ok = StringToColor(elements[0]); !ok { - return false - } - if point.Pos, ok = StringToSizeUnit(elements[1]); !ok { - return false - } - - case 1: - if point.Color, ok = StringToColor(elements[0]); !ok { - return false - } - point.Pos = AutoSize() - - default: - return false - } - - return false -} - -func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool { - - value, ok := gradient.properties[Gradient] - if !ok { - return false - } - - points := []BackgroundGradientPoint{} - - switch value := value.(type) { - case string: - if text, ok := session.resolveConstants(value); ok && text != "" { - elements := strings.Split(text, `,`) - points := make([]BackgroundGradientPoint, len(elements)) - for i, element := range elements { - if !points[i].setValue(element) { - ErrorLogF(`Invalid gradient point #%d: "%s"`, i, element) - return false - } - } - } else { - ErrorLog(`Invalid gradient: ` + value) - return false - } - - case []BackgroundGradientPoint: - points = value - - case []interface{}: - points = make([]BackgroundGradientPoint, len(value)) - for i, element := range value { - switch element := element.(type) { - case string: - if text, ok := session.resolveConstants(element); ok && text != "" { - if !points[i].setValue(text) { - ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text) - return false - } - } else { - ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text) - return false - } - - case BackgroundGradientPoint: - points[i] = element - } - } - } - - if len(points) > 0 { - for i, point := range points { - if i > 0 { - buffer.WriteString(`, `) - } - - buffer.WriteString(point.Color.cssString()) - if point.Pos.Type != Auto { - buffer.WriteRune(' ') - buffer.WriteString(point.Pos.cssString("")) - } - } - return true - } - - return false -} - -func (gradient *backgroundLinearGradient) Tag() string { - return "linear-gradient" -} - -func (gradient *backgroundLinearGradient) Set(tag string, value interface{}) bool { - if tag == Direction { - switch value := value.(type) { - case AngleUnit: - gradient.properties[Direction] = value - return true - - case string: - var angle AngleUnit - if ok, _ := angle.setValue(value); ok { - gradient.properties[Direction] = angle - return true - } - } - return gradient.setEnumProperty(tag, value, enumProperties[Direction].values) - } - - return gradient.backgroundGradient.Set(tag, value) -} - -func (gradient *backgroundLinearGradient) cssStyle(session Session) string { - buffer := allocStringBuilder() - defer freeStringBuilder(buffer) - - if repeating, _ := boolProperty(gradient, Repeating, session); repeating { - buffer.WriteString(`repeating-linear-gradient(`) - } else { - buffer.WriteString(`linear-gradient(`) - } - - if value, ok := gradient.properties[Direction]; ok { - switch value := value.(type) { - case string: - if text, ok := session.resolveConstants(value); ok { - direction := enumProperties[Direction] - if n, ok := enumStringToInt(text, direction.values, false); ok { - buffer.WriteString(direction.cssValues[n]) - buffer.WriteString(", ") - } else { - if angle, ok := StringToAngleUnit(text); ok { - buffer.WriteString(angle.cssString()) - buffer.WriteString(", ") - } else { - ErrorLog(`Invalid linear gradient direction: ` + text) - } - } - } else { - ErrorLog(`Invalid linear gradient direction: ` + value) - } - - case int: - values := enumProperties[Direction].cssValues - if value >= 0 && value < len(values) { - buffer.WriteString(values[value]) - buffer.WriteString(", ") - } else { - ErrorLogF(`Invalid linear gradient direction: %d`, value) - } - - case AngleUnit: - buffer.WriteString(value.cssString()) - buffer.WriteString(", ") - } - } - - if !gradient.writeGradient(session, buffer) { - return "" - } - - buffer.WriteString(") ") - return buffer.String() -} - -func (gradient *backgroundRadialGradient) Tag() string { - return "radial-gradient" -} - -func (gradient *backgroundRadialGradient) normalizeTag(tag string) string { - tag = strings.ToLower(tag) - switch tag { - case Radius: - tag = RadialGradientRadius - - case Shape: - tag = RadialGradientShape - - case "x-center": - tag = CenterX - - case "y-center": - tag = CenterY - } - - return tag -} - -func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) bool { - tag = gradient.normalizeTag(tag) - switch tag { - case RadialGradientRadius: - switch value := value.(type) { - case string, SizeUnit: - return gradient.propertyList.Set(RadialGradientRadius, value) - - case int: - n := value - if n >= 0 && n < len(enumProperties[RadialGradientRadius].values) { - return gradient.propertyList.Set(RadialGradientRadius, value) - } - } - ErrorLogF(`Invalid value of "%s" property: %v`, tag, value) - - case RadialGradientShape: - return gradient.propertyList.Set(RadialGradientShape, value) - - case CenterX, CenterY: - return gradient.propertyList.Set(tag, value) - } - - return gradient.backgroundGradient.Set(tag, value) -} - -func (gradient *backgroundRadialGradient) Get(tag string) interface{} { - return gradient.backgroundGradient.Get(gradient.normalizeTag(tag)) -} - -func (gradient *backgroundRadialGradient) cssStyle(session Session) string { - buffer := allocStringBuilder() - defer freeStringBuilder(buffer) - - if repeating, _ := boolProperty(gradient, Repeating, session); repeating { - buffer.WriteString(`repeating-radial-gradient(`) - } else { - buffer.WriteString(`radial-gradient(`) - } - - if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient { - buffer.WriteString(`circle `) - } else { - buffer.WriteString(`ellipse `) - } - - if value, ok := gradient.properties[RadialGradientRadius]; ok { - switch value := value.(type) { - case string: - if text, ok := session.resolveConstants(value); ok { - values := enumProperties[RadialGradientRadius] - if n, ok := enumStringToInt(text, values.values, false); ok { - buffer.WriteString(values.cssValues[n]) - buffer.WriteString(" ") - } else { - if r, ok := StringToSizeUnit(text); ok && r.Type != Auto { - buffer.WriteString(r.cssString("")) - buffer.WriteString(" ") - } else { - ErrorLog(`Invalid linear gradient radius: ` + text) - } - } - } else { - ErrorLog(`Invalid linear gradient radius: ` + value) - } - - case int: - values := enumProperties[RadialGradientRadius].cssValues - if value >= 0 && value < len(values) { - buffer.WriteString(values[value]) - buffer.WriteString(" ") - } else { - ErrorLogF(`Invalid linear gradient radius: %d`, value) - } - - case SizeUnit: - if value.Type != Auto { - buffer.WriteString(value.cssString("")) - buffer.WriteString(" ") - } - } - } - - x, _ := sizeProperty(gradient, CenterX, session) - y, _ := sizeProperty(gradient, CenterX, session) - if x.Type != Auto || y.Type != Auto { - buffer.WriteString("at ") - buffer.WriteString(x.cssString("50%")) - buffer.WriteString(" ") - buffer.WriteString(y.cssString("50%")) - } - - buffer.WriteString(", ") - if !gradient.writeGradient(session, buffer) { - return "" - } - - buffer.WriteString(") ") - - return buffer.String() -} diff --git a/backgroundConicGradient.go b/backgroundConicGradient.go new file mode 100644 index 0000000..0e1ba4b --- /dev/null +++ b/backgroundConicGradient.go @@ -0,0 +1,416 @@ +package rui + +import ( + "strings" +) + +type backgroundConicGradient struct { + backgroundElement +} + +// BackgroundGradientAngle defined an element of the conic gradient +type BackgroundGradientAngle struct { + // Color - the color of the key angle. Must not be nil. + // Can take a value of Color type or string (color constant or textual description of the color) + Color interface{} + // Angle - the key angle. Optional (may be nil). + // Can take a value of AngleUnit type or string (angle constant or textual description of the angle) + Angle interface{} +} + +// NewBackgroundConicGradient creates the new background conic gradient +func NewBackgroundConicGradient(params Params) BackgroundElement { + result := new(backgroundConicGradient) + result.properties = map[string]interface{}{} + for tag, value := range params { + result.Set(tag, value) + } + return result +} + +func (point *BackgroundGradientAngle) String() string { + result := "black" + if point.Color != nil { + switch color := point.Color.(type) { + case string: + result = color + + case Color: + result = color.String() + } + } + + if point.Angle != nil { + switch value := point.Angle.(type) { + case string: + result += " " + value + + case AngleUnit: + result += " " + value.String() + + } + } + + return result +} + +func (point *BackgroundGradientAngle) color(session Session) (Color, bool) { + if point.Color != nil { + switch color := point.Color.(type) { + case string: + if color != "" { + if color[0] == '@' { + if clr, ok := session.Color(color[1:]); ok { + return clr, true + } + } else { + if clr, ok := StringToColor(color); ok { + return clr, true + } + } + } + + case Color: + return color, true + } + } + return 0, false +} + +func (point *BackgroundGradientAngle) isValid(session Session) bool { + _, ok := point.color(session) + return ok +} + +func (point *BackgroundGradientAngle) cssString(session Session, buffer *strings.Builder) { + if color, ok := point.color(session); ok { + buffer.WriteString(color.cssString()) + } else { + return + } + + if point.Angle != nil { + switch value := point.Angle.(type) { + case string: + if value != "" { + if value[0] == '@' { + if val, ok := session.Constant(value[1:]); ok { + value = val + } else { + return + } + } + + if angle, ok := StringToAngleUnit(value); ok { + buffer.WriteRune(' ') + buffer.WriteString(angle.cssString()) + } + } + + case AngleUnit: + buffer.WriteRune(' ') + buffer.WriteString(value.cssString()) + } + } +} + +func (gradient *backgroundConicGradient) Tag() string { + return "conic-gradient" +} + +func (image *backgroundConicGradient) Clone() BackgroundElement { + result := NewBackgroundConicGradient(nil) + for tag, value := range image.properties { + result.setRaw(tag, value) + } + return result +} + +func (gradient *backgroundConicGradient) normalizeTag(tag string) string { + tag = strings.ToLower(tag) + switch tag { + case "x-center": + tag = CenterX + + case "y-center": + tag = CenterY + } + + return tag +} + +func (gradient *backgroundConicGradient) Set(tag string, value interface{}) bool { + tag = gradient.normalizeTag(tag) + switch tag { + case CenterX, CenterY, Repeating, From: + return gradient.propertyList.Set(tag, value) + + case Gradient: + return gradient.setGradient(value) + } + + ErrorLogF(`"%s" property is not supported by BackgroundConicGradient`, tag) + return false +} + +func (gradient *backgroundConicGradient) stringToAngle(text string) (interface{}, bool) { + if text == "" { + return nil, false + } else if text[0] == '@' { + return text, true + } + return StringToAngleUnit(text) +} + +func (gradient *backgroundConicGradient) stringToGradientPoint(text string) (BackgroundGradientAngle, bool) { + var result BackgroundGradientAngle + colorText := "" + pointText := "" + + if index := strings.Index(text, " "); index > 0 { + colorText = text[:index] + pointText = strings.Trim(text[index+1:], " ") + } else { + colorText = text + } + + if colorText == "" { + return result, false + } + + if colorText[0] == '@' { + result.Color = colorText + } else if color, ok := StringToColor(colorText); ok { + result.Color = color + } else { + return result, false + } + + if pointText != "" { + if angle, ok := gradient.stringToAngle(pointText); ok { + result.Angle = angle + } else { + return result, false + } + } + + return result, true +} + +func (gradient *backgroundConicGradient) parseGradientText(value string) []BackgroundGradientAngle { + elements := strings.Split(value, ",") + count := len(elements) + if count < 2 { + ErrorLog("The gradient must contain at least 2 points") + return nil + } + + vector := make([]BackgroundGradientAngle, count) + for i, element := range elements { + var ok bool + if vector[i], ok = gradient.stringToGradientPoint(strings.Trim(element, " ")); !ok { + ErrorLogF(`Ivalid %d element of the conic gradient: "%s"`, i, element) + return nil + } + } + return vector +} + +func (gradient *backgroundConicGradient) setGradient(value interface{}) bool { + if value == nil { + delete(gradient.properties, Gradient) + return true + } + + switch value := value.(type) { + case string: + if value == "" { + delete(gradient.properties, Gradient) + return true + } + + if strings.Contains(value, ",") || strings.Contains(value, " ") { + if vector := gradient.parseGradientText(value); vector != nil { + gradient.properties[Gradient] = vector + return true + } + return false + } else if value[0] == '@' { + gradient.properties[Gradient] = value + return true + } + + ErrorLogF(`Ivalid conic gradient: "%s"`, value) + return false + + case []interface{}: + count := len(value) + if count < 2 { + ErrorLog("The gradient must contain at least 2 points") + return false + } + vector := make([]BackgroundGradientAngle, len(value)) + for i, point := range value { + if point == nil { + ErrorLogF("Ivalid %d element of the conic gradient: nil", i) + return false + } + + switch element := point.(type) { + case string: + if data, ok := gradient.stringToGradientPoint(element); ok { + vector[i] = data + } else { + ErrorLogF("Ivalid %d element of the conic gradient: %s", i, element) + return false + } + + case Color: + vector[i].Color = element + + case BackgroundGradientAngle: + if element.Color == nil { + ErrorLogF("Ivalid %d element of the conic gradient: Color is nil", i) + return false + } + switch color := element.Color.(type) { + case string: + if color == "" { + ErrorLogF("Ivalid %d element of the conic gradient: empty Color text", i) + return false + } + if color[0] != '@' { + if clr, ok := StringToColor(color); ok { + element.Color = clr + } else { + ErrorLogF("Ivalid %d element of the conic gradient: invalid Color text", i) + return false + } + } + + case Color: + // do nothing + + default: + ErrorLogF("Ivalid %d element of the conic gradient: unsupported Color type", i) + return false + } + + if element.Angle != nil { + switch point := element.Angle.(type) { + case string: + if angle, ok := gradient.stringToAngle(point); ok { + element.Angle = angle + } else { + ErrorLogF("Ivalid %d element of the conic gradient: invalid Point text", i) + return false + } + + case AngleUnit: + // do nothing + + default: + ErrorLogF("Ivalid %d element of the conic gradient: unsupported Point type", i) + return false + } + } + + vector[i] = element + } + } + gradient.properties[Gradient] = vector + return true + + case []BackgroundGradientAngle: + count := len(value) + if count < 2 { + ErrorLog("The gradient must contain at least 2 points") + return false + } + + for i, point := range value { + if point.Color == nil { + ErrorLogF("Ivalid %d element of the conic gradient: Color is nil", i) + return false + } + } + gradient.properties[Gradient] = value + return true + } + return false +} + +func (gradient *backgroundConicGradient) Get(tag string) interface{} { + return gradient.backgroundElement.Get(gradient.normalizeTag(tag)) +} + +func (gradient *backgroundConicGradient) cssStyle(session Session) string { + + points := []BackgroundGradientAngle{} + if value, ok := gradient.properties[Gradient]; ok { + switch value := value.(type) { + case string: + if text, ok := session.resolveConstants(value); ok && text != "" { + if points = gradient.parseGradientText(text); points == nil { + return "" + } + } else { + ErrorLog(`Invalid conic gradient: ` + value) + return "" + } + + case []BackgroundGradientAngle: + points = value + } + } else { + return "" + } + + if len(points) < 2 { + ErrorLog("The gradient must contain at least 2 points") + return "" + } + + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + if repeating, _ := boolProperty(gradient, Repeating, session); repeating { + buffer.WriteString(`repeating-conic-gradient(`) + } else { + buffer.WriteString(`conic-gradient(`) + } + + comma := false + if angle, ok := angleProperty(gradient, From, session); ok { + buffer.WriteString("from ") + buffer.WriteString(angle.cssString()) + comma = true + } + + x, _ := sizeProperty(gradient, CenterX, session) + y, _ := sizeProperty(gradient, CenterX, session) + if x.Type != Auto || y.Type != Auto { + if comma { + buffer.WriteRune(' ') + } + buffer.WriteString("at ") + buffer.WriteString(x.cssString("50%")) + buffer.WriteString(" ") + buffer.WriteString(y.cssString("50%")) + comma = true + } + + for _, point := range points { + if point.isValid(session) { + if comma { + buffer.WriteString(`, `) + } + point.cssString(session, buffer) + comma = true + } + } + + buffer.WriteString(") ") + + return buffer.String() +} diff --git a/backgroundGradient.go b/backgroundGradient.go new file mode 100644 index 0000000..d3ff253 --- /dev/null +++ b/backgroundGradient.go @@ -0,0 +1,508 @@ +package rui + +import "strings" + +const ( + + // ToTopGradient is value of the Direction property of a linear gradient. The value is equivalent to the 0deg angle + ToTopGradient = 0 + // ToRightTopGradient is value of the Direction property of a linear gradient. + ToRightTopGradient = 1 + // ToRightGradient is value of the Direction property of a linear gradient. The value is equivalent to the 90deg angle + ToRightGradient = 2 + // ToRightBottomGradient is value of the Direction property of a linear gradient. + ToRightBottomGradient = 3 + // ToBottomGradient is value of the Direction property of a linear gradient. The value is equivalent to the 180deg angle + ToBottomGradient = 4 + // ToLeftBottomGradient is value of the Direction property of a linear gradient. + ToLeftBottomGradient = 5 + // ToLeftGradient is value of the Direction property of a linear gradient. The value is equivalent to the 270deg angle + ToLeftGradient = 6 + // ToLeftTopGradient is value of the Direction property of a linear gradient. + ToLeftTopGradient = 7 + + // EllipseGradient is value of the Shape property of a radial gradient background: + // the shape is an axis-aligned ellipse + EllipseGradient = 0 + // CircleGradient is value of the Shape property of a radial gradient background: + // the gradient's shape is a circle with constant radius + CircleGradient = 1 + + // ClosestSideGradient is value of the Radius property of a radial gradient background: + // The gradient's ending shape meets the side of the box closest to its center (for circles) + // or meets both the vertical and horizontal sides closest to the center (for ellipses). + ClosestSideGradient = 0 + // ClosestCornerGradient is value of the Radius property of a radial gradient background: + // The gradient's ending shape is sized so that it exactly meets the closest corner + // of the box from its center. + ClosestCornerGradient = 1 + // FarthestSideGradient is value of the Radius property of a radial gradient background: + // Similar to closest-side, except the ending shape is sized to meet the side of the box + // farthest from its center (or vertical and horizontal sides). + FarthestSideGradient = 2 + // FarthestCornerGradient is value of the Radius property of a radial gradient background: + // The default value, the gradient's ending shape is sized so that it exactly meets + // the farthest corner of the box from its center. + FarthestCornerGradient = 3 +) + +// BackgroundGradientPoint define point on gradient straight line +type BackgroundGradientPoint struct { + // Pos - the distance from the start of the gradient straight line + Pos SizeUnit + // Color - the color of the point + Color Color +} + +type backgroundGradient struct { + backgroundElement +} + +type backgroundLinearGradient struct { + backgroundGradient +} + +type backgroundRadialGradient struct { + backgroundGradient +} + +// NewBackgroundLinearGradient creates the new background linear gradient +func NewBackgroundLinearGradient(params Params) BackgroundElement { + result := new(backgroundLinearGradient) + result.properties = map[string]interface{}{} + for tag, value := range params { + result.Set(tag, value) + } + return result +} + +// NewBackgroundRadialGradient creates the new background radial gradient +func NewBackgroundRadialGradient(params Params) BackgroundElement { + result := new(backgroundRadialGradient) + result.properties = map[string]interface{}{} + for tag, value := range params { + result.Set(tag, value) + } + return result +} + +func (gradient *backgroundGradient) Set(tag string, value interface{}) bool { + + switch tag = strings.ToLower(tag); tag { + case Repeating: + return gradient.setBoolProperty(tag, value) + + case Gradient: + switch value := value.(type) { + case string: + if value != "" { + elements := strings.Split(value, `,`) + if count := len(elements); count > 1 { + points := make([]interface{}, count) + for i, element := range elements { + if strings.Contains(element, "@") { + points[i] = element + } else { + var point BackgroundGradientPoint + if point.setValue(element) { + points[i] = point + } else { + ErrorLogF("Invalid gradient element #%d: %s", i, element) + return false + } + } + } + gradient.properties[Gradient] = points + return true + } + + text := strings.Trim(value, " \n\r\t") + if text[0] == '@' { + gradient.properties[Gradient] = text + return true + } + } + + case []BackgroundGradientPoint: + if len(value) >= 2 { + gradient.properties[Gradient] = value + return true + } + + case []Color: + count := len(value) + if count >= 2 { + points := make([]BackgroundGradientPoint, count) + for i, color := range value { + points[i].Color = color + points[i].Pos = AutoSize() + } + gradient.properties[Gradient] = points + return true + } + + case []GradientPoint: + count := len(value) + if count >= 2 { + points := make([]BackgroundGradientPoint, count) + for i, point := range value { + points[i].Color = point.Color + points[i].Pos = Percent(point.Offset * 100) + } + gradient.properties[Gradient] = points + return true + } + + case []interface{}: + if count := len(value); count > 1 { + points := make([]interface{}, count) + for i, element := range value { + switch element := element.(type) { + case string: + if strings.Contains(element, "@") { + points[i] = element + } else { + var point BackgroundGradientPoint + if !point.setValue(element) { + ErrorLogF("Invalid gradient element #%d: %s", i, element) + return false + } + points[i] = point + } + + case BackgroundGradientPoint: + points[i] = element + + case GradientPoint: + points[i] = BackgroundGradientPoint{Color: element.Color, Pos: Percent(element.Offset * 100)} + + case Color: + points[i] = BackgroundGradientPoint{Color: element, Pos: AutoSize()} + + default: + ErrorLogF("Invalid gradient element #%d: %v", i, element) + return false + } + } + gradient.properties[Gradient] = points + return true + } + + default: + ErrorLogF("Invalid gradient %v", value) + return false + } + } + + return gradient.backgroundElement.Set(tag, value) +} + +func (point *BackgroundGradientPoint) setValue(value string) bool { + var ok bool + + switch elements := strings.Split(value, `:`); len(elements) { + case 2: + if point.Color, ok = StringToColor(elements[0]); !ok { + return false + } + if point.Pos, ok = StringToSizeUnit(elements[1]); !ok { + return false + } + + case 1: + if point.Color, ok = StringToColor(elements[0]); !ok { + return false + } + point.Pos = AutoSize() + + default: + return false + } + + return false +} + +func (gradient *backgroundGradient) writeGradient(session Session, buffer *strings.Builder) bool { + + value, ok := gradient.properties[Gradient] + if !ok { + return false + } + + points := []BackgroundGradientPoint{} + + switch value := value.(type) { + case string: + if text, ok := session.resolveConstants(value); ok && text != "" { + elements := strings.Split(text, `,`) + points := make([]BackgroundGradientPoint, len(elements)) + for i, element := range elements { + if !points[i].setValue(element) { + ErrorLogF(`Invalid gradient point #%d: "%s"`, i, element) + return false + } + } + } else { + ErrorLog(`Invalid gradient: ` + value) + return false + } + + case []BackgroundGradientPoint: + points = value + + case []interface{}: + points = make([]BackgroundGradientPoint, len(value)) + for i, element := range value { + switch element := element.(type) { + case string: + if text, ok := session.resolveConstants(element); ok && text != "" { + if !points[i].setValue(text) { + ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text) + return false + } + } else { + ErrorLogF(`Invalid gradient point #%d: "%s"`, i, text) + return false + } + + case BackgroundGradientPoint: + points[i] = element + } + } + } + + if len(points) > 0 { + for i, point := range points { + if i > 0 { + buffer.WriteString(`, `) + } + + buffer.WriteString(point.Color.cssString()) + if point.Pos.Type != Auto { + buffer.WriteRune(' ') + buffer.WriteString(point.Pos.cssString("")) + } + } + return true + } + + return false +} + +func (gradient *backgroundLinearGradient) Tag() string { + return "linear-gradient" +} + +func (image *backgroundLinearGradient) Clone() BackgroundElement { + result := NewBackgroundLinearGradient(nil) + for tag, value := range image.properties { + result.setRaw(tag, value) + } + return result +} + +func (gradient *backgroundLinearGradient) Set(tag string, value interface{}) bool { + if strings.ToLower(tag) == Direction { + switch value := value.(type) { + case AngleUnit: + gradient.properties[Direction] = value + return true + + case string: + var angle AngleUnit + if ok, _ := angle.setValue(value); ok { + gradient.properties[Direction] = angle + return true + } + } + return gradient.setEnumProperty(tag, value, enumProperties[Direction].values) + } + + return gradient.backgroundGradient.Set(tag, value) +} + +func (gradient *backgroundLinearGradient) cssStyle(session Session) string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + if repeating, _ := boolProperty(gradient, Repeating, session); repeating { + buffer.WriteString(`repeating-linear-gradient(`) + } else { + buffer.WriteString(`linear-gradient(`) + } + + if value, ok := gradient.properties[Direction]; ok { + switch value := value.(type) { + case string: + if text, ok := session.resolveConstants(value); ok { + direction := enumProperties[Direction] + if n, ok := enumStringToInt(text, direction.values, false); ok { + buffer.WriteString(direction.cssValues[n]) + buffer.WriteString(", ") + } else { + if angle, ok := StringToAngleUnit(text); ok { + buffer.WriteString(angle.cssString()) + buffer.WriteString(", ") + } else { + ErrorLog(`Invalid linear gradient direction: ` + text) + } + } + } else { + ErrorLog(`Invalid linear gradient direction: ` + value) + } + + case int: + values := enumProperties[Direction].cssValues + if value >= 0 && value < len(values) { + buffer.WriteString(values[value]) + buffer.WriteString(", ") + } else { + ErrorLogF(`Invalid linear gradient direction: %d`, value) + } + + case AngleUnit: + buffer.WriteString(value.cssString()) + buffer.WriteString(", ") + } + } + + if !gradient.writeGradient(session, buffer) { + return "" + } + + buffer.WriteString(") ") + return buffer.String() +} + +func (gradient *backgroundRadialGradient) Tag() string { + return "radial-gradient" +} + +func (image *backgroundRadialGradient) Clone() BackgroundElement { + result := NewBackgroundRadialGradient(nil) + for tag, value := range image.properties { + result.setRaw(tag, value) + } + return result +} + +func (gradient *backgroundRadialGradient) normalizeTag(tag string) string { + tag = strings.ToLower(tag) + switch tag { + case Radius: + tag = RadialGradientRadius + + case Shape: + tag = RadialGradientShape + + case "x-center": + tag = CenterX + + case "y-center": + tag = CenterY + } + + return tag +} + +func (gradient *backgroundRadialGradient) Set(tag string, value interface{}) bool { + tag = gradient.normalizeTag(tag) + switch tag { + case RadialGradientRadius: + switch value := value.(type) { + case string, SizeUnit: + return gradient.propertyList.Set(RadialGradientRadius, value) + + case int: + n := value + if n >= 0 && n < len(enumProperties[RadialGradientRadius].values) { + return gradient.propertyList.Set(RadialGradientRadius, value) + } + } + ErrorLogF(`Invalid value of "%s" property: %v`, tag, value) + + case RadialGradientShape: + return gradient.propertyList.Set(RadialGradientShape, value) + + case CenterX, CenterY: + return gradient.propertyList.Set(tag, value) + } + + return gradient.backgroundGradient.Set(tag, value) +} + +func (gradient *backgroundRadialGradient) Get(tag string) interface{} { + return gradient.backgroundGradient.Get(gradient.normalizeTag(tag)) +} + +func (gradient *backgroundRadialGradient) cssStyle(session Session) string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + if repeating, _ := boolProperty(gradient, Repeating, session); repeating { + buffer.WriteString(`repeating-radial-gradient(`) + } else { + buffer.WriteString(`radial-gradient(`) + } + + if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient { + buffer.WriteString(`circle `) + } else { + buffer.WriteString(`ellipse `) + } + + if value, ok := gradient.properties[RadialGradientRadius]; ok { + switch value := value.(type) { + case string: + if text, ok := session.resolveConstants(value); ok { + values := enumProperties[RadialGradientRadius] + if n, ok := enumStringToInt(text, values.values, false); ok { + buffer.WriteString(values.cssValues[n]) + buffer.WriteString(" ") + } else { + if r, ok := StringToSizeUnit(text); ok && r.Type != Auto { + buffer.WriteString(r.cssString("")) + buffer.WriteString(" ") + } else { + ErrorLog(`Invalid radial gradient radius: ` + text) + } + } + } else { + ErrorLog(`Invalid radial gradient radius: ` + value) + } + + case int: + values := enumProperties[RadialGradientRadius].cssValues + if value >= 0 && value < len(values) { + buffer.WriteString(values[value]) + buffer.WriteString(" ") + } else { + ErrorLogF(`Invalid radial gradient radius: %d`, value) + } + + case SizeUnit: + if value.Type != Auto { + buffer.WriteString(value.cssString("")) + buffer.WriteString(" ") + } + } + } + + x, _ := sizeProperty(gradient, CenterX, session) + y, _ := sizeProperty(gradient, CenterX, session) + if x.Type != Auto || y.Type != Auto { + buffer.WriteString("at ") + buffer.WriteString(x.cssString("50%")) + buffer.WriteString(" ") + buffer.WriteString(y.cssString("50%")) + } + + buffer.WriteString(", ") + if !gradient.writeGradient(session, buffer) { + return "" + } + + buffer.WriteString(") ") + + return buffer.String() +} diff --git a/propertyNames.go b/propertyNames.go index 166ed3b..a8797d4 100644 --- a/propertyNames.go +++ b/propertyNames.go @@ -359,6 +359,8 @@ const ( Direction = "direction" // Repeating is the constant for the "repeating" property tag. Repeating = "repeating" + // Repeating is the constant for the "repeating" property tag. + From = "from" // RadialGradientRadius is the constant for the "radial-gradient-radius" property tag. RadialGradientRadius = "radial-gradient-radius" // RadialGradientShape is the constant for the "radial-gradient-shape" property tag. diff --git a/propertySet.go b/propertySet.go index e8ff8f0..ef98002 100644 --- a/propertySet.go +++ b/propertySet.go @@ -33,6 +33,7 @@ var angleProperties = []string{ Rotate, SkewX, SkewY, + From, } var boolProperties = []string{ @@ -58,6 +59,7 @@ var boolProperties = []string{ AnimationPaused, Multiple, TabCloseButton, + Repeating, } var intProperties = []string{