rui_orig/viewStyle.go

419 lines
11 KiB
Go

package rui
import (
"fmt"
"strconv"
"strings"
)
// ViewStyle interface of the style of view
type ViewStyle interface {
Properties
cssViewStyle(buffer cssBuilder, session Session)
}
type viewStyle struct {
propertyList
transitions map[string]Animation
}
// Range defines range limits. The First and Last value are included in the range
type Range struct {
First, Last int
}
// String returns a string representation of the Range struct
func (r Range) String() string {
if r.First == r.Last {
return fmt.Sprintf("%d", r.First)
}
return fmt.Sprintf("%d:%d", r.First, r.Last)
}
func (r *Range) setValue(value string) bool {
var err error
if strings.Contains(value, ":") {
values := strings.Split(value, ":")
if len(values) != 2 {
ErrorLog("Invalid range value: " + value)
return false
}
if r.First, err = strconv.Atoi(strings.Trim(values[0], " \t\n\r")); err != nil {
ErrorLog(`Invalid first range value "` + value + `" (` + err.Error() + ")")
return false
}
if r.Last, err = strconv.Atoi(strings.Trim(values[1], " \t\n\r")); err != nil {
ErrorLog(`Invalid last range value "` + value + `" (` + err.Error() + ")")
return false
}
return true
}
if r.First, err = strconv.Atoi(value); err != nil {
ErrorLog(`Invalid range value "` + value + `" (` + err.Error() + ")")
return false
}
r.Last = r.First
return true
}
func (style *viewStyle) init() {
style.propertyList.init()
//style.shadows = []ViewShadow{}
style.transitions = map[string]Animation{}
}
// NewViewStyle create new ViewStyle object
func NewViewStyle(params Params) ViewStyle {
style := new(viewStyle)
style.init()
for tag, value := range params {
style.Set(tag, value)
}
return style
}
func (style *viewStyle) cssTextDecoration(session Session) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
noDecoration := false
if strikethrough, ok := boolProperty(style, Strikethrough, session); ok {
if strikethrough {
buffer.WriteString("line-through")
}
noDecoration = true
}
if overline, ok := boolProperty(style, Overline, session); ok {
if overline {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString("overline")
}
noDecoration = true
}
if underline, ok := boolProperty(style, Underline, session); ok {
if underline {
if buffer.Len() > 0 {
buffer.WriteRune(' ')
}
buffer.WriteString("underline")
}
noDecoration = true
}
if buffer.Len() == 0 && noDecoration {
return "none"
}
return buffer.String()
}
func split4Values(text string) []string {
values := strings.Split(text, ",")
count := len(values)
switch count {
case 1, 4:
return values
case 2:
if strings.Trim(values[1], " \t\r\n") == "" {
return values[:1]
}
case 5:
if strings.Trim(values[4], " \t\r\n") != "" {
return values[:4]
}
}
return []string{}
}
func (style *viewStyle) backgroundCSS(session Session) string {
if value, ok := style.properties[Background]; ok {
if backgrounds, ok := value.([]BackgroundElement); ok {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, background := range backgrounds {
if value := background.cssStyle(session); value != "" {
if buffer.Len() > 0 {
buffer.WriteString(", ")
}
buffer.WriteString(value)
}
}
if buffer.Len() > 0 {
return buffer.String()
}
}
}
return ""
}
func (style *viewStyle) cssViewStyle(builder cssBuilder, session Session) {
if margin, ok := boundsProperty(style, Margin, session); ok {
margin.cssValue(Margin, builder)
}
if padding, ok := boundsProperty(style, Padding, session); ok {
padding.cssValue(Padding, builder)
}
if border := getBorder(style, Border); border != nil {
border.cssStyle(builder, session)
border.cssWidth(builder, session)
border.cssColor(builder, session)
}
radius := getRadius(style, session)
radius.cssValue(builder)
if outline := getOutline(style); outline != nil {
outline.ViewOutline(session).cssValue(builder)
}
if z, ok := intProperty(style, ZIndex, session, 0); ok {
builder.add(ZIndex, strconv.Itoa(z))
}
if opacity, ok := floatProperty(style, Opacity, session, 1.0); ok && opacity >= 0 && opacity <= 1 {
builder.add(Opacity, strconv.FormatFloat(opacity, 'f', 3, 32))
}
if n, ok := intProperty(style, ColumnCount, session, 0); ok && n > 0 {
builder.add(ColumnCount, strconv.Itoa(n))
}
for _, tag := range []string{
Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight, Left, Right, Top, Bottom,
TextSize, TextIndent, LetterSpacing, WordSpacing, LineHeight, TextLineThickness,
GridRowGap, GridColumnGap, ColumnGap, ColumnWidth} {
if size, ok := sizeProperty(style, tag, session); ok && size.Type != Auto {
cssTag, ok := sizeProperties[tag]
if !ok {
cssTag = tag
}
builder.add(cssTag, size.cssString(""))
}
}
colorProperties := []struct{ property, cssTag string }{
{BackgroundColor, BackgroundColor},
{TextColor, "color"},
{TextLineColor, "text-decoration-color"},
}
for _, p := range colorProperties {
if color, ok := colorProperty(style, p.property, session); ok && color != 0 {
builder.add(p.cssTag, color.cssString())
}
}
if value, ok := enumProperty(style, BackgroundClip, session, 0); ok {
builder.add(BackgroundClip, enumProperties[BackgroundClip].values[value])
}
if background := style.backgroundCSS(session); background != "" {
builder.add("background", background)
}
if font, ok := stringProperty(style, FontName, session); ok && font != "" {
builder.add(`font-family`, font)
}
writingMode := 0
for _, tag := range []string{
TextAlign, TextTransform, TextWeight, TextLineStyle, WritingMode, TextDirection,
VerticalTextOrientation, CellVerticalAlign, CellHorizontalAlign, Cursor, WhiteSpace,
WordBreak, TextOverflow, Float, TableVerticalAlign} {
if data, ok := enumProperties[tag]; ok {
if tag != VerticalTextOrientation || (writingMode != VerticalLeftToRight && writingMode != VerticalRightToLeft) {
if value, ok := enumProperty(style, tag, session, 0); ok {
cssValue := data.values[value]
if cssValue != "" {
builder.add(data.cssTag, cssValue)
}
if tag == WritingMode {
writingMode = value
}
}
}
}
}
for _, prop := range []struct{ tag, cssTag, off, on string }{
{tag: Italic, cssTag: "font-style", off: "normal", on: "italic"},
{tag: SmallCaps, cssTag: "font-variant", off: "normal", on: "small-caps"},
} {
if flag, ok := boolProperty(style, prop.tag, session); ok {
if flag {
builder.add(prop.cssTag, prop.on)
} else {
builder.add(prop.cssTag, prop.off)
}
}
}
if text := style.cssTextDecoration(session); text != "" {
builder.add("text-decoration", text)
}
if css := shadowCSS(style, Shadow, session); css != "" {
builder.add("box-shadow", css)
}
if css := shadowCSS(style, TextShadow, session); css != "" {
builder.add("text-shadow", css)
}
if value, ok := style.properties[ColumnSeparator]; ok {
if separator, ok := value.(ColumnSeparatorProperty); ok {
if css := separator.cssValue(session); css != "" {
builder.add("column-rule", css)
}
}
}
if avoid, ok := boolProperty(style, AvoidBreak, session); ok {
if avoid {
builder.add("break-inside", "avoid")
} else {
builder.add("break-inside", "auto")
}
}
wrap, _ := enumProperty(style, Wrap, session, 0)
orientation, ok := getOrientation(style, session)
if ok || wrap > 0 {
cssText := enumProperties[Orientation].cssValues[orientation]
switch wrap {
case WrapOn:
cssText += " wrap"
case WrapReverse:
cssText += " wrap-reverse"
}
builder.add(`flex-flow`, cssText)
}
rows := (orientation == StartToEndOrientation || orientation == EndToStartOrientation)
var hAlignTag, vAlignTag string
if rows {
hAlignTag = `justify-content`
vAlignTag = `align-items`
} else {
hAlignTag = `align-items`
vAlignTag = `justify-content`
}
if align, ok := enumProperty(style, HorizontalAlign, session, LeftAlign); ok {
switch align {
case LeftAlign:
if (!rows && wrap == WrapReverse) || orientation == EndToStartOrientation {
builder.add(hAlignTag, `flex-end`)
} else {
builder.add(hAlignTag, `flex-start`)
}
case RightAlign:
if (!rows && wrap == WrapReverse) || orientation == EndToStartOrientation {
builder.add(hAlignTag, `flex-start`)
} else {
builder.add(hAlignTag, `flex-end`)
}
case CenterAlign:
builder.add(hAlignTag, `center`)
case StretchAlign:
if rows {
builder.add(hAlignTag, `space-between`)
} else {
builder.add(hAlignTag, `stretch`)
}
}
}
if align, ok := enumProperty(style, VerticalAlign, session, LeftAlign); ok {
switch align {
case TopAlign:
if (rows && wrap == WrapReverse) || orientation == BottomUpOrientation {
builder.add(vAlignTag, `flex-end`)
} else {
builder.add(vAlignTag, `flex-start`)
}
case BottomAlign:
if (rows && wrap == WrapReverse) || orientation == BottomUpOrientation {
builder.add(vAlignTag, `flex-start`)
} else {
builder.add(vAlignTag, `flex-end`)
}
case CenterAlign:
builder.add(vAlignTag, `center`)
case StretchAlign:
if rows {
builder.add(hAlignTag, `stretch`)
} else {
builder.add(hAlignTag, `space-between`)
}
}
}
if r, ok := rangeProperty(style, Row, session); ok {
builder.add("grid-row-start", strconv.Itoa(r.First+1))
builder.add("grid-row-end", strconv.Itoa(r.Last+2))
}
if r, ok := rangeProperty(style, Column, session); ok {
builder.add("grid-column-start", strconv.Itoa(r.First+1))
builder.add("grid-column-end", strconv.Itoa(r.Last+2))
}
if text := style.gridCellSizesCSS(CellWidth, session); text != "" {
builder.add(`grid-template-columns`, text)
}
if text := style.gridCellSizesCSS(CellHeight, session); text != "" {
builder.add(`grid-template-rows`, text)
}
style.writeViewTransformCSS(builder, session)
if clip := getClipShape(style, Clip, session); clip != nil && clip.valid(session) {
builder.add(`clip-path`, clip.cssStyle(session))
}
if clip := getClipShape(style, ShapeOutside, session); clip != nil && clip.valid(session) {
builder.add(`shape-outside`, clip.cssStyle(session))
}
if value := style.getRaw(Filter); value != nil {
if filter, ok := value.(ViewFilter); ok {
if text := filter.cssStyle(session); text != "" {
builder.add(`filter`, text)
}
}
}
if transition := style.transitionCSS(session); transition != "" {
builder.add(`transition`, transition)
}
if animation := style.animationCSS(session); animation != "" {
builder.add(AnimationTag, animation)
}
if pause, ok := boolProperty(style, AnimationPaused, session); ok {
if pause {
builder.add(`animation-play-state`, `paused`)
} else {
builder.add(`animation-play-state`, `running`)
}
}
}