forked from mbk-lab/rui_orig
				
			Added Theme interface, NewTheme, CreateThemeFromText, and AddTheme functions
This commit is contained in:
		
							parent
							
								
									0286918dd4
								
							
						
					
					
						commit
						894242f2dd
					
				|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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=[" | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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 | ||||
| 			} | ||||
|  |  | |||
							
								
								
									
										6
									
								
								init.go
								
								
								
								
							
							
						
						
									
										6
									
								
								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 | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										52
									
								
								resources.go
								
								
								
								
							
							
						
						
									
										52
									
								
								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 | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										67
									
								
								session.go
								
								
								
								
							
							
						
						
									
										67
									
								
								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() { | ||||
|  |  | |||
							
								
								
									
										106
									
								
								sessionTheme.go
								
								
								
								
							
							
						
						
									
										106
									
								
								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() | ||||
| } | ||||
|  |  | |||
							
								
								
									
										329
									
								
								theme.go
								
								
								
								
							
							
						
						
									
										329
									
								
								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{} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue