Added Theme interface, NewTheme, CreateThemeFromText, and AddTheme functions

This commit is contained in:
Alexei Anoshenko 2022-05-12 11:05:50 +03:00
parent 0286918dd4
commit 894242f2dd
11 changed files with 403 additions and 209 deletions

View File

@ -3,6 +3,7 @@
* Added "user-data" property * Added "user-data" property
* Added "focusable" property * Added "focusable" property
* Added ReloadTableViewData, AllImageResources functions * Added ReloadTableViewData, AllImageResources functions
* Added Theme interface, NewTheme, CreateThemeFromText, and AddTheme functions
# v0.5.0 # v0.5.0

View File

@ -554,7 +554,9 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string {
cssBuilder.endAnimation() cssBuilder.endAnimation()
style := strings.ReplaceAll(cssBuilder.finish(), "\n", `\n`) style := cssBuilder.finish()
session.animationCSS += style
style = strings.ReplaceAll(style, "\n", `\n`)
session.runScript(`document.querySelector('style').textContent += "` + style + `"`) session.runScript(`document.querySelector('style').textContent += "` + style + `"`)
return name return name

View File

@ -137,6 +137,18 @@ function getIntAttribute(element, tag) {
} }
function scanElementsSize() { function scanElementsSize() {
var element = document.getElementById("ruiRootView");
if (element) {
let rect = element.getBoundingClientRect();
let width = getIntAttribute(element, "data-width");
let height = getIntAttribute(element, "data-height");
if (rect.width > 0 && rect.height > 0 && (width != rect.width || height != rect.height)) {
element.setAttribute("data-width", rect.width);
element.setAttribute("data-height", rect.height);
sendMessage("root-size{session=" + sessionID + ",width=" + rect.width + ",height=" + rect.height +"}");
}
}
var views = document.getElementsByClassName("ruiView"); var views = document.getElementsByClassName("ruiView");
if (views) { if (views) {
var message = "resize{session=" + sessionID + ",views=[" var message = "resize{session=" + sessionID + ",views=["

View File

@ -90,11 +90,6 @@ func (app *application) getStartPage() string {
return buffer.String() return buffer.String()
} }
func (app *application) init(params AppParams) {
app.params = params
app.sessions = map[int]Session{}
}
func (app *application) Start(addr string) { func (app *application) Start(addr string) {
http.Handle("/", app) http.Handle("/", app)
log.Fatal(http.ListenAndServe(addr, nil)) log.Fatal(http.ListenAndServe(addr, nil))
@ -104,7 +99,6 @@ func (app *application) Finish() {
for _, session := range app.sessions { for _, session := range app.sessions {
session.close() session.close()
} }
} }
func (app *application) nextSessionID() int { func (app *application) nextSessionID() int {
@ -250,6 +244,9 @@ func sessionEventHandler(session Session, events chan DataObject, brige WebBrige
case "session-resume": case "session-resume":
session.onResume() session.onResume()
case "root-size":
session.handleRootSize(data)
case "resize": case "resize":
session.handleResize(data) session.handleResize(data)
@ -291,7 +288,8 @@ func (app *application) startSession(params DataObject, events chan DataObject,
// NewApplication - create the new application and start it // NewApplication - create the new application and start it
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
app := new(application) app := new(application)
app.init(params) app.params = params
app.sessions = map[int]Session{}
app.createContentFunc = createContentFunc app.createContentFunc = createContentFunc
http.Handle("/", app) http.Handle("/", app)

View File

@ -12,10 +12,11 @@ theme {
ruiHighlightTextColor = #FFFFFFFF, ruiHighlightTextColor = #FFFFFFFF,
ruiSelectedColor = #FFE0E0E0, ruiSelectedColor = #FFE0E0E0,
ruiSelectedTextColor = #FF000000, ruiSelectedTextColor = #FF000000,
ruiPopupBackgroundColor = #FFFFFFFF, ruiPopupBackgroundColor = #FFF5F5F5,
ruiPopupTextColor = #FF000000, ruiPopupTextColor = black,
ruiPopupTitleColor = #FF0000FF, ruiPopupTitleColor = #FF0000FF,
ruiPopupTitleTextColor = #FFFFFFFF, ruiPopupTitleTextColor = #FFFFFFFF,
ruiPopupShadow = #80808080,
ruiTabBarBackgroundColor = #FFEEEEEE, ruiTabBarBackgroundColor = #FFEEEEEE,
ruiTabColor = #FFD0D0D0, ruiTabColor = #FFD0D0D0,
@ -33,6 +34,9 @@ theme {
ruiButtonDisabledTextColor = #FFA0A0A0, ruiButtonDisabledTextColor = #FFA0A0A0,
ruiHighlightColor = #FF1A74E8, ruiHighlightColor = #FF1A74E8,
ruiHighlightTextColor = #FFFFFFFF, ruiHighlightTextColor = #FFFFFFFF,
ruiPopupBackgroundColor = #FF424242,
ruiPopupTextColor = white,
ruiPopupShadow = #80EEEEEE,
ruiTabBarBackgroundColor = #FF303030, ruiTabBarBackgroundColor = #FF303030,
ruiTabColor = #FF606060, ruiTabColor = #FF606060,
@ -212,7 +216,7 @@ theme {
background-color = @ruiPopupBackgroundColor, background-color = @ruiPopupBackgroundColor,
text-color = @ruiPopupTextColor, text-color = @ruiPopupTextColor,
radius = 4px, radius = 4px,
shadow = _{spread-radius=4px, blur=16px, color=#80808080}, shadow = _{spread-radius=4px, blur=16px, color=@ruiPopupShadow },
}, },
ruiPopupTitle { ruiPopupTitle {
background-color = @ruiPopupTitleColor, background-color = @ruiPopupTitleColor,

View File

@ -40,6 +40,17 @@ func (detailsView *detailsViewData) Init(session Session) {
//detailsView.systemClass = "ruiDetailsView" //detailsView.systemClass = "ruiDetailsView"
} }
func (detailsView *detailsViewData) Views() []View {
views := detailsView.viewsContainerData.Views()
if summary := detailsView.get(Summary); summary != nil {
switch summary := summary.(type) {
case View:
return append([]View{summary}, views...)
}
}
return views
}
func (detailsView *detailsViewData) Remove(tag string) { func (detailsView *detailsViewData) Remove(tag string) {
detailsView.remove(strings.ToLower(tag)) detailsView.remove(strings.ToLower(tag))
} }
@ -75,10 +86,12 @@ func (detailsView *detailsViewData) set(tag string, value interface{}) bool {
case View: case View:
detailsView.properties[Summary] = value detailsView.properties[Summary] = value
value.setParentID(detailsView.htmlID())
case DataObject: case DataObject:
if view := CreateViewFromObject(detailsView.Session(), value); view != nil { if view := CreateViewFromObject(detailsView.Session(), value); view != nil {
detailsView.properties[Summary] = view detailsView.properties[Summary] = view
view.setParentID(detailsView.htmlID())
} else { } else {
return false return false
} }

View File

@ -1,7 +1,7 @@
package rui package rui
func init() { func init() {
//resources.init() if theme, ok := CreateThemeFromText(defaultThemeText); ok {
defaultTheme.init() defaultTheme = theme
defaultTheme.addText(defaultThemeText) }
} }

View File

@ -32,7 +32,7 @@ type imagePath struct {
type resourceManager struct { type resourceManager struct {
embedFS []*embed.FS embedFS []*embed.FS
themes map[string]*theme themes map[string]Theme
images map[string]imagePath images map[string]imagePath
imageSrcSets map[string][]scaledImage imageSrcSets map[string][]scaledImage
path string path string
@ -40,7 +40,7 @@ type resourceManager struct {
var resources = resourceManager{ var resources = resourceManager{
embedFS: []*embed.FS{}, embedFS: []*embed.FS{},
themes: map[string]*theme{}, themes: map[string]Theme{},
images: map[string]imagePath{}, images: map[string]imagePath{},
imageSrcSets: map[string][]scaledImage{}, imageSrcSets: map[string][]scaledImage{},
} }
@ -107,7 +107,7 @@ func scanEmbedThemesDir(fs *embed.FS, dir string) {
scanEmbedThemesDir(fs, path) scanEmbedThemesDir(fs, path)
} else if strings.ToLower(filepath.Ext(name)) == ".rui" { } else if strings.ToLower(filepath.Ext(name)) == ".rui" {
if data, err := fs.ReadFile(path); err == nil { if data, err := fs.ReadFile(path); err == nil {
RegisterThemeText(string(data)) registerThemeText(string(data))
} }
} }
} }
@ -198,7 +198,7 @@ func scanThemesDir(path string) {
scanThemesDir(newPath) scanThemesDir(newPath)
} else if strings.ToLower(filepath.Ext(newPath)) == ".rui" { } else if strings.ToLower(filepath.Ext(newPath)) == ".rui" {
if data, err := ioutil.ReadFile(newPath); err == nil { if data, err := ioutil.ReadFile(newPath); err == nil {
RegisterThemeText(string(data)) registerThemeText(string(data))
} else { } else {
ErrorLog(err.Error()) ErrorLog(err.Error())
} }
@ -223,32 +223,19 @@ func SetResourcePath(path string) {
scanStringsDir(resources.path + stringsDir) scanStringsDir(resources.path + stringsDir)
} }
// RegisterThemeText parse text and add result to the theme list func registerThemeText(text string) bool {
func RegisterThemeText(text string) bool { theme, ok := CreateThemeFromText(text)
data := ParseDataText(text) if !ok {
if data == nil {
return false return false
} }
if !data.IsObject() { name := theme.Name()
ErrorLog(`Root element is not object`) if name == "" {
return false defaultTheme.concat(theme)
} } else if t, ok := resources.themes[name]; ok {
if data.Tag() != "theme" { t.concat(theme)
ErrorLog(`Invalid the root object tag. Must be "theme"`)
return false
}
if name, ok := data.PropertyValue("name"); ok && name != "" {
t := resources.themes[name]
if t == nil {
t = new(theme)
t.init()
resources.themes[name] = t
}
t.addData(data)
} else { } else {
defaultTheme.addData(data) resources.themes[name] = theme
} }
return true return true
@ -426,3 +413,16 @@ func AllImageResources() []string {
sort.Strings(result) sort.Strings(result)
return result return result
} }
func AddTheme(theme Theme) {
if theme != nil {
name := theme.Name()
if name == "" {
defaultTheme.concat(theme)
} else if t, ok := resources.themes[name]; ok {
t.concat(theme)
} else {
resources.themes[name] = theme
}
}
}

View File

@ -94,6 +94,7 @@ type Session interface {
runScript(script string) runScript(script string)
runGetterScript(script string) DataObject //, answer chan DataObject) runGetterScript(script string) DataObject //, answer chan DataObject)
handleAnswer(data DataObject) handleAnswer(data DataObject)
handleRootSize(data DataObject)
handleResize(data DataObject) handleResize(data DataObject)
handleViewEvent(command string, data DataObject) handleViewEvent(command string, data DataObject)
close() close()
@ -113,10 +114,12 @@ type Session interface {
} }
type sessionData struct { type sessionData struct {
customTheme *theme customTheme Theme
currentTheme *theme currentTheme Theme
darkTheme bool darkTheme bool
touchScreen bool touchScreen bool
screenWidth int
screenHeight int
textDirection int textDirection int
pixelRatio float64 pixelRatio float64
userAgent string userAgent string
@ -137,6 +140,7 @@ type sessionData struct {
brige WebBrige brige WebBrige
events chan DataObject events chan DataObject
animationCounter int animationCounter int
animationCSS string
} }
func newSession(app Application, id int, customTheme string, params DataObject) Session { func newSession(app Application, id int, customTheme string, params DataObject) Session {
@ -151,9 +155,10 @@ func newSession(app Application, id int, customTheme string, params DataObject)
session.viewCounter = 0 session.viewCounter = 0
session.ignoreUpdates = false session.ignoreUpdates = false
session.animationCounter = 0 session.animationCounter = 0
session.animationCSS = ""
if customTheme != "" { if customTheme != "" {
if theme, ok := newTheme(customTheme); ok { if theme, ok := CreateThemeFromText(customTheme); ok {
session.customTheme = theme session.customTheme = theme
session.currentTheme = nil session.currentTheme = nil
} }
@ -216,11 +221,10 @@ func (session *sessionData) close() {
} }
func (session *sessionData) styleProperty(styleTag, propertyTag string) (string, bool) { func (session *sessionData) styleProperty(styleTag, propertyTag string) (string, bool) {
if style, ok := session.getCurrentTheme().styles[styleTag]; ok { style := session.getCurrentTheme().style(styleTag)
if value, ok := style[propertyTag]; ok { if value, ok := style[propertyTag]; ok {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
return session.resolveConstants(text) return session.resolveConstants(text)
}
} }
} }
@ -229,11 +233,10 @@ func (session *sessionData) styleProperty(styleTag, propertyTag string) (string,
} }
func (session *sessionData) stylePropertyNode(styleTag, propertyTag string) DataNode { func (session *sessionData) stylePropertyNode(styleTag, propertyTag string) DataNode {
if style, ok := session.getCurrentTheme().styles[styleTag]; ok { style := session.getCurrentTheme().style(styleTag)
if value, ok := style[propertyTag]; ok { if value, ok := style[propertyTag]; ok {
if node, ok := value.(DataNode); ok { if node, ok := value.(DataNode); ok {
return node return node
}
} }
} }
@ -279,6 +282,8 @@ func (session *sessionData) RootView() View {
func (session *sessionData) writeInitScript(writer *strings.Builder) { func (session *sessionData) writeInitScript(writer *strings.Builder) {
if css := session.getCurrentTheme().cssText(session); css != "" { if css := session.getCurrentTheme().cssText(session); css != "" {
css = strings.ReplaceAll(css, "\n", `\n`)
css = strings.ReplaceAll(css, "\t", `\t`)
writer.WriteString(`document.querySelector('style').textContent += "`) writer.WriteString(`document.querySelector('style').textContent += "`)
writer.WriteString(css) writer.WriteString(css)
writer.WriteString("\";\n") writer.WriteString("\";\n")
@ -295,7 +300,19 @@ func (session *sessionData) reload() {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
session.writeInitScript(buffer) css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
css = strings.ReplaceAll(css, "\n", `\n`)
css = strings.ReplaceAll(css, "\t", `\t`)
buffer.WriteString(`document.querySelector('style').textContent = "`)
buffer.WriteString(css)
buffer.WriteString("\";\n")
if session.rootView != nil {
buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
viewHTML(session.rootView, buffer)
buffer.WriteString("';\nscanElementsSize();")
}
session.runScript(buffer.String()) session.runScript(buffer.String())
} }
@ -360,6 +377,28 @@ func (session *sessionData) handleAnswer(data DataObject) {
session.brige.AnswerReceived(data) session.brige.AnswerReceived(data)
} }
func (session *sessionData) handleRootSize(data DataObject) {
getValue := func(tag string) int {
if value, ok := data.PropertyValue(tag); ok {
float, err := strconv.ParseFloat(value, 64)
if err == nil {
return int(float)
}
ErrorLog(`Resize event error: ` + err.Error())
} else {
ErrorLogF(`Resize event error: the property "%s" not found`, tag)
}
return 0
}
if w := getValue("width"); w > 0 {
session.screenWidth = w
}
if h := getValue("height"); h > 0 {
session.screenHeight = h
}
}
func (session *sessionData) handleResize(data DataObject) { func (session *sessionData) handleResize(data DataObject) {
if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode { if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode {
for _, el := range node.ArrayElements() { for _, el := range node.ArrayElements() {

View File

@ -2,7 +2,6 @@ package rui
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
) )
@ -24,21 +23,10 @@ func (session *sessionData) TextDirection() int {
func (session *sessionData) constant(tag string, prevTags []string) (string, bool) { func (session *sessionData) constant(tag string, prevTags []string) (string, bool) {
tags := append(prevTags, tag) tags := append(prevTags, tag)
result := ""
theme := session.getCurrentTheme() theme := session.getCurrentTheme()
for { for {
ok := false result := theme.constant(tag, session.touchScreen)
if session.touchScreen { if result == "" {
if theme.touchConstants != nil {
result, ok = theme.touchConstants[tag]
}
}
if !ok {
result, ok = theme.constants[tag]
}
if !ok {
ErrorLogF(`"%v" constant not found`, tag) ErrorLogF(`"%v" constant not found`, tag)
return "", false return "", false
} }
@ -49,8 +37,7 @@ func (session *sessionData) constant(tag string, prevTags []string) (string, boo
for _, separator := range []string{",", " ", ":", ";", "|", "/"} { for _, separator := range []string{",", " ", ":", ";", "|", "/"} {
if strings.Contains(result, separator) { if strings.Contains(result, separator) {
result, ok = session.resolveConstantsNext(result, tags) return session.resolveConstantsNext(result, tags)
return result, ok
} }
} }
@ -125,14 +112,13 @@ func (session *sessionData) Constant(tag string) (string, bool) {
return session.constant(tag, []string{}) return session.constant(tag, []string{})
} }
func (session *sessionData) getCurrentTheme() *theme { func (session *sessionData) getCurrentTheme() Theme {
if session.currentTheme != nil { if session.currentTheme != nil {
return session.currentTheme return session.currentTheme
} }
if session.customTheme != nil { if session.customTheme != nil {
session.currentTheme = new(theme) session.currentTheme = NewTheme("")
session.currentTheme.init()
session.currentTheme.concat(defaultTheme) session.currentTheme.concat(defaultTheme)
session.currentTheme.concat(session.customTheme) session.currentTheme.concat(session.customTheme)
return session.currentTheme return session.currentTheme
@ -144,23 +130,10 @@ func (session *sessionData) getCurrentTheme() *theme {
// Color return the color with "tag" name or 0 if it is not exists // Color return the color with "tag" name or 0 if it is not exists
func (session *sessionData) Color(tag string) (Color, bool) { func (session *sessionData) Color(tag string) (Color, bool) {
tags := []string{tag} tags := []string{tag}
result := ""
theme := session.getCurrentTheme() theme := session.getCurrentTheme()
for { for {
ok := false result := theme.color(tag, session.darkTheme)
if session.darkTheme { if result == "" {
if theme.darkColors != nil {
result, ok = theme.darkColors[tag]
}
}
if !ok {
if theme.colors != nil {
result, ok = theme.colors[tag]
}
}
if !ok {
ErrorLogF(`"%v" color not found`, tag) ErrorLogF(`"%v" color not found`, tag)
return 0, false return 0, false
} }
@ -188,23 +161,10 @@ func (session *sessionData) Color(tag string) (Color, bool) {
func (session *sessionData) ImageConstant(tag string) (string, bool) { func (session *sessionData) ImageConstant(tag string) (string, bool) {
tags := []string{tag} tags := []string{tag}
result := ""
theme := session.getCurrentTheme() theme := session.getCurrentTheme()
for { for {
ok := false result := theme.image(tag, session.darkTheme)
if session.darkTheme { if result == "" {
if theme.darkImages != nil {
result, ok = theme.darkImages[tag]
}
}
if !ok {
if theme.images != nil {
result, ok = theme.images[tag]
}
}
if !ok {
ErrorLogF(`"%v" image not found`, tag) ErrorLogF(`"%v" image not found`, tag)
return "", false return "", false
} }
@ -379,55 +339,13 @@ func (session *sessionData) SetLanguage(lang string) {
} }
func (session *sessionData) ConstantTags() []string { func (session *sessionData) ConstantTags() []string {
theme := session.getCurrentTheme() return session.getCurrentTheme().ConstantTags()
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 (session *sessionData) ColorTags() []string { func (session *sessionData) ColorTags() []string {
theme := session.getCurrentTheme() return session.getCurrentTheme().ColorTags()
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 (session *sessionData) ImageConstantTags() []string { func (session *sessionData) ImageConstantTags() []string {
theme := session.getCurrentTheme() return session.getCurrentTheme().ImageConstantTags()
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
} }

329
theme.go
View File

@ -7,63 +7,105 @@ import (
) )
const ( const (
defaultMedia = 0 DefaultMedia = 0
portraitMedia = 1 PortraitMedia = 1
landscapeMedia = 2 LandscapeMedia = 2
) )
type mediaStyle struct { type MediaStyle struct {
orientation int Orientation int
width int MaxWidth int
height int MaxHeight int
styles map[string]Params Styles map[string]Params
} }
func (rule mediaStyle) cssText() string { 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
styles map[string]Params
mediaStyles []MediaStyle
}
type Theme interface {
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
constant(tag string, touchUI bool) string
color(tag string, darkUI bool) string
image(tag string, darkUI bool) string
style(tag string) Params
concat(anotherTheme Theme)
cssText(session Session) string
data() *theme
}
func (rule MediaStyle) cssText() string {
builder := allocStringBuilder() builder := allocStringBuilder()
defer freeStringBuilder(builder) defer freeStringBuilder(builder)
switch rule.orientation { switch rule.Orientation {
case portraitMedia: case PortraitMedia:
builder.WriteString(" and (orientation: portrait)") builder.WriteString(" and (orientation: portrait)")
case landscapeMedia: case LandscapeMedia:
builder.WriteString(" and (orientation: landscape)") builder.WriteString(" and (orientation: landscape)")
} }
if rule.width > 0 { if rule.MaxWidth > 0 {
builder.WriteString(" and (max-width: ") builder.WriteString(" and (max-width: ")
builder.WriteString(strconv.Itoa(rule.width)) builder.WriteString(strconv.Itoa(rule.MaxWidth))
builder.WriteString("px)") builder.WriteString("px)")
} }
if rule.height > 0 { if rule.MaxHeight > 0 {
builder.WriteString(" and (max-height: ") builder.WriteString(" and (max-height: ")
builder.WriteString(strconv.Itoa(rule.height)) builder.WriteString(strconv.Itoa(rule.MaxHeight))
builder.WriteString("px)") builder.WriteString("px)")
} }
return builder.String() return builder.String()
} }
func parseMediaRule(text string) (mediaStyle, bool) { func parseMediaRule(text string) (MediaStyle, bool) {
rule := mediaStyle{orientation: defaultMedia, width: 0, height: 0, styles: map[string]Params{}} rule := MediaStyle{
Orientation: DefaultMedia,
MaxWidth: 0,
MaxHeight: 0,
Styles: map[string]Params{},
}
elements := strings.Split(text, ":") elements := strings.Split(text, ":")
for i := 1; i < len(elements); i++ { for i := 1; i < len(elements); i++ {
switch element := elements[i]; element { switch element := elements[i]; element {
case "portrait": case "portrait":
if rule.orientation != defaultMedia { if rule.Orientation != DefaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`) ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false return rule, false
} }
rule.orientation = portraitMedia rule.Orientation = PortraitMedia
case "landscape": case "landscape":
if rule.orientation != defaultMedia { if rule.Orientation != DefaultMedia {
ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`) ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`)
return rule, false return rule, false
} }
rule.orientation = landscapeMedia rule.Orientation = LandscapeMedia
default: default:
elementSize := func(name string) (int, bool) { elementSize := func(name string) (int, bool) {
@ -82,20 +124,20 @@ func parseMediaRule(text string) (mediaStyle, bool) {
if !ok { if !ok {
return rule, false return rule, false
} }
if rule.width != 0 { if rule.MaxWidth != 0 {
ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`) ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`)
return rule, false return rule, false
} }
rule.width = size rule.MaxWidth = size
} else if size, ok := elementSize("height"); !ok || size > 0 { } else if size, ok := elementSize("height"); !ok || size > 0 {
if !ok { if !ok {
return rule, false return rule, false
} }
if rule.height != 0 { if rule.MaxHeight != 0 {
ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`) ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`)
return rule, false return rule, false
} }
rule.height = size rule.MaxHeight = size
} else { } else {
ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text) ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text)
return rule, false return rule, false
@ -105,21 +147,16 @@ func parseMediaRule(text string) (mediaStyle, bool) {
return rule, true return rule, true
} }
type theme struct { var defaultTheme = NewTheme("")
name string
constants map[string]string func NewTheme(name string) Theme {
touchConstants map[string]string result := new(theme)
colors map[string]string result.init()
darkColors map[string]string result.name = name
images map[string]string return result
darkImages map[string]string
styles map[string]Params
mediaStyles []mediaStyle
} }
var defaultTheme = new(theme) func CreateThemeFromText(text string) (Theme, bool) {
func newTheme(text string) (*theme, bool) {
result := new(theme) result := new(theme)
result.init() result.init()
ok := result.addText(text) ok := result.addText(text)
@ -134,50 +171,167 @@ func (theme *theme) init() {
theme.images = map[string]string{} theme.images = map[string]string{}
theme.darkImages = map[string]string{} theme.darkImages = map[string]string{}
theme.styles = map[string]Params{} theme.styles = map[string]Params{}
theme.mediaStyles = []mediaStyle{} theme.mediaStyles = []MediaStyle{}
} }
func (theme *theme) concat(anotherTheme *theme) { 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
}
}
}
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
}
}
}
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
}
func (theme *theme) data() *theme {
return theme
}
func (theme *theme) concat(anotherTheme Theme) {
if theme.constants == nil { if theme.constants == nil {
theme.init() theme.init()
} }
for tag, constant := range anotherTheme.constants { another := anotherTheme.data()
for tag, constant := range another.constants {
theme.constants[tag] = constant theme.constants[tag] = constant
} }
for tag, constant := range anotherTheme.touchConstants { for tag, constant := range another.touchConstants {
theme.touchConstants[tag] = constant theme.touchConstants[tag] = constant
} }
for tag, color := range anotherTheme.colors { for tag, color := range another.colors {
theme.colors[tag] = color theme.colors[tag] = color
} }
for tag, color := range anotherTheme.darkColors { for tag, color := range another.darkColors {
theme.darkColors[tag] = color theme.darkColors[tag] = color
} }
for tag, image := range anotherTheme.images { for tag, image := range another.images {
theme.images[tag] = image theme.images[tag] = image
} }
for tag, image := range anotherTheme.darkImages { for tag, image := range another.darkImages {
theme.darkImages[tag] = image theme.darkImages[tag] = image
} }
for tag, style := range anotherTheme.styles { for tag, style := range another.styles {
theme.styles[tag] = style theme.styles[tag] = style
} }
for _, anotherMedia := range anotherTheme.mediaStyles { for _, anotherMedia := range another.mediaStyles {
exists := false exists := false
for _, media := range theme.mediaStyles { for _, media := range theme.mediaStyles {
if anotherMedia.height == media.height && if anotherMedia.MaxHeight == media.MaxHeight &&
anotherMedia.width == media.width && anotherMedia.MaxWidth == media.MaxWidth &&
anotherMedia.orientation == media.orientation { anotherMedia.Orientation == media.Orientation {
for tag, style := range anotherMedia.styles { for tag, style := range anotherMedia.Styles {
media.styles[tag] = style media.Styles[tag] = style
} }
exists = true exists = true
break break
@ -211,7 +365,7 @@ func (theme *theme) cssText(session Session) string {
for _, media := range theme.mediaStyles { for _, media := range theme.mediaStyles {
builder.startMedia(media.cssText()) builder.startMedia(media.cssText())
for tag, obj := range media.styles { for tag, obj := range media.Styles {
var style viewStyle var style viewStyle
style.init() style.init()
for tag, value := range obj { for tag, value := range obj {
@ -363,7 +517,7 @@ func (theme *theme) parseThemeData(data DataObject) {
for k := 0; k < arraySize; k++ { for k := 0; k < arraySize; k++ {
if element := d.ArrayElement(k); element != nil && element.IsObject() { if element := d.ArrayElement(k); element != nil && element.IsObject() {
if obj := element.Object(); obj != nil { if obj := element.Object(); obj != nil {
rule.styles[obj.Tag()] = objToParams(obj) rule.Styles[obj.Tag()] = objToParams(obj)
} }
} }
} }
@ -376,13 +530,66 @@ func (theme *theme) parseThemeData(data DataObject) {
if len(theme.mediaStyles) > 0 { if len(theme.mediaStyles) > 0 {
sort.SliceStable(theme.mediaStyles, func(i, j int) bool { sort.SliceStable(theme.mediaStyles, func(i, j int) bool {
if theme.mediaStyles[i].orientation != theme.mediaStyles[j].orientation { if theme.mediaStyles[i].Orientation != theme.mediaStyles[j].Orientation {
return 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 { if theme.mediaStyles[i].MaxWidth != theme.mediaStyles[j].MaxWidth {
return theme.mediaStyles[i].width < theme.mediaStyles[j].width return theme.mediaStyles[i].MaxWidth < theme.mediaStyles[j].MaxWidth
} }
return theme.mediaStyles[i].height < theme.mediaStyles[j].height return theme.mediaStyles[i].MaxHeight < theme.mediaStyles[j].MaxHeight
}) })
} }
} }
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
}
func (theme *theme) style(tag string) Params {
if style, ok := theme.styles[tag]; ok {
return style
}
return Params{}
}