From f81ffe6bed2379125403554d321078a803521ed4 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:42:38 +0300 Subject: [PATCH] Refactoring --- background.go | 192 ----------- backgroundImage.go | 198 +++++++++++ ...Gradient.go => backgroundLinearGradient.go | 304 +---------------- backgroundRadialGradient.go | 307 ++++++++++++++++++ 4 files changed, 512 insertions(+), 489 deletions(-) create mode 100644 backgroundImage.go rename backgroundGradient.go => backgroundLinearGradient.go (53%) create mode 100644 backgroundRadialGradient.go diff --git a/background.go b/background.go index 44a99da..68e6b8f 100644 --- a/background.go +++ b/background.go @@ -2,62 +2,9 @@ package rui import ( "fmt" - "strings" ) -// Constants related to view's background description const ( - // NoRepeat is value of the Repeat property of an background image: - // The image is not repeated (and hence the background image painting area - // will not necessarily be entirely covered). The position of the non-repeated - // background image is defined by the background-position CSS property. - NoRepeat = 0 - - // RepeatXY is value of the Repeat property of an background image: - // The image is repeated as much as needed to cover the whole background - // image painting area. The last image will be clipped if it doesn't fit. - RepeatXY = 1 - - // RepeatX is value of the Repeat property of an background image: - // The image is repeated horizontally as much as needed to cover - // the whole width background image painting area. The image is not repeated vertically. - // The last image will be clipped if it doesn't fit. - RepeatX = 2 - - // RepeatY is value of the Repeat property of an background image: - // The image is repeated vertically as much as needed to cover - // the whole height background image painting area. The image is not repeated horizontally. - // The last image will be clipped if it doesn't fit. - RepeatY = 3 - - // RepeatRound is value of the Repeat property of an background image: - // As the allowed space increases in size, the repeated images will stretch (leaving no gaps) - // until there is room (space left >= half of the image width) for another one to be added. - // When the next image is added, all of the current ones compress to allow room. - RepeatRound = 4 - - // RepeatSpace is value of the Repeat property of an background image: - // The image is repeated as much as possible without clipping. The first and last images - // are pinned to either side of the element, and whitespace is distributed evenly between the images. - RepeatSpace = 5 - - // ScrollAttachment is value of the Attachment property of an background image: - // The background is fixed relative to the element itself and does not scroll with its contents. - // (It is effectively attached to the element's border.) - ScrollAttachment = 0 - - // FixedAttachment is value of the Attachment property of an background image: - // The background is fixed relative to the viewport. Even if an element has - // a scrolling mechanism, the background doesn't move with the element. - FixedAttachment = 1 - - // LocalAttachment is value of the Attachment property of an background image: - // The background is fixed relative to the element's contents. If the element has a scrolling mechanism, - // the background scrolls with the element's contents, and the background painting area - // and background positioning area are relative to the scrollable area of the element - // rather than to the border framing them. - LocalAttachment = 2 - // BorderBox is the value of the following properties: // // * BackgroundClip - The background extends to the outside edge of the border (but underneath the border in z-ordering). @@ -111,10 +58,6 @@ type backgroundElement struct { dataProperty } -type backgroundImage struct { - backgroundElement -} - // NewBackgroundImage creates the new background image func createBackground(obj DataObject) BackgroundElement { var result BackgroundElement = nil @@ -148,141 +91,6 @@ func createBackground(obj DataObject) BackgroundElement { return result } -// NewBackgroundImage creates the new background image -func NewBackgroundImage(params Params) BackgroundElement { - result := new(backgroundImage) - result.init() - for tag, value := range params { - result.Set(tag, value) - } - return result -} - -func (image *backgroundImage) init() { - image.backgroundElement.init() - image.normalize = normalizeBackgroundImageTag - image.supportedProperties = []PropertyName{ - Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign, backgroundFit, Source, - } -} - -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 normalizeBackgroundImageTag(tag PropertyName) PropertyName { - tag = defaultNormalize(tag) - switch tag { - case "source": - tag = Source - - case Fit: - tag = backgroundFit - - case HorizontalAlign: - tag = ImageHorizontalAlign - - case VerticalAlign: - tag = ImageVerticalAlign - } - - return tag -} - -func (image *backgroundImage) cssStyle(session Session) string { - if src, ok := imageProperty(image, Source, session); ok && src != "" { - buffer := allocStringBuilder() - defer freeStringBuilder(buffer) - - buffer.WriteString(`url(`) - buffer.WriteString(src) - buffer.WriteRune(')') - - attachment, _ := enumProperty(image, Attachment, session, NoRepeat) - values := enumProperties[Attachment].values - if attachment > 0 && attachment < len(values) { - buffer.WriteRune(' ') - buffer.WriteString(values[attachment]) - } - - align, _ := enumProperty(image, ImageHorizontalAlign, session, LeftAlign) - values = enumProperties[ImageHorizontalAlign].values - if align >= 0 && align < len(values) { - buffer.WriteRune(' ') - buffer.WriteString(values[align]) - } else { - buffer.WriteString(` left`) - } - - align, _ = enumProperty(image, ImageVerticalAlign, session, TopAlign) - values = enumProperties[ImageVerticalAlign].values - if align >= 0 && align < len(values) { - buffer.WriteRune(' ') - buffer.WriteString(values[align]) - } else { - buffer.WriteString(` top`) - } - - fit, _ := enumProperty(image, backgroundFit, session, NoneFit) - values = enumProperties[backgroundFit].values - if fit > 0 && fit < len(values) { - - buffer.WriteString(` / `) - buffer.WriteString(values[fit]) - - } else { - - width, _ := sizeProperty(image, Width, session) - height, _ := sizeProperty(image, Height, session) - - if width.Type != Auto || height.Type != Auto { - buffer.WriteString(` / `) - buffer.WriteString(width.cssString("auto", session)) - buffer.WriteRune(' ') - buffer.WriteString(height.cssString("auto", session)) - } - } - - repeat, _ := enumProperty(image, Repeat, session, NoRepeat) - values = enumProperties[Repeat].values - if repeat >= 0 && repeat < len(values) { - buffer.WriteRune(' ') - buffer.WriteString(values[repeat]) - } else { - buffer.WriteString(` no-repeat`) - } - - return buffer.String() - } - - return "" -} - -func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) { - image.writeToBuffer(buffer, indent, image.Tag(), []PropertyName{ - Source, - Width, - Height, - ImageHorizontalAlign, - ImageVerticalAlign, - backgroundFit, - Repeat, - Attachment, - }) -} - -func (image *backgroundImage) String() string { - return runStringWriter(image) -} - func parseBackgroundValue(value any) []BackgroundElement { switch value := value.(type) { diff --git a/backgroundImage.go b/backgroundImage.go new file mode 100644 index 0000000..aac13fb --- /dev/null +++ b/backgroundImage.go @@ -0,0 +1,198 @@ +package rui + +import ( + "strings" +) + +// Constants related to view's background description +const ( + // NoRepeat is value of the Repeat property of an background image: + // The image is not repeated (and hence the background image painting area + // will not necessarily be entirely covered). The position of the non-repeated + // background image is defined by the background-position CSS property. + NoRepeat = 0 + + // RepeatXY is value of the Repeat property of an background image: + // The image is repeated as much as needed to cover the whole background + // image painting area. The last image will be clipped if it doesn't fit. + RepeatXY = 1 + + // RepeatX is value of the Repeat property of an background image: + // The image is repeated horizontally as much as needed to cover + // the whole width background image painting area. The image is not repeated vertically. + // The last image will be clipped if it doesn't fit. + RepeatX = 2 + + // RepeatY is value of the Repeat property of an background image: + // The image is repeated vertically as much as needed to cover + // the whole height background image painting area. The image is not repeated horizontally. + // The last image will be clipped if it doesn't fit. + RepeatY = 3 + + // RepeatRound is value of the Repeat property of an background image: + // As the allowed space increases in size, the repeated images will stretch (leaving no gaps) + // until there is room (space left >= half of the image width) for another one to be added. + // When the next image is added, all of the current ones compress to allow room. + RepeatRound = 4 + + // RepeatSpace is value of the Repeat property of an background image: + // The image is repeated as much as possible without clipping. The first and last images + // are pinned to either side of the element, and whitespace is distributed evenly between the images. + RepeatSpace = 5 + + // ScrollAttachment is value of the Attachment property of an background image: + // The background is fixed relative to the element itself and does not scroll with its contents. + // (It is effectively attached to the element's border.) + ScrollAttachment = 0 + + // FixedAttachment is value of the Attachment property of an background image: + // The background is fixed relative to the viewport. Even if an element has + // a scrolling mechanism, the background doesn't move with the element. + FixedAttachment = 1 + + // LocalAttachment is value of the Attachment property of an background image: + // The background is fixed relative to the element's contents. If the element has a scrolling mechanism, + // the background scrolls with the element's contents, and the background painting area + // and background positioning area are relative to the scrollable area of the element + // rather than to the border framing them. + LocalAttachment = 2 +) + +type backgroundImage struct { + backgroundElement +} + +// NewBackgroundImage creates the new background image +func NewBackgroundImage(params Params) BackgroundElement { + result := new(backgroundImage) + result.init() + for tag, value := range params { + result.Set(tag, value) + } + return result +} + +func (image *backgroundImage) init() { + image.backgroundElement.init() + image.normalize = normalizeBackgroundImageTag + image.supportedProperties = []PropertyName{ + Attachment, Width, Height, Repeat, ImageHorizontalAlign, ImageVerticalAlign, backgroundFit, Source, + } +} + +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 normalizeBackgroundImageTag(tag PropertyName) PropertyName { + tag = defaultNormalize(tag) + switch tag { + case "source": + tag = Source + + case Fit: + tag = backgroundFit + + case HorizontalAlign: + tag = ImageHorizontalAlign + + case VerticalAlign: + tag = ImageVerticalAlign + } + + return tag +} + +func (image *backgroundImage) cssStyle(session Session) string { + if src, ok := imageProperty(image, Source, session); ok && src != "" { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + buffer.WriteString(`url(`) + buffer.WriteString(src) + buffer.WriteRune(')') + + attachment, _ := enumProperty(image, Attachment, session, NoRepeat) + values := enumProperties[Attachment].values + if attachment > 0 && attachment < len(values) { + buffer.WriteRune(' ') + buffer.WriteString(values[attachment]) + } + + align, _ := enumProperty(image, ImageHorizontalAlign, session, LeftAlign) + values = enumProperties[ImageHorizontalAlign].values + if align >= 0 && align < len(values) { + buffer.WriteRune(' ') + buffer.WriteString(values[align]) + } else { + buffer.WriteString(` left`) + } + + align, _ = enumProperty(image, ImageVerticalAlign, session, TopAlign) + values = enumProperties[ImageVerticalAlign].values + if align >= 0 && align < len(values) { + buffer.WriteRune(' ') + buffer.WriteString(values[align]) + } else { + buffer.WriteString(` top`) + } + + fit, _ := enumProperty(image, backgroundFit, session, NoneFit) + values = enumProperties[backgroundFit].values + if fit > 0 && fit < len(values) { + + buffer.WriteString(` / `) + buffer.WriteString(values[fit]) + + } else { + + width, _ := sizeProperty(image, Width, session) + height, _ := sizeProperty(image, Height, session) + + if width.Type != Auto || height.Type != Auto { + buffer.WriteString(` / `) + buffer.WriteString(width.cssString("auto", session)) + buffer.WriteRune(' ') + buffer.WriteString(height.cssString("auto", session)) + } + } + + repeat, _ := enumProperty(image, Repeat, session, NoRepeat) + values = enumProperties[Repeat].values + if repeat >= 0 && repeat < len(values) { + buffer.WriteRune(' ') + buffer.WriteString(values[repeat]) + } else { + buffer.WriteString(` no-repeat`) + } + + return buffer.String() + } + + return "" +} + +func (image *backgroundImage) writeString(buffer *strings.Builder, indent string) { + image.writeToBuffer(buffer, indent, image.Tag(), []PropertyName{ + Source, + Width, + Height, + ImageHorizontalAlign, + ImageVerticalAlign, + backgroundFit, + Repeat, + Attachment, + }) +} + +func (image *backgroundImage) String() string { + return runStringWriter(image) +} diff --git a/backgroundGradient.go b/backgroundLinearGradient.go similarity index 53% rename from backgroundGradient.go rename to backgroundLinearGradient.go index 1a5355f..1760b9c 100644 --- a/backgroundGradient.go +++ b/backgroundLinearGradient.go @@ -4,47 +4,29 @@ import "strings" // Constants related to view's background gradient description 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 @@ -65,10 +47,6 @@ type backgroundLinearGradient struct { backgroundGradient } -type backgroundRadialGradient struct { - backgroundGradient -} - // NewBackgroundLinearGradient creates the new background linear gradient func NewBackgroundLinearGradient(params Params) BackgroundElement { result := new(backgroundLinearGradient) @@ -79,16 +57,6 @@ func NewBackgroundLinearGradient(params Params) BackgroundElement { return result } -// NewBackgroundRadialGradient creates the new background radial gradient -func NewBackgroundRadialGradient(params Params) BackgroundElement { - result := new(backgroundRadialGradient) - result.init() - for tag, value := range params { - result.Set(tag, value) - } - return result -} - func parseGradientText(value string) []BackgroundGradientPoint { elements := strings.Split(value, ",") count := len(elements) @@ -422,261 +390,3 @@ func (gradient *backgroundLinearGradient) writeString(buffer *strings.Builder, i func (gradient *backgroundLinearGradient) String() string { return runStringWriter(gradient) } - -func (gradient *backgroundRadialGradient) init() { - gradient.backgroundElement.init() - gradient.normalize = normalizeRadialGradientTag - gradient.set = backgroundRadialGradientSet - gradient.supportedProperties = []PropertyName{ - RadialGradientRadius, RadialGradientShape, CenterX, CenterY, Gradient, Repeating, - } -} - -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 normalizeRadialGradientTag(tag PropertyName) PropertyName { - tag = defaultNormalize(tag) - switch tag { - case Radius: - tag = RadialGradientRadius - - case Shape: - tag = RadialGradientShape - - case "x-center": - tag = CenterX - - case "y-center": - tag = CenterY - } - - return tag -} - -func backgroundRadialGradientSet(properties Properties, tag PropertyName, value any) []PropertyName { - switch tag { - case RadialGradientRadius: - switch value := value.(type) { - case []SizeUnit: - switch len(value) { - case 0: - properties.setRaw(RadialGradientRadius, nil) - - case 1: - if value[0].Type == Auto { - properties.setRaw(RadialGradientRadius, nil) - } else { - properties.setRaw(RadialGradientRadius, value[0]) - } - - default: - properties.setRaw(RadialGradientRadius, value) - } - return []PropertyName{tag} - - case []any: - switch len(value) { - case 0: - properties.setRaw(RadialGradientRadius, nil) - return []PropertyName{tag} - - case 1: - return backgroundRadialGradientSet(properties, RadialGradientRadius, value[0]) - - default: - properties.setRaw(RadialGradientRadius, value) - return []PropertyName{tag} - } - - case string: - if setSimpleProperty(properties, RadialGradientRadius, value) { - return []PropertyName{tag} - } - if size, err := stringToSizeUnit(value); err == nil { - if size.Type == Auto { - properties.setRaw(RadialGradientRadius, nil) - } else { - properties.setRaw(RadialGradientRadius, size) - } - return []PropertyName{tag} - } - return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values) - - case SizeUnit: - if value.Type == Auto { - properties.setRaw(RadialGradientRadius, nil) - } else { - properties.setRaw(RadialGradientRadius, value) - } - return []PropertyName{tag} - - case int: - return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values) - } - - ErrorLogF(`Invalid value of "%s" property: %v`, tag, value) - return nil - - case RadialGradientShape, CenterX, CenterY: - return propertiesSet(properties, tag, value) - } - - return backgroundGradientSet(properties, tag, value) -} - -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(`) - } - - var shapeText string - if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient { - shapeText = `circle ` - } else { - shapeText = `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(shapeText) - shapeText = "" - buffer.WriteString(values.cssValues[n]) - buffer.WriteString(" ") - } else { - if r, ok := StringToSizeUnit(text); ok && r.Type != Auto { - buffer.WriteString("ellipse ") - shapeText = "" - buffer.WriteString(r.cssString("", session)) - buffer.WriteString(" ") - buffer.WriteString(r.cssString("", session)) - 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(shapeText) - shapeText = "" - buffer.WriteString(values[value]) - buffer.WriteString(" ") - } else { - ErrorLogF(`Invalid radial gradient radius: %d`, value) - } - - case SizeUnit: - if value.Type != Auto { - buffer.WriteString("ellipse ") - shapeText = "" - buffer.WriteString(value.cssString("", session)) - buffer.WriteString(" ") - buffer.WriteString(value.cssString("", session)) - buffer.WriteString(" ") - } - - case []SizeUnit: - count := len(value) - if count > 2 { - count = 2 - } - buffer.WriteString("ellipse ") - shapeText = "" - for i := 0; i < count; i++ { - buffer.WriteString(value[i].cssString("50%", session)) - buffer.WriteString(" ") - } - - case []any: - count := len(value) - if count > 2 { - count = 2 - } - buffer.WriteString("ellipse ") - shapeText = "" - for i := 0; i < count; i++ { - if value[i] != nil { - switch value := value[i].(type) { - case SizeUnit: - buffer.WriteString(value.cssString("50%", session)) - buffer.WriteString(" ") - - case string: - if text, ok := session.resolveConstants(value); ok { - if size, err := stringToSizeUnit(text); err == nil { - buffer.WriteString(size.cssString("50%", session)) - buffer.WriteString(" ") - } else { - buffer.WriteString("50% ") - } - } else { - buffer.WriteString("50% ") - } - } - } else { - buffer.WriteString("50% ") - } - } - } - } - - x, _ := sizeProperty(gradient, CenterX, session) - y, _ := sizeProperty(gradient, CenterX, session) - if x.Type != Auto || y.Type != Auto { - if shapeText != "" { - buffer.WriteString(shapeText) - } - buffer.WriteString("at ") - buffer.WriteString(x.cssString("50%", session)) - buffer.WriteString(" ") - buffer.WriteString(y.cssString("50%", session)) - } else if shapeText != "" { - buffer.WriteString(shapeText) - } - - buffer.WriteString(", ") - if !gradient.writeGradient(session, buffer) { - return "" - } - - buffer.WriteString(") ") - - return buffer.String() -} -func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) { - gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{ - Gradient, - CenterX, - CenterY, - Repeating, - RadialGradientShape, - RadialGradientRadius, - }) -} - -func (gradient *backgroundRadialGradient) String() string { - return runStringWriter(gradient) -} diff --git a/backgroundRadialGradient.go b/backgroundRadialGradient.go new file mode 100644 index 0000000..85956c6 --- /dev/null +++ b/backgroundRadialGradient.go @@ -0,0 +1,307 @@ +package rui + +import "strings" + +// Constants related to view's background gradient description +const ( + // 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 +) + +type backgroundRadialGradient struct { + backgroundGradient +} + +// NewBackgroundRadialGradient creates the new background radial gradient +func NewBackgroundRadialGradient(params Params) BackgroundElement { + result := new(backgroundRadialGradient) + result.init() + for tag, value := range params { + result.Set(tag, value) + } + return result +} + +func (gradient *backgroundRadialGradient) init() { + gradient.backgroundElement.init() + gradient.normalize = normalizeRadialGradientTag + gradient.set = backgroundRadialGradientSet + gradient.supportedProperties = []PropertyName{ + RadialGradientRadius, RadialGradientShape, CenterX, CenterY, Gradient, Repeating, + } +} + +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 normalizeRadialGradientTag(tag PropertyName) PropertyName { + tag = defaultNormalize(tag) + switch tag { + case Radius: + tag = RadialGradientRadius + + case Shape: + tag = RadialGradientShape + + case "x-center": + tag = CenterX + + case "y-center": + tag = CenterY + } + + return tag +} + +func backgroundRadialGradientSet(properties Properties, tag PropertyName, value any) []PropertyName { + switch tag { + case RadialGradientRadius: + switch value := value.(type) { + case []SizeUnit: + switch len(value) { + case 0: + properties.setRaw(RadialGradientRadius, nil) + + case 1: + if value[0].Type == Auto { + properties.setRaw(RadialGradientRadius, nil) + } else { + properties.setRaw(RadialGradientRadius, value[0]) + } + + default: + properties.setRaw(RadialGradientRadius, value) + } + return []PropertyName{tag} + + case []any: + switch len(value) { + case 0: + properties.setRaw(RadialGradientRadius, nil) + return []PropertyName{tag} + + case 1: + return backgroundRadialGradientSet(properties, RadialGradientRadius, value[0]) + + default: + properties.setRaw(RadialGradientRadius, value) + return []PropertyName{tag} + } + + case string: + if setSimpleProperty(properties, RadialGradientRadius, value) { + return []PropertyName{tag} + } + if size, err := stringToSizeUnit(value); err == nil { + if size.Type == Auto { + properties.setRaw(RadialGradientRadius, nil) + } else { + properties.setRaw(RadialGradientRadius, size) + } + return []PropertyName{tag} + } + return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values) + + case SizeUnit: + if value.Type == Auto { + properties.setRaw(RadialGradientRadius, nil) + } else { + properties.setRaw(RadialGradientRadius, value) + } + return []PropertyName{tag} + + case int: + return setEnumProperty(properties, RadialGradientRadius, value, enumProperties[RadialGradientRadius].values) + } + + ErrorLogF(`Invalid value of "%s" property: %v`, tag, value) + return nil + + case RadialGradientShape, CenterX, CenterY: + return propertiesSet(properties, tag, value) + } + + return backgroundGradientSet(properties, tag, value) +} + +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(`) + } + + var shapeText string + if shape, ok := enumProperty(gradient, RadialGradientShape, session, EllipseGradient); ok && shape == CircleGradient { + shapeText = `circle ` + } else { + shapeText = `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(shapeText) + shapeText = "" + buffer.WriteString(values.cssValues[n]) + buffer.WriteString(" ") + } else { + if r, ok := StringToSizeUnit(text); ok && r.Type != Auto { + buffer.WriteString("ellipse ") + shapeText = "" + buffer.WriteString(r.cssString("", session)) + buffer.WriteString(" ") + buffer.WriteString(r.cssString("", session)) + 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(shapeText) + shapeText = "" + buffer.WriteString(values[value]) + buffer.WriteString(" ") + } else { + ErrorLogF(`Invalid radial gradient radius: %d`, value) + } + + case SizeUnit: + if value.Type != Auto { + buffer.WriteString("ellipse ") + shapeText = "" + buffer.WriteString(value.cssString("", session)) + buffer.WriteString(" ") + buffer.WriteString(value.cssString("", session)) + buffer.WriteString(" ") + } + + case []SizeUnit: + count := len(value) + if count > 2 { + count = 2 + } + buffer.WriteString("ellipse ") + shapeText = "" + for i := 0; i < count; i++ { + buffer.WriteString(value[i].cssString("50%", session)) + buffer.WriteString(" ") + } + + case []any: + count := len(value) + if count > 2 { + count = 2 + } + buffer.WriteString("ellipse ") + shapeText = "" + for i := 0; i < count; i++ { + if value[i] != nil { + switch value := value[i].(type) { + case SizeUnit: + buffer.WriteString(value.cssString("50%", session)) + buffer.WriteString(" ") + + case string: + if text, ok := session.resolveConstants(value); ok { + if size, err := stringToSizeUnit(text); err == nil { + buffer.WriteString(size.cssString("50%", session)) + buffer.WriteString(" ") + } else { + buffer.WriteString("50% ") + } + } else { + buffer.WriteString("50% ") + } + } + } else { + buffer.WriteString("50% ") + } + } + } + } + + x, _ := sizeProperty(gradient, CenterX, session) + y, _ := sizeProperty(gradient, CenterX, session) + if x.Type != Auto || y.Type != Auto { + if shapeText != "" { + buffer.WriteString(shapeText) + } + buffer.WriteString("at ") + buffer.WriteString(x.cssString("50%", session)) + buffer.WriteString(" ") + buffer.WriteString(y.cssString("50%", session)) + } else if shapeText != "" { + buffer.WriteString(shapeText) + } + + buffer.WriteString(", ") + if !gradient.writeGradient(session, buffer) { + return "" + } + + buffer.WriteString(") ") + + return buffer.String() +} + +func (gradient *backgroundRadialGradient) writeString(buffer *strings.Builder, indent string) { + gradient.writeToBuffer(buffer, indent, gradient.Tag(), []PropertyName{ + Gradient, + CenterX, + CenterY, + Repeating, + RadialGradientShape, + RadialGradientRadius, + }) +} + +func (gradient *backgroundRadialGradient) String() string { + return runStringWriter(gradient) +}