clui/theme.go

316 lines
7.2 KiB
Go
Raw Normal View History

2015-09-21 20:54:39 -07:00
package clui
2015-10-16 10:27:43 -07:00
import (
2015-10-20 14:32:16 -07:00
"bufio"
2015-10-16 10:27:43 -07:00
term "github.com/nsf/termbox-go"
2015-10-20 14:32:16 -07:00
"io/ioutil"
"os"
"strings"
2015-10-16 10:27:43 -07:00
)
2015-09-21 20:54:39 -07:00
/*
Theme support for controls.
The current implementation is limited but later the manager will be
able to load a requested theme on demand and use deep inheritance.
Theme 'default' exists always - it is predefinded and always complete.
User-defined themes may omit any theme section, all omitted items
are loaded from parent theme. The only required property that a user-
defined theme must have is a theme name.
*/
type ThemeManager struct {
// available theme list
themes map[string]theme
// name of the current theme
2015-10-20 14:32:16 -07:00
current string
themePath string
version string
2015-09-21 20:54:39 -07:00
}
2015-10-20 15:46:14 -07:00
type ThemeInfo struct {
parent string
title string
author string
version string
}
2015-09-21 20:54:39 -07:00
/*
A theme structure. It keeps all colors, characters for the theme.
Parent property determines a theme name that is used if a requested
theme object is not declared in the current one. If no parent is
defined then the library uses default built-in theme.
*/
type theme struct {
parent string
2015-10-20 14:32:16 -07:00
title string
author string
version string
2015-10-20 15:46:14 -07:00
colors map[string]term.Attribute
objects map[string]string
2015-09-21 20:54:39 -07:00
}
const defaultTheme = "default"
func NewThemeManager() *ThemeManager {
sm := new(ThemeManager)
2015-10-20 14:32:16 -07:00
sm.Reset()
return sm
}
func (s *ThemeManager) Reset() {
s.current = defaultTheme
s.themes = make(map[string]theme, 0)
defTheme := theme{parent: "", title: "Default Theme", author: "V. Markelov", version: "1.0"}
defTheme.colors = make(map[string]term.Attribute, 0)
defTheme.objects = make(map[string]string, 0)
2015-10-16 10:27:43 -07:00
defTheme.objects[ObjSingleBorder] = "─│┌┐└┘"
defTheme.objects[ObjDoubleBorder] = "═║╔╗╚╝"
defTheme.objects[ObjEdit] = "←→V"
defTheme.objects[ObjScrollBar] = "|O^v"
defTheme.objects[ObjViewButtons] = "^↓○[]"
defTheme.objects[ObjCheckBox] = "[] X?"
defTheme.objects[ObjRadio] = "() *"
defTheme.objects[ObjProgressBar] = "░▒"
2015-10-19 12:05:43 -07:00
defTheme.colors[ColorDisabledText] = ColorBlackBold
defTheme.colors[ColorDisabledBack] = ColorWhite
2015-10-16 10:27:43 -07:00
defTheme.colors[ColorText] = ColorWhite
2015-10-19 16:54:26 -07:00
defTheme.colors[ColorBack] = ColorBlackBold
2015-10-16 10:27:43 -07:00
defTheme.colors[ColorViewBack] = ColorBlackBold
defTheme.colors[ColorViewText] = ColorWhite
2015-09-21 20:54:39 -07:00
2015-10-19 12:05:43 -07:00
defTheme.colors[ColorControlText] = ColorWhite
defTheme.colors[ColorControlBack] = ColorBlack
defTheme.colors[ColorControlActiveText] = ColorWhite
defTheme.colors[ColorControlActiveBack] = ColorMagenta
defTheme.colors[ColorControlShadow] = ColorBlue
defTheme.colors[ColorControlDisabledText] = ColorWhite
defTheme.colors[ColorControlDisabledBack] = ColorBlackBold
defTheme.colors[ColorEditText] = ColorBlack
defTheme.colors[ColorEditBack] = ColorWhite
defTheme.colors[ColorEditActiveText] = ColorBlack
defTheme.colors[ColorEditActiveBack] = ColorWhiteBold
defTheme.colors[ColorSelectionText] = ColorYellow
defTheme.colors[ColorSelectionBack] = ColorBlue
defTheme.colors[ColorScrollBack] = ColorBlackBold
defTheme.colors[ColorScrollText] = ColorWhite
defTheme.colors[ColorThumbBack] = ColorBlackBold
defTheme.colors[ColorThumbText] = ColorWhite
defTheme.colors[ColorProgressText] = ColorBlue
defTheme.colors[ColorProgressBack] = ColorBlackBold
defTheme.colors[ColorProgressActiveText] = ColorBlack
defTheme.colors[ColorProgressActiveBack] = ColorBlueBold
2015-10-20 14:32:16 -07:00
s.themes[defaultTheme] = defTheme
2015-09-21 20:54:39 -07:00
}
2015-10-20 14:32:16 -07:00
func (s *ThemeManager) SysColor(color string) term.Attribute {
2015-09-21 20:54:39 -07:00
sch, ok := s.themes[s.current]
if !ok {
sch = s.themes[defaultTheme]
}
clr, okclr := sch.colors[color]
2015-10-21 10:22:31 -07:00
if !okclr {
visited := make(map[string]int, 0)
visited[s.current] = 1
if !ok {
visited[defaultTheme] = 1
}
for {
s.LoadTheme(sch.parent)
sch = s.themes[sch.parent]
clr, okclr = sch.colors[color]
if ok {
break
} else {
if _, okSch := visited[sch.parent]; okSch {
panic("Color + " + color + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
} else {
visited[sch.parent] = 1
}
}
2015-09-21 20:54:39 -07:00
}
}
return clr
}
2015-10-20 14:32:16 -07:00
func (s *ThemeManager) SysObject(object string) string {
2015-09-21 20:54:39 -07:00
sch, ok := s.themes[s.current]
if !ok {
sch = s.themes[defaultTheme]
}
obj, okobj := sch.objects[object]
2015-10-21 10:22:31 -07:00
if !okobj {
visited := make(map[string]int, 0)
visited[s.current] = 1
if !ok {
visited[defaultTheme] = 1
}
for {
s.LoadTheme(sch.parent)
sch = s.themes[sch.parent]
obj, okobj = sch.objects[object]
if ok {
break
} else {
if _, okSch := visited[sch.parent]; okSch {
panic("Object: " + object + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
} else {
visited[sch.parent] = 1
}
}
}
2015-09-21 20:54:39 -07:00
}
return obj
}
2015-10-20 15:46:14 -07:00
func (s *ThemeManager) ThemeNames() []string {
2015-10-20 14:32:16 -07:00
var str []string
str = append(str, defaultTheme)
path := s.themePath
if path == "" {
path = "." + string(os.PathSeparator)
}
files, err := ioutil.ReadDir(path)
if err != nil {
panic("Failed to read theme directory: " + s.themePath)
}
for _, f := range files {
if !f.IsDir() {
str = append(str, f.Name())
}
2015-09-21 20:54:39 -07:00
}
return str
}
func (s *ThemeManager) CurrentTheme() string {
2015-09-21 20:54:39 -07:00
return s.current
}
func (s *ThemeManager) SetCurrentTheme(name string) bool {
2015-10-20 14:32:16 -07:00
if _, ok := s.themes[name]; !ok {
2015-10-20 15:46:14 -07:00
tnames := s.ThemeNames()
2015-10-20 14:32:16 -07:00
for _, theme := range tnames {
if theme == name {
s.LoadTheme(theme)
break
}
}
}
2015-09-21 20:54:39 -07:00
if _, ok := s.themes[name]; ok {
s.current = name
return true
}
return false
}
2015-10-20 14:32:16 -07:00
func (s *ThemeManager) ThemePath() string {
return s.themePath
}
func (s *ThemeManager) SetThemePath(path string) {
if path == s.themePath {
return
}
s.themePath = path
s.Reset()
}
func (s *ThemeManager) LoadTheme(name string) {
if _, ok := s.themes[name]; ok {
2015-10-21 10:22:31 -07:00
return
2015-10-20 14:32:16 -07:00
}
theme := theme{parent: "", title: "", author: ""}
theme.colors = make(map[string]term.Attribute, 0)
theme.objects = make(map[string]string, 0)
file, err := os.Open(s.themePath + string(os.PathSeparator) + name)
if err != nil {
panic("Failed to open theme " + name + " : " + err.Error())
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
line = strings.Trim(line, " ")
// skip comments
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
continue
}
// skip invalid lines
if !strings.Contains(line, "=") {
continue
}
parts := strings.SplitN(line, "=", 2)
key := strings.Trim(parts[0], " ")
value := strings.Trim(parts[1], " ")
low := strings.ToLower(key)
if low == "parent" {
theme.parent = value
} else if low == "author" {
theme.author = value
} else if low == "name" || low == "title" {
theme.title = value
} else if low == "version" {
theme.version = value
} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
c := StringToColor(value)
if c%32 == 0 {
panic("Failed to read color: " + value)
}
theme.colors[key] = c
} else {
theme.objects[key] = value
}
}
if theme.parent == "" {
theme.parent = "default"
}
s.themes[name] = theme
}
2015-10-20 15:46:14 -07:00
2015-10-21 10:22:31 -07:00
func (s *ThemeManager) ReLoadTheme(name string) {
if _, ok := s.themes[name]; ok {
delete(s.themes, name)
}
s.LoadTheme(name)
}
2015-10-20 15:46:14 -07:00
func (s *ThemeManager) ThemeInfo(name string) ThemeInfo {
s.LoadTheme(name)
var theme ThemeInfo
if t, ok := s.themes[name]; !ok {
theme.parent = t.parent
theme.title = t.title
theme.version = t.version
}
return theme
}