rui_orig/theme.go

330 lines
7.8 KiB
Go

package rui
import (
"sort"
"strconv"
"strings"
)
const (
defaultMedia = 0
portraitMedia = 1
landscapeMedia = 2
)
type mediaStyle struct {
orientation int
width int
height int
styles map[string]DataObject
}
func (rule mediaStyle) cssText() string {
builder := allocStringBuilder()
defer freeStringBuilder(builder)
switch rule.orientation {
case portraitMedia:
builder.WriteString(" and (orientation: portrait)")
case landscapeMedia:
builder.WriteString(" and (orientation: landscape)")
}
if rule.width > 0 {
builder.WriteString(" and (max-width: ")
builder.WriteString(strconv.Itoa(rule.width))
builder.WriteString("px)")
}
if rule.height > 0 {
builder.WriteString(" and (max-height: ")
builder.WriteString(strconv.Itoa(rule.height))
builder.WriteString("px)")
}
return builder.String()
}
func parseMediaRule(text string) (mediaStyle, bool) {
rule := mediaStyle{orientation: defaultMedia, width: 0, height: 0, styles: map[string]DataObject{}}
elements := strings.Split(text, ":")
for i := 1; i < len(elements); i++ {
switch element := elements[i]; element {
case "portrait":
if rule.orientation != defaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false
}
rule.orientation = portraitMedia
case "landscape":
if rule.orientation != defaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false
}
rule.orientation = landscapeMedia
default:
elementSize := func(name string) (int, bool) {
if strings.HasPrefix(element, name) {
size, err := strconv.Atoi(element[len(name):])
if err == nil && size > 0 {
return size, true
}
ErrorLogF(`Invalid style section name "%s": %s`, text, err.Error())
return 0, false
}
return 0, true
}
if size, ok := elementSize("width"); !ok || size > 0 {
if !ok {
return rule, false
}
if rule.width != 0 {
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
return rule, false
}
rule.width = size
} else if size, ok := elementSize("height"); !ok || size > 0 {
if !ok {
return rule, false
}
if rule.height != 0 {
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
return rule, false
}
rule.height = size
} else {
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
return rule, false
}
}
}
return rule, true
}
type theme struct {
name string
constants map[string]string
touchConstants map[string]string
colors map[string]string
darkColors map[string]string
styles map[string]DataObject
mediaStyles []mediaStyle
}
var defaultTheme = new(theme)
func newTheme(text string) (*theme, bool) {
result := new(theme)
result.init()
ok := result.addText(text)
return result, ok
}
func (theme *theme) init() {
theme.constants = map[string]string{}
theme.touchConstants = map[string]string{}
theme.colors = map[string]string{}
theme.darkColors = map[string]string{}
theme.styles = map[string]DataObject{}
theme.mediaStyles = []mediaStyle{}
}
func (theme *theme) concat(anotherTheme *theme) {
if theme.constants == nil {
theme.init()
}
for tag, constant := range anotherTheme.constants {
theme.constants[tag] = constant
}
for tag, constant := range anotherTheme.touchConstants {
theme.touchConstants[tag] = constant
}
for tag, color := range anotherTheme.colors {
theme.colors[tag] = color
}
for tag, color := range anotherTheme.darkColors {
theme.darkColors[tag] = color
}
for tag, style := range anotherTheme.styles {
theme.styles[tag] = style
}
for _, anotherMedia := range anotherTheme.mediaStyles {
exists := false
for _, media := range theme.mediaStyles {
if anotherMedia.height == media.height &&
anotherMedia.width == media.width &&
anotherMedia.orientation == media.orientation {
for tag, style := range anotherMedia.styles {
media.styles[tag] = style
}
exists = true
break
}
}
if !exists {
theme.mediaStyles = append(theme.mediaStyles, anotherMedia)
}
}
}
func (theme *theme) cssText(session Session) string {
if theme.styles == nil {
theme.init()
return ""
}
var builder cssStyleBuilder
builder.init()
for tag, obj := range theme.styles {
var style viewStyle
style.init()
parseProperties(&style, obj)
builder.startStyle(tag)
style.cssViewStyle(&builder, session, nil)
builder.endStyle()
}
for _, media := range theme.mediaStyles {
builder.startMedia(media.cssText())
for tag, obj := range media.styles {
var style viewStyle
style.init()
parseProperties(&style, obj)
builder.startStyle(tag)
style.cssViewStyle(&builder, session, nil)
builder.endStyle()
}
builder.endMedia()
}
return builder.finish()
}
func (theme *theme) addText(themeText string) bool {
data := ParseDataText(themeText)
if data == nil {
return false
}
theme.addData(data)
return true
}
func (theme *theme) addData(data DataObject) {
if theme.constants == nil {
theme.init()
}
if data.IsObject() && data.Tag() == "theme" {
theme.parseThemeData(data)
}
}
func (theme *theme) parseThemeData(data DataObject) {
count := data.PropertyCount()
for i := 0; i < count; i++ {
if d := data.Property(i); d != nil {
switch tag := d.Tag(); tag {
case "constants":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil {
objCount := obj.PropertyCount()
for k := 0; k < objCount; k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
theme.constants[prop.Tag()] = prop.Text()
}
}
}
}
case "constants:touch":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil {
objCount := obj.PropertyCount()
for k := 0; k < objCount; k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
theme.touchConstants[prop.Tag()] = prop.Text()
}
}
}
}
case "colors":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil {
objCount := obj.PropertyCount()
for k := 0; k < objCount; k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
theme.colors[prop.Tag()] = prop.Text()
}
}
}
}
case "colors:dark":
if d.Type() == ObjectNode {
if obj := d.Object(); obj != nil {
objCount := obj.PropertyCount()
for k := 0; k < objCount; k++ {
if prop := obj.Property(k); prop != nil && prop.Type() == TextNode {
theme.darkColors[prop.Tag()] = prop.Text()
}
}
}
}
case "styles":
if d.Type() == ArrayNode {
arraySize := d.ArraySize()
for k := 0; k < arraySize; k++ {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil {
theme.styles[obj.Tag()] = obj
}
}
}
}
default:
if d.Type() == ArrayNode && strings.HasPrefix(tag, "styles:") {
if rule, ok := parseMediaRule(tag); ok {
arraySize := d.ArraySize()
for k := 0; k < arraySize; k++ {
if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil {
rule.styles[obj.Tag()] = obj
}
}
}
theme.mediaStyles = append(theme.mediaStyles, rule)
}
}
}
}
}
if len(theme.mediaStyles) > 0 {
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
if theme.mediaStyles[i].orientation != theme.mediaStyles[j].orientation {
return theme.mediaStyles[i].orientation < theme.mediaStyles[j].orientation
}
if theme.mediaStyles[i].width != theme.mediaStyles[j].width {
return theme.mediaStyles[i].width < theme.mediaStyles[j].width
}
return theme.mediaStyles[i].height < theme.mediaStyles[j].height
})
}
}