From 894242f2ddd0996b7c3e654da2a1f2ed42edc6e7 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko Date: Thu, 12 May 2022 11:05:50 +0300 Subject: [PATCH] Added Theme interface, NewTheme, CreateThemeFromText, and AddTheme functions --- CHANGELOG.md | 1 + animation.go | 4 +- app_scripts.js | 12 ++ application.go | 12 +- defaultTheme.rui | 10 +- detailsView.go | 13 ++ init.go | 6 +- resources.go | 52 ++++---- session.go | 67 ++++++++-- sessionTheme.go | 106 ++------------- theme.go | 329 ++++++++++++++++++++++++++++++++++++++--------- 11 files changed, 403 insertions(+), 209 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbcd036..d6b7481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Added "user-data" property * Added "focusable" property * Added ReloadTableViewData, AllImageResources functions +* Added Theme interface, NewTheme, CreateThemeFromText, and AddTheme functions # v0.5.0 diff --git a/animation.go b/animation.go index 5e3f500..2158ffb 100644 --- a/animation.go +++ b/animation.go @@ -554,7 +554,9 @@ func (session *sessionData) registerAnimation(props []AnimatedProperty) string { 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 + `"`) return name diff --git a/app_scripts.js b/app_scripts.js index e57d7d0..f266e24 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -137,6 +137,18 @@ function getIntAttribute(element, tag) { } 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"); if (views) { var message = "resize{session=" + sessionID + ",views=[" diff --git a/application.go b/application.go index 39df812..d9b7840 100644 --- a/application.go +++ b/application.go @@ -90,11 +90,6 @@ func (app *application) getStartPage() string { return buffer.String() } -func (app *application) init(params AppParams) { - app.params = params - app.sessions = map[int]Session{} -} - func (app *application) Start(addr string) { http.Handle("/", app) log.Fatal(http.ListenAndServe(addr, nil)) @@ -104,7 +99,6 @@ func (app *application) Finish() { for _, session := range app.sessions { session.close() } - } func (app *application) nextSessionID() int { @@ -250,6 +244,9 @@ func sessionEventHandler(session Session, events chan DataObject, brige WebBrige case "session-resume": session.onResume() + case "root-size": + session.handleRootSize(data) + case "resize": session.handleResize(data) @@ -291,7 +288,8 @@ func (app *application) startSession(params DataObject, events chan DataObject, // NewApplication - create the new application and start it func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { app := new(application) - app.init(params) + app.params = params + app.sessions = map[int]Session{} app.createContentFunc = createContentFunc http.Handle("/", app) diff --git a/defaultTheme.rui b/defaultTheme.rui index 28a6e4f..6b8acbd 100644 --- a/defaultTheme.rui +++ b/defaultTheme.rui @@ -12,10 +12,11 @@ theme { ruiHighlightTextColor = #FFFFFFFF, ruiSelectedColor = #FFE0E0E0, ruiSelectedTextColor = #FF000000, - ruiPopupBackgroundColor = #FFFFFFFF, - ruiPopupTextColor = #FF000000, + ruiPopupBackgroundColor = #FFF5F5F5, + ruiPopupTextColor = black, ruiPopupTitleColor = #FF0000FF, ruiPopupTitleTextColor = #FFFFFFFF, + ruiPopupShadow = #80808080, ruiTabBarBackgroundColor = #FFEEEEEE, ruiTabColor = #FFD0D0D0, @@ -33,6 +34,9 @@ theme { ruiButtonDisabledTextColor = #FFA0A0A0, ruiHighlightColor = #FF1A74E8, ruiHighlightTextColor = #FFFFFFFF, + ruiPopupBackgroundColor = #FF424242, + ruiPopupTextColor = white, + ruiPopupShadow = #80EEEEEE, ruiTabBarBackgroundColor = #FF303030, ruiTabColor = #FF606060, @@ -212,7 +216,7 @@ theme { background-color = @ruiPopupBackgroundColor, text-color = @ruiPopupTextColor, radius = 4px, - shadow = _{spread-radius=4px, blur=16px, color=#80808080}, + shadow = _{spread-radius=4px, blur=16px, color=@ruiPopupShadow }, }, ruiPopupTitle { background-color = @ruiPopupTitleColor, diff --git a/detailsView.go b/detailsView.go index a404fc1..2069ad4 100644 --- a/detailsView.go +++ b/detailsView.go @@ -40,6 +40,17 @@ func (detailsView *detailsViewData) Init(session Session) { //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) { detailsView.remove(strings.ToLower(tag)) } @@ -75,10 +86,12 @@ func (detailsView *detailsViewData) set(tag string, value interface{}) bool { case View: detailsView.properties[Summary] = value + value.setParentID(detailsView.htmlID()) case DataObject: if view := CreateViewFromObject(detailsView.Session(), value); view != nil { detailsView.properties[Summary] = view + view.setParentID(detailsView.htmlID()) } else { return false } diff --git a/init.go b/init.go index e881d96..77b6ec9 100644 --- a/init.go +++ b/init.go @@ -1,7 +1,7 @@ package rui func init() { - //resources.init() - defaultTheme.init() - defaultTheme.addText(defaultThemeText) + if theme, ok := CreateThemeFromText(defaultThemeText); ok { + defaultTheme = theme + } } diff --git a/resources.go b/resources.go index 62312d4..468db00 100644 --- a/resources.go +++ b/resources.go @@ -32,7 +32,7 @@ type imagePath struct { type resourceManager struct { embedFS []*embed.FS - themes map[string]*theme + themes map[string]Theme images map[string]imagePath imageSrcSets map[string][]scaledImage path string @@ -40,7 +40,7 @@ type resourceManager struct { var resources = resourceManager{ embedFS: []*embed.FS{}, - themes: map[string]*theme{}, + themes: map[string]Theme{}, images: map[string]imagePath{}, imageSrcSets: map[string][]scaledImage{}, } @@ -107,7 +107,7 @@ func scanEmbedThemesDir(fs *embed.FS, dir string) { scanEmbedThemesDir(fs, path) } else if strings.ToLower(filepath.Ext(name)) == ".rui" { if data, err := fs.ReadFile(path); err == nil { - RegisterThemeText(string(data)) + registerThemeText(string(data)) } } } @@ -198,7 +198,7 @@ func scanThemesDir(path string) { scanThemesDir(newPath) } else if strings.ToLower(filepath.Ext(newPath)) == ".rui" { if data, err := ioutil.ReadFile(newPath); err == nil { - RegisterThemeText(string(data)) + registerThemeText(string(data)) } else { ErrorLog(err.Error()) } @@ -223,32 +223,19 @@ func SetResourcePath(path string) { scanStringsDir(resources.path + stringsDir) } -// RegisterThemeText parse text and add result to the theme list -func RegisterThemeText(text string) bool { - data := ParseDataText(text) - if data == nil { +func registerThemeText(text string) bool { + theme, ok := CreateThemeFromText(text) + if !ok { return false } - if !data.IsObject() { - ErrorLog(`Root element is not object`) - return false - } - if data.Tag() != "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) + name := theme.Name() + if name == "" { + defaultTheme.concat(theme) + } else if t, ok := resources.themes[name]; ok { + t.concat(theme) } else { - defaultTheme.addData(data) + resources.themes[name] = theme } return true @@ -426,3 +413,16 @@ func AllImageResources() []string { sort.Strings(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 + } + } +} diff --git a/session.go b/session.go index dd57c19..b3c5013 100644 --- a/session.go +++ b/session.go @@ -94,6 +94,7 @@ type Session interface { runScript(script string) runGetterScript(script string) DataObject //, answer chan DataObject) handleAnswer(data DataObject) + handleRootSize(data DataObject) handleResize(data DataObject) handleViewEvent(command string, data DataObject) close() @@ -113,10 +114,12 @@ type Session interface { } type sessionData struct { - customTheme *theme - currentTheme *theme + customTheme Theme + currentTheme Theme darkTheme bool touchScreen bool + screenWidth int + screenHeight int textDirection int pixelRatio float64 userAgent string @@ -137,6 +140,7 @@ type sessionData struct { brige WebBrige events chan DataObject animationCounter int + animationCSS string } 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.ignoreUpdates = false session.animationCounter = 0 + session.animationCSS = "" if customTheme != "" { - if theme, ok := newTheme(customTheme); ok { + if theme, ok := CreateThemeFromText(customTheme); ok { session.customTheme = theme session.currentTheme = nil } @@ -216,11 +221,10 @@ func (session *sessionData) close() { } func (session *sessionData) styleProperty(styleTag, propertyTag string) (string, bool) { - if style, ok := session.getCurrentTheme().styles[styleTag]; ok { - if value, ok := style[propertyTag]; ok { - if text, ok := value.(string); ok { - return session.resolveConstants(text) - } + style := session.getCurrentTheme().style(styleTag) + if value, ok := style[propertyTag]; ok { + if text, ok := value.(string); ok { + return session.resolveConstants(text) } } @@ -229,11 +233,10 @@ func (session *sessionData) styleProperty(styleTag, propertyTag string) (string, } func (session *sessionData) stylePropertyNode(styleTag, propertyTag string) DataNode { - if style, ok := session.getCurrentTheme().styles[styleTag]; ok { - if value, ok := style[propertyTag]; ok { - if node, ok := value.(DataNode); ok { - return node - } + style := session.getCurrentTheme().style(styleTag) + if value, ok := style[propertyTag]; ok { + if node, ok := value.(DataNode); ok { + return node } } @@ -279,6 +282,8 @@ func (session *sessionData) RootView() View { func (session *sessionData) writeInitScript(writer *strings.Builder) { 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(css) writer.WriteString("\";\n") @@ -295,7 +300,19 @@ func (session *sessionData) reload() { buffer := allocStringBuilder() 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()) } @@ -360,6 +377,28 @@ func (session *sessionData) handleAnswer(data DataObject) { 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) { if node := data.PropertyWithTag("views"); node != nil && node.Type() == ArrayNode { for _, el := range node.ArrayElements() { diff --git a/sessionTheme.go b/sessionTheme.go index 91d67d5..ef99e6a 100644 --- a/sessionTheme.go +++ b/sessionTheme.go @@ -2,7 +2,6 @@ package rui import ( "fmt" - "sort" "strings" ) @@ -24,21 +23,10 @@ func (session *sessionData) TextDirection() int { func (session *sessionData) constant(tag string, prevTags []string) (string, bool) { tags := append(prevTags, tag) - result := "" theme := session.getCurrentTheme() for { - ok := false - if session.touchScreen { - if theme.touchConstants != nil { - result, ok = theme.touchConstants[tag] - } - } - - if !ok { - result, ok = theme.constants[tag] - } - - if !ok { + result := theme.constant(tag, session.touchScreen) + if result == "" { ErrorLogF(`"%v" constant not found`, tag) return "", false } @@ -49,8 +37,7 @@ func (session *sessionData) constant(tag string, prevTags []string) (string, boo for _, separator := range []string{",", " ", ":", ";", "|", "/"} { if strings.Contains(result, separator) { - result, ok = session.resolveConstantsNext(result, tags) - return result, ok + return session.resolveConstantsNext(result, tags) } } @@ -125,14 +112,13 @@ func (session *sessionData) Constant(tag string) (string, bool) { return session.constant(tag, []string{}) } -func (session *sessionData) getCurrentTheme() *theme { +func (session *sessionData) getCurrentTheme() Theme { if session.currentTheme != nil { return session.currentTheme } if session.customTheme != nil { - session.currentTheme = new(theme) - session.currentTheme.init() + session.currentTheme = NewTheme("") session.currentTheme.concat(defaultTheme) session.currentTheme.concat(session.customTheme) 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 func (session *sessionData) Color(tag string) (Color, bool) { tags := []string{tag} - result := "" theme := session.getCurrentTheme() for { - ok := false - if session.darkTheme { - if theme.darkColors != nil { - result, ok = theme.darkColors[tag] - } - } - - if !ok { - if theme.colors != nil { - result, ok = theme.colors[tag] - } - } - - if !ok { + result := theme.color(tag, session.darkTheme) + if result == "" { ErrorLogF(`"%v" color not found`, tag) return 0, false } @@ -188,23 +161,10 @@ func (session *sessionData) Color(tag string) (Color, bool) { func (session *sessionData) ImageConstant(tag string) (string, bool) { tags := []string{tag} - result := "" theme := session.getCurrentTheme() for { - ok := false - if session.darkTheme { - if theme.darkImages != nil { - result, ok = theme.darkImages[tag] - } - } - - if !ok { - if theme.images != nil { - result, ok = theme.images[tag] - } - } - - if !ok { + result := theme.image(tag, session.darkTheme) + if result == "" { ErrorLogF(`"%v" image not found`, tag) return "", false } @@ -379,55 +339,13 @@ func (session *sessionData) SetLanguage(lang string) { } func (session *sessionData) ConstantTags() []string { - theme := session.getCurrentTheme() - - 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 + return session.getCurrentTheme().ConstantTags() } func (session *sessionData) ColorTags() []string { - theme := session.getCurrentTheme() - - 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 + return session.getCurrentTheme().ColorTags() } func (session *sessionData) ImageConstantTags() []string { - theme := session.getCurrentTheme() - - 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 + return session.getCurrentTheme().ImageConstantTags() } diff --git a/theme.go b/theme.go index 04ceb0f..705b284 100644 --- a/theme.go +++ b/theme.go @@ -7,63 +7,105 @@ import ( ) const ( - defaultMedia = 0 - portraitMedia = 1 - landscapeMedia = 2 + DefaultMedia = 0 + PortraitMedia = 1 + LandscapeMedia = 2 ) -type mediaStyle struct { - orientation int - width int - height int - styles map[string]Params +type MediaStyle struct { + Orientation int + MaxWidth int + MaxHeight int + 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() defer freeStringBuilder(builder) - switch rule.orientation { - case portraitMedia: + switch rule.Orientation { + case PortraitMedia: builder.WriteString(" and (orientation: portrait)") - case landscapeMedia: + case LandscapeMedia: builder.WriteString(" and (orientation: landscape)") } - if rule.width > 0 { + if rule.MaxWidth > 0 { builder.WriteString(" and (max-width: ") - builder.WriteString(strconv.Itoa(rule.width)) + builder.WriteString(strconv.Itoa(rule.MaxWidth)) builder.WriteString("px)") } - if rule.height > 0 { + if rule.MaxHeight > 0 { builder.WriteString(" and (max-height: ") - builder.WriteString(strconv.Itoa(rule.height)) + builder.WriteString(strconv.Itoa(rule.MaxHeight)) builder.WriteString("px)") } return builder.String() } -func parseMediaRule(text string) (mediaStyle, bool) { - rule := mediaStyle{orientation: defaultMedia, width: 0, height: 0, styles: map[string]Params{}} +func parseMediaRule(text string) (MediaStyle, bool) { + rule := MediaStyle{ + Orientation: DefaultMedia, + MaxWidth: 0, + MaxHeight: 0, + Styles: map[string]Params{}, + } + elements := strings.Split(text, ":") for i := 1; i < len(elements); i++ { switch element := elements[i]; element { case "portrait": - if rule.orientation != defaultMedia { + if rule.Orientation != DefaultMedia { ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`) return rule, false } - rule.orientation = portraitMedia + rule.Orientation = PortraitMedia case "landscape": - if rule.orientation != defaultMedia { + if rule.Orientation != DefaultMedia { ErrorLog(`Duplicate orientation tag in the style section "` + text + `"`) return rule, false } - rule.orientation = landscapeMedia + rule.Orientation = LandscapeMedia default: elementSize := func(name string) (int, bool) { @@ -82,20 +124,20 @@ func parseMediaRule(text string) (mediaStyle, bool) { if !ok { return rule, false } - if rule.width != 0 { + if rule.MaxWidth != 0 { ErrorLog(`Duplicate "width" tag in the style section "` + text + `"`) return rule, false } - rule.width = size + rule.MaxWidth = size } else if size, ok := elementSize("height"); !ok || size > 0 { if !ok { return rule, false } - if rule.height != 0 { + if rule.MaxHeight != 0 { ErrorLog(`Duplicate "height" tag in the style section "` + text + `"`) return rule, false } - rule.height = size + rule.MaxHeight = size } else { ErrorLogF(`Unknown elemnet "%s" in the style section name "%s"`, element, text) return rule, false @@ -105,21 +147,16 @@ func parseMediaRule(text string) (mediaStyle, bool) { return rule, true } -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 +var defaultTheme = NewTheme("") + +func NewTheme(name string) Theme { + result := new(theme) + result.init() + result.name = name + return result } -var defaultTheme = new(theme) - -func newTheme(text string) (*theme, bool) { +func CreateThemeFromText(text string) (Theme, bool) { result := new(theme) result.init() ok := result.addText(text) @@ -134,50 +171,167 @@ func (theme *theme) init() { theme.images = map[string]string{} theme.darkImages = map[string]string{} 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 { theme.init() } - for tag, constant := range anotherTheme.constants { + another := anotherTheme.data() + for tag, constant := range another.constants { theme.constants[tag] = constant } - for tag, constant := range anotherTheme.touchConstants { + for tag, constant := range another.touchConstants { theme.touchConstants[tag] = constant } - for tag, color := range anotherTheme.colors { + for tag, color := range another.colors { theme.colors[tag] = color } - for tag, color := range anotherTheme.darkColors { + for tag, color := range another.darkColors { theme.darkColors[tag] = color } - for tag, image := range anotherTheme.images { + for tag, image := range another.images { theme.images[tag] = image } - for tag, image := range anotherTheme.darkImages { + for tag, image := range another.darkImages { theme.darkImages[tag] = image } - for tag, style := range anotherTheme.styles { + for tag, style := range another.styles { theme.styles[tag] = style } - for _, anotherMedia := range anotherTheme.mediaStyles { + for _, anotherMedia := range another.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 + if anotherMedia.MaxHeight == media.MaxHeight && + anotherMedia.MaxWidth == media.MaxWidth && + anotherMedia.Orientation == media.Orientation { + for tag, style := range anotherMedia.Styles { + media.Styles[tag] = style } exists = true break @@ -211,7 +365,7 @@ func (theme *theme) cssText(session Session) string { for _, media := range theme.mediaStyles { builder.startMedia(media.cssText()) - for tag, obj := range media.styles { + for tag, obj := range media.Styles { var style viewStyle style.init() for tag, value := range obj { @@ -363,7 +517,7 @@ func (theme *theme) parseThemeData(data DataObject) { 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()] = objToParams(obj) + rule.Styles[obj.Tag()] = objToParams(obj) } } } @@ -376,13 +530,66 @@ func (theme *theme) parseThemeData(data DataObject) { 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].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 + if theme.mediaStyles[i].MaxWidth != theme.mediaStyles[j].MaxWidth { + 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{} +}