rui_orig/border.go

874 lines
23 KiB
Go

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
}