package rui import ( "fmt" "strings" ) // Constants related to view's border description const ( // NoneLine constant specifies that there is no border NoneLine = 0 // SolidLine constant specifies the border/line as a solid line SolidLine = 1 // DashedLine constant specifies the border/line as a dashed line DashedLine = 2 // DottedLine constant specifies the border/line as a dotted line DottedLine = 3 // DoubleLine constant specifies the border/line as a double solid line DoubleLine = 4 // DoubleLine constant specifies the border/line as a double solid line WavyLine = 5 // LeftStyle is the constant for "left-style" property tag. // // Used by `BorderProperty`. // Left border line style. // // Supported types: `int`, `string`. // // Values: // `0`(`NoneLine`) or "none" - The border will not be drawn. // `1`(`SolidLine`) or "solid" - Solid line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border. LeftStyle = "left-style" // RightStyle is the constant for "right-style" property tag. // // Used by `BorderProperty`. // Right border line style. // // Supported types: `int`, `string`. // // Values: // `0`(`NoneLine`) or "none" - The border will not be drawn. // `1`(`SolidLine`) or "solid" - Solid line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border. RightStyle = "right-style" // TopStyle is the constant for "top-style" property tag. // // Used by `BorderProperty`. // Top border line style. // // Supported types: `int`, `string`. // // Values: // `0`(`NoneLine`) or "none" - The border will not be drawn. // `1`(`SolidLine`) or "solid" - Solid line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border. TopStyle = "top-style" // BottomStyle is the constant for "bottom-style" property tag. // // Used by `BorderProperty`. // Bottom border line style. // // Supported types: `int`, `string`. // // Values: // `0`(`NoneLine`) or "none" - The border will not be drawn. // `1`(`SolidLine`) or "solid" - Solid line as a border. // `2`(`DashedLine`) or "dashed" - Dashed line as a border. // `3`(`DottedLine`) or "dotted" - Dotted line as a border. // `4`(`DoubleLine`) or "double" - Double line as a border. BottomStyle = "bottom-style" // LeftWidth is the constant for "left-width" property tag. // // Used by `BorderProperty`. // Left border line width. // // Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`. // // Internal type is `SizeUnit`, other types converted to it during assignment. // See `SizeUnit` description for more details. LeftWidth = "left-width" // RightWidth is the constant for "right-width" property tag. // // Used by `BorderProperty`. // Right border line width. // // Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`. // // Internal type is `SizeUnit`, other types converted to it during assignment. // See `SizeUnit` description for more details. RightWidth = "right-width" // TopWidth is the constant for "top-width" property tag. // // Used by `BorderProperty`. // Top border line width. // // Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`. // // Internal type is `SizeUnit`, other types converted to it during assignment. // See `SizeUnit` description for more details. TopWidth = "top-width" // BottomWidth is the constant for "bottom-width" property tag. // // Used by `BorderProperty`. // Bottom border line width. // // Supported types: `SizeUnit`, `SizeFunc`, `string`, `float`, `int`. // // Internal type is `SizeUnit`, other types converted to it during assignment. // See `SizeUnit` description for more details. BottomWidth = "bottom-width" // LeftColor is the constant for "left-color" property tag. // // Used by `BorderProperty`. // Left border line color. // // Supported types: `Color`, `string`. // // Internal type is `Color`, other types converted to it during assignment. // See `Color` description for more details. LeftColor = "left-color" // RightColor is the constant for "right-color" property tag. // // Used by `BorderProperty`. // Right border line color. // // Supported types: `Color`, `string`. // // Internal type is `Color`, other types converted to it during assignment. // See `Color` description for more details. RightColor = "right-color" // TopColor is the constant for "top-color" property tag. // // Used by `BorderProperty`. // Top border line color. // // Supported types: `Color`, `string`. // // Internal type is `Color`, other types converted to it during assignment. // See `Color` description for more details. TopColor = "top-color" // BottomColor is the constant for "bottom-color" property tag. // // Used by `BorderProperty`. // Bottom border line color. // // Supported types: `Color`, `string`. // // Internal type is `Color`, other types converted to it during assignment. // See `Color` description for more details. BottomColor = "bottom-color" ) // BorderProperty is the interface of a view border data type BorderProperty interface { Properties fmt.Stringer stringWriter // ViewBorders returns top, right, bottom and left borders information all together ViewBorders(session Session) ViewBorders delete(tag string) cssStyle(builder cssBuilder, session Session) cssWidth(builder cssBuilder, session Session) cssColor(builder cssBuilder, session Session) cssStyleValue(session Session) string cssWidthValue(session Session) string cssColorValue(session Session) string } type borderProperty struct { propertyList } func newBorderProperty(value any) BorderProperty { border := new(borderProperty) border.properties = map[string]any{} if value != nil { switch value := value.(type) { case BorderProperty: return value case DataNode: if value.Type() == ObjectNode { _ = border.setBorderObject(value.Object()) } else { return nil } case DataObject: _ = border.setBorderObject(value) case ViewBorder: border.properties[Style] = value.Style border.properties[Width] = value.Width border.properties[ColorTag] = value.Color case ViewBorders: if value.Left.Style == value.Right.Style && value.Left.Style == value.Top.Style && value.Left.Style == value.Bottom.Style { border.properties[Style] = value.Left.Style } else { border.properties[LeftStyle] = value.Left.Style border.properties[RightStyle] = value.Right.Style border.properties[TopStyle] = value.Top.Style border.properties[BottomStyle] = value.Bottom.Style } if value.Left.Width.Equal(value.Right.Width) && value.Left.Width.Equal(value.Top.Width) && value.Left.Width.Equal(value.Bottom.Width) { border.properties[Width] = value.Left.Width } else { border.properties[LeftWidth] = value.Left.Width border.properties[RightWidth] = value.Right.Width border.properties[TopWidth] = value.Top.Width border.properties[BottomWidth] = value.Bottom.Width } if value.Left.Color == value.Right.Color && value.Left.Color == value.Top.Color && value.Left.Color == value.Bottom.Color { border.properties[ColorTag] = value.Left.Color } else { border.properties[LeftColor] = value.Left.Color border.properties[RightColor] = value.Right.Color border.properties[TopColor] = value.Top.Color border.properties[BottomColor] = value.Bottom.Color } default: invalidPropertyValue(Border, value) return nil } } return border } // NewBorder creates the new BorderProperty func NewBorder(params Params) BorderProperty { border := new(borderProperty) border.properties = map[string]any{} if params != nil { for _, tag := range []string{Style, Width, ColorTag, Left, Right, Top, Bottom, LeftStyle, RightStyle, TopStyle, BottomStyle, LeftWidth, RightWidth, TopWidth, BottomWidth, LeftColor, RightColor, TopColor, BottomColor} { if value, ok := params[tag]; ok && value != nil { border.Set(tag, value) } } } return border } func (border *borderProperty) normalizeTag(tag string) string { tag = strings.ToLower(tag) switch tag { case BorderLeft, CellBorderLeft: return Left case BorderRight, CellBorderRight: return Right case BorderTop, CellBorderTop: return Top case BorderBottom, CellBorderBottom: return Bottom case BorderStyle, CellBorderStyle: return Style case BorderLeftStyle, CellBorderLeftStyle, "style-left": return LeftStyle case BorderRightStyle, CellBorderRightStyle, "style-right": return RightStyle case BorderTopStyle, CellBorderTopStyle, "style-top": return TopStyle case BorderBottomStyle, CellBorderBottomStyle, "style-bottom": return BottomStyle case BorderWidth, CellBorderWidth: return Width case BorderLeftWidth, CellBorderLeftWidth, "width-left": return LeftWidth case BorderRightWidth, CellBorderRightWidth, "width-right": return RightWidth case BorderTopWidth, CellBorderTopWidth, "width-top": return TopWidth case BorderBottomWidth, CellBorderBottomWidth, "width-bottom": return BottomWidth case BorderColor, CellBorderColor: return ColorTag case BorderLeftColor, CellBorderLeftColor, "color-left": return LeftColor case BorderRightColor, CellBorderRightColor, "color-right": return RightColor case BorderTopColor, CellBorderTopColor, "color-top": return TopColor case BorderBottomColor, CellBorderBottomColor, "color-bottom": return BottomColor } return tag } func (border *borderProperty) writeString(buffer *strings.Builder, indent string) { buffer.WriteString("_{ ") comma := false write := func(tag string, value any) { if comma { buffer.WriteString(", ") } buffer.WriteString(tag) buffer.WriteString(" = ") writePropertyValue(buffer, BorderStyle, value, indent) comma = true } for _, tag := range []string{Style, Width, ColorTag} { if value, ok := border.properties[tag]; ok { write(tag, value) } } for _, side := range []string{Top, Right, Bottom, Left} { style, okStyle := border.properties[side+"-"+Style] width, okWidth := border.properties[side+"-"+Width] color, okColor := border.properties[side+"-"+ColorTag] if okStyle || okWidth || okColor { if comma { buffer.WriteString(", ") comma = false } buffer.WriteString(side) buffer.WriteString(" = _{ ") if okStyle { write(Style, style) } if okWidth { write(Width, width) } if okColor { write(ColorTag, color) } buffer.WriteString(" }") comma = true } } buffer.WriteString(" }") } func (border *borderProperty) String() string { return runStringWriter(border) } func (border *borderProperty) setSingleBorderObject(prefix string, obj DataObject) bool { result := true if text, ok := obj.PropertyValue(Style); ok { if !border.setEnumProperty(prefix+"-style", text, enumProperties[BorderStyle].values) { result = false } } if text, ok := obj.PropertyValue(ColorTag); ok { if !border.setColorProperty(prefix+"-color", text) { result = false } } if text, ok := obj.PropertyValue("width"); ok { if !border.setSizeProperty(prefix+"-width", text) { result = false } } return result } func (border *borderProperty) setBorderObject(obj DataObject) bool { result := true for _, side := range []string{Top, Right, Bottom, Left} { if node := obj.PropertyByTag(side); node != nil { if node.Type() == ObjectNode { if !border.setSingleBorderObject(side, node.Object()) { result = false } } else { notCompatibleType(side, node) result = false } } } if text, ok := obj.PropertyValue(Style); ok { values := split4Values(text) styles := enumProperties[BorderStyle].values switch len(values) { case 1: if !border.setEnumProperty(Style, values[0], styles) { result = false } case 4: for n, tag := range [4]string{TopStyle, RightStyle, BottomStyle, LeftStyle} { if !border.setEnumProperty(tag, values[n], styles) { result = false } } default: notCompatibleType(Style, text) result = false } } if text, ok := obj.PropertyValue(ColorTag); ok { values := split4Values(text) switch len(values) { case 1: if !border.setColorProperty(ColorTag, values[0]) { return false } case 4: for n, tag := range [4]string{TopColor, RightColor, BottomColor, LeftColor} { if !border.setColorProperty(tag, values[n]) { return false } } default: notCompatibleType(ColorTag, text) result = false } } if text, ok := obj.PropertyValue(Width); ok { values := split4Values(text) switch len(values) { case 1: if !border.setSizeProperty(Width, values[0]) { result = false } case 4: for n, tag := range [4]string{TopWidth, RightWidth, BottomWidth, LeftWidth} { if !border.setSizeProperty(tag, values[n]) { result = false } } default: notCompatibleType(Width, text) result = false } } return result } func (border *borderProperty) Remove(tag string) { tag = border.normalizeTag(tag) switch tag { case Style: for _, t := range []string{tag, TopStyle, RightStyle, BottomStyle, LeftStyle} { delete(border.properties, t) } case Width: for _, t := range []string{tag, TopWidth, RightWidth, BottomWidth, LeftWidth} { delete(border.properties, t) } case ColorTag: for _, t := range []string{tag, TopColor, RightColor, BottomColor, LeftColor} { delete(border.properties, t) } case Left, Right, Top, Bottom: border.Remove(tag + "-style") border.Remove(tag + "-width") border.Remove(tag + "-color") case LeftStyle, RightStyle, TopStyle, BottomStyle: delete(border.properties, tag) if style, ok := border.properties[Style]; ok && style != nil { for _, t := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} { if t != tag { if _, ok := border.properties[t]; !ok { border.properties[t] = style } } } } case LeftWidth, RightWidth, TopWidth, BottomWidth: delete(border.properties, tag) if width, ok := border.properties[Width]; ok && width != nil { for _, t := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} { if t != tag { if _, ok := border.properties[t]; !ok { border.properties[t] = width } } } } case LeftColor, RightColor, TopColor, BottomColor: delete(border.properties, tag) if color, ok := border.properties[ColorTag]; ok && color != nil { for _, t := range []string{TopColor, RightColor, BottomColor, LeftColor} { if t != tag { if _, ok := border.properties[t]; !ok { border.properties[t] = color } } } } default: ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag) } } func (border *borderProperty) Set(tag string, value any) bool { if value == nil { border.Remove(tag) return true } tag = border.normalizeTag(tag) switch tag { case Style: if border.setEnumProperty(Style, value, enumProperties[BorderStyle].values) { for _, side := range []string{TopStyle, RightStyle, BottomStyle, LeftStyle} { delete(border.properties, side) } return true } case Width: if border.setSizeProperty(Width, value) { for _, side := range []string{TopWidth, RightWidth, BottomWidth, LeftWidth} { delete(border.properties, side) } return true } case ColorTag: if border.setColorProperty(ColorTag, value) { for _, side := range []string{TopColor, RightColor, BottomColor, LeftColor} { delete(border.properties, side) } return true } case LeftStyle, RightStyle, TopStyle, BottomStyle: return border.setEnumProperty(tag, value, enumProperties[BorderStyle].values) case LeftWidth, RightWidth, TopWidth, BottomWidth: return border.setSizeProperty(tag, value) case LeftColor, RightColor, TopColor, BottomColor: return border.setColorProperty(tag, value) case Left, Right, Top, Bottom: switch value := value.(type) { case string: if obj := ParseDataText(value); obj != nil { return border.setSingleBorderObject(tag, obj) } case DataObject: return border.setSingleBorderObject(tag, value) case BorderProperty: styleTag := tag + "-" + Style if style := value.Get(styleTag); value != nil { border.properties[styleTag] = style } colorTag := tag + "-" + ColorTag if color := value.Get(colorTag); value != nil { border.properties[colorTag] = color } widthTag := tag + "-" + Width if width := value.Get(widthTag); value != nil { border.properties[widthTag] = width } return true case ViewBorder: border.properties[tag+"-"+Style] = value.Style border.properties[tag+"-"+Width] = value.Width border.properties[tag+"-"+ColorTag] = value.Color return true } fallthrough default: ErrorLogF(`"%s" property is not compatible with the BorderProperty`, tag) } return false } func (border *borderProperty) Get(tag string) any { tag = border.normalizeTag(tag) if result, ok := border.properties[tag]; ok { return result } switch tag { case Left, Right, Top, Bottom: result := newBorderProperty(nil) if style, ok := border.properties[tag+"-"+Style]; ok { result.Set(Style, style) } else if style, ok := border.properties[Style]; ok { result.Set(Style, style) } if width, ok := border.properties[tag+"-"+Width]; ok { result.Set(Width, width) } else if width, ok := border.properties[Width]; ok { result.Set(Width, width) } if color, ok := border.properties[tag+"-"+ColorTag]; ok { result.Set(ColorTag, color) } else if color, ok := border.properties[ColorTag]; ok { result.Set(ColorTag, color) } return result case LeftStyle, RightStyle, TopStyle, BottomStyle: if style, ok := border.properties[tag]; ok { return style } return border.properties[Style] case LeftWidth, RightWidth, TopWidth, BottomWidth: if width, ok := border.properties[tag]; ok { return width } return border.properties[Width] case LeftColor, RightColor, TopColor, BottomColor: if color, ok := border.properties[tag]; ok { return color } return border.properties[ColorTag] } return nil } func (border *borderProperty) delete(tag string) { tag = border.normalizeTag(tag) remove := []string{} switch tag { case Style: remove = []string{Style, LeftStyle, RightStyle, TopStyle, BottomStyle} case Width: remove = []string{Width, LeftWidth, RightWidth, TopWidth, BottomWidth} case ColorTag: remove = []string{ColorTag, LeftColor, RightColor, TopColor, BottomColor} case Left, Right, Top, Bottom: if border.Get(Style) != nil { border.properties[tag+"-"+Style] = 0 remove = []string{tag + "-" + ColorTag, tag + "-" + Width} } else { remove = []string{tag + "-" + Style, tag + "-" + ColorTag, tag + "-" + Width} } case LeftStyle, RightStyle, TopStyle, BottomStyle: if border.Get(Style) != nil { border.properties[tag] = 0 } else { remove = []string{tag} } case LeftWidth, RightWidth, TopWidth, BottomWidth: if border.Get(Width) != nil { border.properties[tag] = AutoSize() } else { remove = []string{tag} } case LeftColor, RightColor, TopColor, BottomColor: if border.Get(ColorTag) != nil { border.properties[tag] = 0 } else { remove = []string{tag} } } for _, tag := range remove { delete(border.properties, tag) } } func (border *borderProperty) ViewBorders(session Session) ViewBorders { defStyle, _ := valueToEnum(border.getRaw(Style), BorderStyle, session, NoneLine) defWidth, _ := sizeProperty(border, Width, session) defColor, _ := colorProperty(border, ColorTag, session) getBorder := func(prefix string) ViewBorder { var result ViewBorder var ok bool if result.Style, ok = valueToEnum(border.getRaw(prefix+Style), BorderStyle, session, NoneLine); !ok { result.Style = defStyle } if result.Width, ok = sizeProperty(border, prefix+Width, session); !ok { result.Width = defWidth } if result.Color, ok = colorProperty(border, prefix+ColorTag, session); !ok { result.Color = defColor } return result } return ViewBorders{ Top: getBorder("top-"), Left: getBorder("left-"), Right: getBorder("right-"), Bottom: getBorder("bottom-"), } } func (border *borderProperty) cssStyle(builder cssBuilder, session Session) { borders := border.ViewBorders(session) values := enumProperties[BorderStyle].cssValues if borders.Top.Style == borders.Right.Style && borders.Top.Style == borders.Left.Style && borders.Top.Style == borders.Bottom.Style { builder.add(BorderStyle, values[borders.Top.Style]) } else { builder.addValues(BorderStyle, " ", values[borders.Top.Style], values[borders.Right.Style], values[borders.Bottom.Style], values[borders.Left.Style]) } } func (border *borderProperty) cssWidth(builder cssBuilder, session Session) { borders := border.ViewBorders(session) if borders.Top.Width == borders.Right.Width && borders.Top.Width == borders.Left.Width && borders.Top.Width == borders.Bottom.Width { if borders.Top.Width.Type != Auto { builder.add("border-width", borders.Top.Width.cssString("0", session)) } } else { builder.addValues("border-width", " ", borders.Top.Width.cssString("0", session), borders.Right.Width.cssString("0", session), borders.Bottom.Width.cssString("0", session), borders.Left.Width.cssString("0", session)) } } func (border *borderProperty) cssColor(builder cssBuilder, session Session) { borders := border.ViewBorders(session) if borders.Top.Color == borders.Right.Color && borders.Top.Color == borders.Left.Color && borders.Top.Color == borders.Bottom.Color { if borders.Top.Color != 0 { builder.add("border-color", borders.Top.Color.cssString()) } } else { builder.addValues("border-color", " ", borders.Top.Color.cssString(), borders.Right.Color.cssString(), borders.Bottom.Color.cssString(), borders.Left.Color.cssString()) } } func (border *borderProperty) cssStyleValue(session Session) string { var builder cssValueBuilder border.cssStyle(&builder, session) return builder.finish() } func (border *borderProperty) cssWidthValue(session Session) string { var builder cssValueBuilder border.cssWidth(&builder, session) return builder.finish() } func (border *borderProperty) cssColorValue(session Session) string { var builder cssValueBuilder border.cssColor(&builder, session) return builder.finish() } // ViewBorder describes parameters of a view border type ViewBorder struct { // Style of the border line Style int // Color of the border line Color Color // Width of the border line Width SizeUnit } // ViewBorders describes the top, right, bottom, and left border of a view type ViewBorders struct { Top, Right, Bottom, Left ViewBorder } // AllTheSame returns true if all borders are the same func (border *ViewBorders) AllTheSame() bool { return border.Top.Style == border.Right.Style && border.Top.Style == border.Left.Style && border.Top.Style == border.Bottom.Style && border.Top.Color == border.Right.Color && border.Top.Color == border.Left.Color && border.Top.Color == border.Bottom.Color && border.Top.Width.Equal(border.Right.Width) && border.Top.Width.Equal(border.Left.Width) && border.Top.Width.Equal(border.Bottom.Width) } func getBorder(style Properties, tag string) BorderProperty { if value := style.Get(tag); value != nil { if border, ok := value.(BorderProperty); ok { return border } } return nil }