2021-09-07 17:36:50 +03:00
|
|
|
package rui
|
|
|
|
|
|
|
|
import (
|
2022-05-23 15:22:14 +03:00
|
|
|
"fmt"
|
2021-09-07 17:36:50 +03:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2022-05-12 11:05:50 +03:00
|
|
|
DefaultMedia = 0
|
|
|
|
PortraitMedia = 1
|
|
|
|
LandscapeMedia = 2
|
2021-09-07 17:36:50 +03:00
|
|
|
)
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
type mediaStyle struct {
|
|
|
|
orientation int
|
|
|
|
maxWidth int
|
|
|
|
maxHeight int
|
|
|
|
styles map[string]ViewStyle
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
type theme struct {
|
|
|
|
name string
|
|
|
|
constants map[string]string
|
|
|
|
touchConstants map[string]string
|
|
|
|
colors map[string]string
|
|
|
|
darkColors map[string]string
|
|
|
|
images map[string]string
|
|
|
|
darkImages map[string]string
|
2022-05-23 15:22:14 +03:00
|
|
|
styles map[string]ViewStyle
|
2022-05-25 20:03:32 +03:00
|
|
|
mediaStyles []mediaStyle
|
2022-05-12 11:05:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Theme interface {
|
2022-05-23 15:22:14 +03:00
|
|
|
fmt.Stringer
|
2022-05-12 11:05:50 +03:00
|
|
|
Name() string
|
|
|
|
Constant(tag string) (string, string)
|
|
|
|
SetConstant(tag string, value, touchUIValue string)
|
|
|
|
// ConstantTags returns the list of all available constants
|
|
|
|
ConstantTags() []string
|
|
|
|
Color(tag string) (string, string)
|
|
|
|
SetColor(tag, color, darkUIColor string)
|
|
|
|
// ColorTags returns the list of all available color constants
|
|
|
|
ColorTags() []string
|
|
|
|
Image(tag string) (string, string)
|
|
|
|
SetImage(tag, image, darkUIImage string)
|
|
|
|
// ImageConstantTags returns the list of all available image constants
|
|
|
|
ImageConstantTags() []string
|
2022-05-25 20:03:32 +03:00
|
|
|
Style(tag string) ViewStyle
|
|
|
|
SetStyle(tag string, style ViewStyle)
|
|
|
|
MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle
|
|
|
|
SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle)
|
|
|
|
StyleTags() []string
|
Added some properties and functions
* Added "resize", "grid-auto-flow", "caret-color", and "backdrop-filter" properties
* Added BlurView, BlurViewByID, GetResize, GetGridAutoFlow, GetCaretColor, GetBackdropFilter functions
* The "warp" property for ListView and ListLayout renamed to "list-warp"
* The "warp" property for EditView renamed to "edit-warp"
* Added CertFile and KeyFile optional fields to the AppParams struct.If they are set, then an https connection is created, otherwise http.
2022-06-07 13:07:10 +03:00
|
|
|
MediaStyles(tag string) []struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
}
|
2022-05-16 17:43:06 +03:00
|
|
|
Append(anotherTheme Theme)
|
2022-05-12 11:05:50 +03:00
|
|
|
|
|
|
|
constant(tag string, touchUI bool) string
|
|
|
|
color(tag string, darkUI bool) string
|
|
|
|
image(tag string, darkUI bool) string
|
2022-05-23 15:22:14 +03:00
|
|
|
style(tag string) ViewStyle
|
2022-05-12 11:05:50 +03:00
|
|
|
cssText(session Session) string
|
|
|
|
data() *theme
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
func (rule mediaStyle) cssText() string {
|
2021-09-07 17:36:50 +03:00
|
|
|
builder := allocStringBuilder()
|
|
|
|
defer freeStringBuilder(builder)
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
switch rule.orientation {
|
2022-05-12 11:05:50 +03:00
|
|
|
case PortraitMedia:
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString(" and (orientation: portrait)")
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
case LandscapeMedia:
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString(" and (orientation: landscape)")
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.maxWidth > 0 {
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString(" and (max-width: ")
|
2022-05-25 20:03:32 +03:00
|
|
|
builder.WriteString(strconv.Itoa(rule.maxWidth))
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString("px)")
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.maxHeight > 0 {
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString(" and (max-height: ")
|
2022-05-25 20:03:32 +03:00
|
|
|
builder.WriteString(strconv.Itoa(rule.maxHeight))
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.WriteString("px)")
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.String()
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
func parseMediaRule(text string) (mediaStyle, bool) {
|
|
|
|
rule := mediaStyle{
|
|
|
|
orientation: DefaultMedia,
|
|
|
|
maxWidth: 0,
|
|
|
|
maxHeight: 0,
|
|
|
|
styles: map[string]ViewStyle{},
|
2022-05-12 11:05:50 +03:00
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
elements := strings.Split(text, ":")
|
|
|
|
for i := 1; i < len(elements); i++ {
|
|
|
|
switch element := elements[i]; element {
|
|
|
|
case "portrait":
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.orientation != DefaultMedia {
|
2021-09-07 17:36:50 +03:00
|
|
|
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
|
|
|
return rule, false
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
rule.orientation = PortraitMedia
|
2021-09-07 17:36:50 +03:00
|
|
|
|
|
|
|
case "landscape":
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.orientation != DefaultMedia {
|
2021-09-07 17:36:50 +03:00
|
|
|
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
|
|
|
|
return rule, false
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
rule.orientation = LandscapeMedia
|
2021-09-07 17:36:50 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.maxWidth != 0 {
|
2021-09-07 17:36:50 +03:00
|
|
|
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
|
|
|
|
return rule, false
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
rule.maxWidth = size
|
2021-09-07 17:36:50 +03:00
|
|
|
} else if size, ok := elementSize("height"); !ok || size > 0 {
|
|
|
|
if !ok {
|
|
|
|
return rule, false
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
if rule.maxHeight != 0 {
|
2021-09-07 17:36:50 +03:00
|
|
|
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
|
|
|
|
return rule, false
|
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
rule.maxHeight = size
|
2021-09-07 17:36:50 +03:00
|
|
|
} else {
|
|
|
|
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
|
|
|
|
return rule, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return rule, true
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
var defaultTheme = NewTheme("")
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
func NewTheme(name string) Theme {
|
|
|
|
result := new(theme)
|
|
|
|
result.init()
|
|
|
|
result.name = name
|
|
|
|
return result
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
func CreateThemeFromText(text string) (Theme, bool) {
|
2021-09-07 17:36:50 +03:00
|
|
|
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{}
|
2022-04-23 18:13:35 +03:00
|
|
|
theme.images = map[string]string{}
|
|
|
|
theme.darkImages = map[string]string{}
|
2022-05-23 15:22:14 +03:00
|
|
|
theme.styles = map[string]ViewStyle{}
|
2022-05-25 20:03:32 +03:00
|
|
|
theme.mediaStyles = []mediaStyle{}
|
2022-05-12 11:05:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) Name() string {
|
|
|
|
return theme.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) Constant(tag string) (string, string) {
|
|
|
|
return theme.constants[tag], theme.touchConstants[tag]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) SetConstant(tag, value, touchUIValue string) {
|
|
|
|
value = strings.Trim(value, " \t")
|
|
|
|
if value == "" {
|
|
|
|
delete(theme.constants, tag)
|
|
|
|
delete(theme.touchConstants, tag)
|
|
|
|
} else {
|
|
|
|
theme.constants[tag] = value
|
|
|
|
touchUIValue = strings.Trim(touchUIValue, " \t")
|
|
|
|
if touchUIValue == "" {
|
|
|
|
delete(theme.touchConstants, tag)
|
|
|
|
} else {
|
|
|
|
theme.touchConstants[tag] = touchUIValue
|
|
|
|
}
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
func (theme *theme) Color(tag string) (string, string) {
|
|
|
|
return theme.colors[tag], theme.darkColors[tag]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) SetColor(tag, color, darkUIColor string) {
|
|
|
|
color = strings.Trim(color, " \t")
|
|
|
|
if color == "" {
|
|
|
|
delete(theme.colors, tag)
|
|
|
|
delete(theme.darkColors, tag)
|
|
|
|
} else {
|
|
|
|
theme.colors[tag] = color
|
|
|
|
darkUIColor = strings.Trim(darkUIColor, " \t")
|
|
|
|
if darkUIColor == "" {
|
|
|
|
delete(theme.darkColors, tag)
|
|
|
|
} else {
|
|
|
|
theme.darkColors[tag] = darkUIColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) Image(tag string) (string, string) {
|
|
|
|
return theme.images[tag], theme.darkImages[tag]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) SetImage(tag, image, darkUIImage string) {
|
|
|
|
image = strings.Trim(image, " \t")
|
|
|
|
if image == "" {
|
|
|
|
delete(theme.images, tag)
|
|
|
|
delete(theme.darkImages, tag)
|
|
|
|
} else {
|
|
|
|
theme.images[tag] = image
|
|
|
|
darkUIImage = strings.Trim(darkUIImage, " \t")
|
|
|
|
if darkUIImage == "" {
|
|
|
|
delete(theme.darkImages, tag)
|
|
|
|
} else {
|
|
|
|
theme.darkImages[tag] = darkUIImage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
func (theme *theme) Style(tag string) ViewStyle {
|
|
|
|
if style, ok := theme.styles[tag]; ok {
|
|
|
|
return style
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) SetStyle(tag string, style ViewStyle) {
|
|
|
|
if style != nil {
|
|
|
|
theme.styles[tag] = style
|
|
|
|
} else {
|
|
|
|
delete(theme.styles, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) MediaStyle(tag string, orientation, maxWidth, maxHeight int) ViewStyle {
|
|
|
|
for _, styles := range theme.mediaStyles {
|
|
|
|
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
|
|
|
if style, ok := styles.styles[tag]; ok {
|
|
|
|
return style
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
Added some properties and functions
* Added "resize", "grid-auto-flow", "caret-color", and "backdrop-filter" properties
* Added BlurView, BlurViewByID, GetResize, GetGridAutoFlow, GetCaretColor, GetBackdropFilter functions
* The "warp" property for ListView and ListLayout renamed to "list-warp"
* The "warp" property for EditView renamed to "edit-warp"
* Added CertFile and KeyFile optional fields to the AppParams struct.If they are set, then an https connection is created, otherwise http.
2022-06-07 13:07:10 +03:00
|
|
|
if orientation == 0 && maxWidth <= 0 && maxHeight <= 0 {
|
|
|
|
return theme.style(tag)
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) SetMediaStyle(tag string, orientation, maxWidth, maxHeight int, style ViewStyle) {
|
|
|
|
if maxWidth < 0 {
|
|
|
|
maxWidth = 0
|
|
|
|
}
|
|
|
|
if maxHeight < 0 {
|
|
|
|
maxHeight = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if orientation == DefaultMedia && maxWidth == 0 && maxHeight == 0 {
|
|
|
|
theme.SetStyle(tag, style)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, styles := range theme.mediaStyles {
|
|
|
|
if styles.orientation == orientation && styles.maxWidth == maxWidth && styles.maxHeight == maxHeight {
|
|
|
|
if style != nil {
|
|
|
|
theme.mediaStyles[i].styles[tag] = style
|
|
|
|
} else {
|
|
|
|
delete(theme.mediaStyles[i].styles, tag)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if style != nil {
|
|
|
|
theme.mediaStyles = append(theme.mediaStyles, mediaStyle{
|
|
|
|
orientation: orientation,
|
|
|
|
maxWidth: maxWidth,
|
|
|
|
maxHeight: maxHeight,
|
|
|
|
styles: map[string]ViewStyle{tag: style},
|
|
|
|
})
|
|
|
|
theme.sortMediaStyles()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
func (theme *theme) ConstantTags() []string {
|
|
|
|
keys := make([]string, 0, len(theme.constants))
|
|
|
|
for k := range theme.constants {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
for tag := range theme.touchConstants {
|
|
|
|
if _, ok := theme.constants[tag]; !ok {
|
|
|
|
keys = append(keys, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) ColorTags() []string {
|
|
|
|
keys := make([]string, 0, len(theme.colors))
|
|
|
|
for k := range theme.colors {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
for tag := range theme.darkColors {
|
|
|
|
if _, ok := theme.colors[tag]; !ok {
|
|
|
|
keys = append(keys, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) ImageConstantTags() []string {
|
|
|
|
keys := make([]string, 0, len(theme.colors))
|
|
|
|
for k := range theme.images {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
for tag := range theme.darkImages {
|
|
|
|
if _, ok := theme.images[tag]; !ok {
|
|
|
|
keys = append(keys, tag)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(keys)
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
func (theme *theme) StyleTags() []string {
|
|
|
|
keys := make([]string, 0, len(theme.styles)*2)
|
|
|
|
|
|
|
|
appendTag := func(k string) {
|
|
|
|
n := sort.SearchStrings(keys, k)
|
|
|
|
if n >= len(keys) {
|
|
|
|
keys = append(keys, k)
|
|
|
|
} else if keys[n] != k {
|
|
|
|
if n == 0 {
|
|
|
|
keys = append([]string{k}, keys...)
|
|
|
|
} else {
|
|
|
|
keys = append(keys[:n+1], keys[n:]...)
|
|
|
|
keys[n] = k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for k := range theme.styles {
|
|
|
|
if index := strings.IndexRune(k, ':'); index < 0 {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
for k := range theme.styles {
|
|
|
|
if index := strings.IndexRune(k, ':'); index > 0 {
|
|
|
|
appendTag(k[:index])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, media := range theme.mediaStyles {
|
|
|
|
for k := range media.styles {
|
|
|
|
index := strings.IndexRune(k, ':')
|
|
|
|
if index > 0 {
|
|
|
|
appendTag(k[:index])
|
|
|
|
} else if index < 0 {
|
|
|
|
appendTag(k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
Added some properties and functions
* Added "resize", "grid-auto-flow", "caret-color", and "backdrop-filter" properties
* Added BlurView, BlurViewByID, GetResize, GetGridAutoFlow, GetCaretColor, GetBackdropFilter functions
* The "warp" property for ListView and ListLayout renamed to "list-warp"
* The "warp" property for EditView renamed to "edit-warp"
* Added CertFile and KeyFile optional fields to the AppParams struct.If they are set, then an https connection is created, otherwise http.
2022-06-07 13:07:10 +03:00
|
|
|
func (theme *theme) MediaStyles(tag string) []struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
} {
|
|
|
|
result := []struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
}{}
|
|
|
|
|
|
|
|
prefix := tag + ":"
|
|
|
|
prefixLen := len(prefix)
|
|
|
|
for themeTag := range theme.styles {
|
|
|
|
if strings.HasPrefix(themeTag, prefix) {
|
|
|
|
result = append(result, struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
}{
|
|
|
|
Selectors: themeTag[prefixLen:],
|
|
|
|
Orientation: DefaultMedia,
|
|
|
|
MaxWidth: 0,
|
|
|
|
MaxHeight: 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, media := range theme.mediaStyles {
|
|
|
|
if _, ok := media.styles[tag]; ok {
|
|
|
|
result = append(result, struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
}{
|
|
|
|
Selectors: "",
|
|
|
|
Orientation: media.orientation,
|
|
|
|
MaxWidth: media.maxWidth,
|
|
|
|
MaxHeight: media.maxHeight,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for themeTag := range media.styles {
|
|
|
|
if strings.HasPrefix(themeTag, prefix) {
|
|
|
|
result = append(result, struct {
|
|
|
|
Selectors string
|
|
|
|
Orientation, MaxWidth, MaxHeight int
|
|
|
|
}{
|
|
|
|
Selectors: themeTag[prefixLen:],
|
|
|
|
Orientation: media.orientation,
|
|
|
|
MaxWidth: media.maxWidth,
|
|
|
|
MaxHeight: media.maxHeight,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
func (theme *theme) data() *theme {
|
|
|
|
return theme
|
|
|
|
}
|
|
|
|
|
2022-05-16 17:43:06 +03:00
|
|
|
func (theme *theme) Append(anotherTheme Theme) {
|
2021-09-07 17:36:50 +03:00
|
|
|
if theme.constants == nil {
|
|
|
|
theme.init()
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
another := anotherTheme.data()
|
|
|
|
for tag, constant := range another.constants {
|
2021-09-07 17:36:50 +03:00
|
|
|
theme.constants[tag] = constant
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, constant := range another.touchConstants {
|
2021-09-07 17:36:50 +03:00
|
|
|
theme.touchConstants[tag] = constant
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, color := range another.colors {
|
2021-09-07 17:36:50 +03:00
|
|
|
theme.colors[tag] = color
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, color := range another.darkColors {
|
2021-09-07 17:36:50 +03:00
|
|
|
theme.darkColors[tag] = color
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, image := range another.images {
|
2022-04-23 18:13:35 +03:00
|
|
|
theme.images[tag] = image
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, image := range another.darkImages {
|
2022-04-23 18:13:35 +03:00
|
|
|
theme.darkImages[tag] = image
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for tag, style := range another.styles {
|
2021-09-07 17:36:50 +03:00
|
|
|
theme.styles[tag] = style
|
|
|
|
}
|
|
|
|
|
2022-05-12 11:05:50 +03:00
|
|
|
for _, anotherMedia := range another.mediaStyles {
|
2021-09-07 17:36:50 +03:00
|
|
|
exists := false
|
|
|
|
for _, media := range theme.mediaStyles {
|
2022-05-25 20:03:32 +03:00
|
|
|
if anotherMedia.maxHeight == media.maxHeight &&
|
|
|
|
anotherMedia.maxWidth == media.maxWidth &&
|
|
|
|
anotherMedia.orientation == media.orientation {
|
|
|
|
for tag, style := range anotherMedia.styles {
|
|
|
|
media.styles[tag] = style
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
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()
|
|
|
|
|
2022-05-23 15:22:14 +03:00
|
|
|
for tag, style := range theme.styles {
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.startStyle(tag)
|
2021-10-04 17:58:17 +03:00
|
|
|
style.cssViewStyle(&builder, session)
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.endStyle()
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, media := range theme.mediaStyles {
|
|
|
|
builder.startMedia(media.cssText())
|
2022-05-25 20:03:32 +03:00
|
|
|
for tag, style := range media.styles {
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.startStyle(tag)
|
2021-10-04 17:58:17 +03:00
|
|
|
style.cssViewStyle(&builder, session)
|
2021-09-07 17:36:50 +03:00
|
|
|
builder.endStyle()
|
|
|
|
}
|
|
|
|
builder.endMedia()
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) addText(themeText string) bool {
|
|
|
|
if theme.constants == nil {
|
|
|
|
theme.init()
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
data := ParseDataText(themeText)
|
|
|
|
if data == nil || !data.IsObject() || data.Tag() != "theme" {
|
|
|
|
return false
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
count := data.PropertyCount()
|
|
|
|
|
2022-05-23 15:22:14 +03:00
|
|
|
objToStyle := func(obj DataObject) ViewStyle {
|
2022-04-14 12:05:45 +03:00
|
|
|
params := Params{}
|
|
|
|
for i := 0; i < obj.PropertyCount(); i++ {
|
|
|
|
if node := obj.Property(i); node != nil {
|
|
|
|
switch node.Type() {
|
|
|
|
case ArrayNode:
|
|
|
|
params[node.Tag()] = node.ArrayElements()
|
|
|
|
|
|
|
|
case ObjectNode:
|
|
|
|
params[node.Tag()] = node.Object()
|
|
|
|
|
|
|
|
default:
|
|
|
|
params[node.Tag()] = node.Text()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-23 15:22:14 +03:00
|
|
|
return NewViewStyle(params)
|
2022-04-14 12:05:45 +03:00
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-23 18:13:35 +03:00
|
|
|
case "images":
|
|
|
|
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.images[prop.Tag()] = prop.Text()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case "images: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.darkImages[prop.Tag()] = prop.Text()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
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 {
|
2022-05-23 15:22:14 +03:00
|
|
|
theme.styles[obj.Tag()] = objToStyle(obj)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2022-05-25 20:03:32 +03:00
|
|
|
rule.styles[obj.Tag()] = objToStyle(obj)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
theme.mediaStyles = append(theme.mediaStyles, rule)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
theme.sortMediaStyles()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) sortMediaStyles() {
|
|
|
|
if len(theme.mediaStyles) > 1 {
|
2021-09-07 17:36:50 +03:00
|
|
|
sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
|
2022-05-25 20:03:32 +03:00
|
|
|
if theme.mediaStyles[i].orientation != theme.mediaStyles[j].orientation {
|
|
|
|
return theme.mediaStyles[i].orientation < theme.mediaStyles[j].orientation
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
if theme.mediaStyles[i].maxWidth != theme.mediaStyles[j].maxWidth {
|
|
|
|
return theme.mediaStyles[i].maxWidth < theme.mediaStyles[j].maxWidth
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2022-05-25 20:03:32 +03:00
|
|
|
return theme.mediaStyles[i].maxHeight < theme.mediaStyles[j].maxHeight
|
2021-09-07 17:36:50 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-05-12 11:05:50 +03:00
|
|
|
|
|
|
|
func (theme *theme) constant(tag string, touchUI bool) string {
|
|
|
|
result := ""
|
|
|
|
if touchUI {
|
|
|
|
if value, ok := theme.touchConstants[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if result == "" {
|
|
|
|
if value, ok := theme.constants[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) color(tag string, darkUI bool) string {
|
|
|
|
result := ""
|
|
|
|
if darkUI {
|
|
|
|
if value, ok := theme.darkColors[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if result == "" {
|
|
|
|
if value, ok := theme.colors[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) image(tag string, darkUI bool) string {
|
|
|
|
result := ""
|
|
|
|
if darkUI {
|
|
|
|
if value, ok := theme.darkImages[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if result == "" {
|
|
|
|
if value, ok := theme.images[tag]; ok {
|
|
|
|
result = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:22:14 +03:00
|
|
|
func (theme *theme) style(tag string) ViewStyle {
|
2022-05-12 11:05:50 +03:00
|
|
|
if style, ok := theme.styles[tag]; ok {
|
|
|
|
return style
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:22:14 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (theme *theme) String() string {
|
|
|
|
buffer := allocStringBuilder()
|
|
|
|
defer freeStringBuilder(buffer)
|
|
|
|
|
|
|
|
writeString := func(text string) {
|
|
|
|
if strings.ContainsAny(text, " \t\n\r\\\"'`,;{}[]()") {
|
|
|
|
replace := []struct{ old, new string }{
|
|
|
|
{old: "\\", new: `\\`},
|
|
|
|
{old: "\t", new: `\t`},
|
|
|
|
{old: "\r", new: `\r`},
|
|
|
|
{old: "\n", new: `\n`},
|
|
|
|
{old: "\"", new: `\"`},
|
|
|
|
}
|
|
|
|
for _, s := range replace {
|
|
|
|
text = strings.Replace(text, s.old, s.new, -1)
|
|
|
|
}
|
|
|
|
buffer.WriteRune('"')
|
|
|
|
buffer.WriteString(text)
|
|
|
|
buffer.WriteRune('"')
|
|
|
|
} else {
|
|
|
|
buffer.WriteString(text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writeConstants := func(tag string, constants map[string]string) {
|
|
|
|
count := len(constants)
|
|
|
|
if count == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString("\t")
|
|
|
|
buffer.WriteString(tag)
|
|
|
|
buffer.WriteString(" = _{\n")
|
|
|
|
|
|
|
|
tags := make([]string, 0, count)
|
|
|
|
for name := range constants {
|
|
|
|
tags = append(tags, name)
|
|
|
|
}
|
|
|
|
sort.Strings(tags)
|
|
|
|
for _, name := range tags {
|
|
|
|
if value, ok := constants[name]; ok && value != "" {
|
|
|
|
buffer.WriteString("\t\t")
|
|
|
|
writeString(name)
|
|
|
|
buffer.WriteString(" = ")
|
|
|
|
writeString(value)
|
|
|
|
buffer.WriteString(",\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString("\t},\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString("theme {\n")
|
|
|
|
writeConstants("colors", theme.colors)
|
|
|
|
writeConstants("colors:dark", theme.darkColors)
|
|
|
|
writeConstants("images", theme.images)
|
|
|
|
writeConstants("images:dark", theme.darkImages)
|
|
|
|
writeConstants("constants", theme.constants)
|
|
|
|
writeConstants("constants:touch", theme.touchConstants)
|
|
|
|
|
2022-05-25 20:03:32 +03:00
|
|
|
writeStyles := func(orientation, maxWidth, maxHeihgt int, styles map[string]ViewStyle) bool {
|
|
|
|
count := len(styles)
|
|
|
|
if count == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := make([]string, 0, count)
|
|
|
|
for name := range styles {
|
|
|
|
tags = append(tags, name)
|
|
|
|
}
|
|
|
|
sort.Strings(tags)
|
|
|
|
|
|
|
|
buffer.WriteString("\tstyles")
|
|
|
|
switch orientation {
|
|
|
|
case PortraitMedia:
|
|
|
|
buffer.WriteString(":portrait")
|
|
|
|
|
|
|
|
case LandscapeMedia:
|
|
|
|
buffer.WriteString(":landscape")
|
|
|
|
}
|
|
|
|
if maxWidth > 0 {
|
|
|
|
buffer.WriteString(fmt.Sprintf(":width%d", maxWidth))
|
|
|
|
}
|
|
|
|
if maxHeihgt > 0 {
|
|
|
|
buffer.WriteString(fmt.Sprintf(":heihgt%d", maxHeihgt))
|
|
|
|
}
|
|
|
|
buffer.WriteString(" = [\n")
|
|
|
|
|
|
|
|
for _, tag := range tags {
|
|
|
|
if style, ok := styles[tag]; ok {
|
|
|
|
buffer.WriteString("\t\t")
|
|
|
|
writeViewStyle(tag, style, buffer, "\t\t")
|
|
|
|
buffer.WriteString(",")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer.WriteString("\t],\n")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
writeStyles(0, 0, 0, theme.styles)
|
|
|
|
for _, media := range theme.mediaStyles {
|
|
|
|
writeStyles(media.orientation, media.maxWidth, media.maxHeight, media.styles)
|
|
|
|
}
|
|
|
|
|
2022-05-23 15:22:14 +03:00
|
|
|
buffer.WriteString("}\n")
|
|
|
|
return buffer.String()
|
2022-05-12 11:05:50 +03:00
|
|
|
}
|