forked from mbk-lab/rui_orig
2
0
Fork 0

Added the background conic gradient

This commit is contained in:
Alexei Anoshenko 2022-04-30 12:13:16 +03:00
parent 371079e27b
commit 86271a3c6e
7 changed files with 1038 additions and 506 deletions

View File

@ -991,12 +991,12 @@ RadiusProperty, а не структура BoxRadius. Получить стру
или одно из следующих значений типа Int:
| Значение | Константа | Имя | Описание |
|:--------:|-----------------------|-------------------|------------------------------------------------|
| 0 | ToTopGradient | "to-top" | Линия идет снизу вверх (значение по умолчанию) |
|:--------:|-----------------------|-------------------|-----------------------------------------|
| 0 | ToTopGradient | "to-top" | Линия идет снизу вверх |
| 1 | ToRightTopGradient | "to-right-top" | Из левого нижнего угла в правый верхний |
| 2 | ToRightGradient | "to-right" | Слева направо |
| 3 | ToRightBottomGradient | "to-right-bottom" | Из левого верхнего угла в правый нижний |
| 4 | ToBottomGradient | "to-bottom" | Сверху вних |
| 4 | ToBottomGradient | "to-bottom" | Сверху вниз (значение по умолчанию) |
| 5 | ToLeftBottomGradient | "to-left-bottom" | Из правого верхнего угла в левый нижний |
| 6 | ToLeftGradient | "to-left" | Справа налево |
| 7 | ToLeftTopGradient | "to-left-top" | Из правого нижнего угла в левый верхний |
@ -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 = <значение>]}
#### Изображение
Изображение имеет следующие параметры:

View File

@ -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 = <Value> [, repeat = <Value>] [, shape = <Value>]
[, radius = <Value>][, center-x = <Value>][, center-y = <Value>]}
#### 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 = <Value> [, repeating = <Value>] [, from = <Value>]
[, center-x = <Value>][, center-y = <Value>]}
#### Image
The image has the following parameters:

View File

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

416
backgroundConicGradient.go Normal file
View File

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

508
backgroundGradient.go Normal file
View File

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

View File

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

View File

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