github.com/jmigpin/editor@v1.6.0/ui/theme.go (about)

     1  package ui
     2  
     3  import (
     4  	"fmt"
     5  	"image"
     6  	"image/color"
     7  	"io/ioutil"
     8  
     9  	"github.com/golang/freetype/truetype"
    10  	"github.com/jmigpin/editor/util/fontutil"
    11  	"github.com/jmigpin/editor/util/imageutil"
    12  	"github.com/jmigpin/editor/util/uiutil/widget"
    13  	"golang.org/x/image/font/gofont/gomedium"
    14  	"golang.org/x/image/font/gofont/gomono"
    15  	"golang.org/x/image/font/gofont/goregular"
    16  )
    17  
    18  var (
    19  	ScrollBarLeft             = true
    20  	ScrollBarWidth        int = 0 // 0=based on a portion of the font size
    21  	TextAreaCommentsColor color.Color
    22  	TextAreaStringsColor  color.Color
    23  )
    24  
    25  const (
    26  	separatorWidth = 1 // col/row separators width
    27  )
    28  
    29  //----------
    30  
    31  // Palette with user supplied color options that should override themes.
    32  func userPalette() widget.Palette {
    33  	pal := widget.Palette{}
    34  	if TextAreaCommentsColor != nil {
    35  		pal["text_colorize_comments_fg"] = TextAreaCommentsColor
    36  	}
    37  	if TextAreaStringsColor != nil {
    38  		pal["text_colorize_string_fg"] = TextAreaStringsColor
    39  	}
    40  	return pal
    41  }
    42  
    43  //----------
    44  
    45  func lightThemeColors(node widget.Node) {
    46  	pal := widget.Palette{
    47  		"text_cursor_fg":            cint(0x0),
    48  		"text_fg":                   cint(0x0),
    49  		"text_bg":                   cint(0xffffff),
    50  		"text_selection_fg":         nil,
    51  		"text_selection_bg":         cint(0xeeee9e), // yellow
    52  		"text_colorize_string_fg":   nil,
    53  		"text_colorize_comments_fg": cint(0x008b00), // green
    54  		"text_highlightword_fg":     nil,
    55  		"text_highlightword_bg":     cint(0xc6ee9e), // green
    56  		"text_wrapline_fg":          cint(0x0),
    57  		"text_wrapline_bg":          cint(0xd8d8d8),
    58  		"text_parenthesis_fg":       nil,
    59  		"text_parenthesis_bg":       cint(0xd8d8d8),
    60  
    61  		"toolbar_text_bg":          cint(0xecf0f1), // "clouds" grey
    62  		"toolbar_text_wrapline_bg": cint(0xccccd8),
    63  
    64  		"scrollbar_bg":        cint(0xf2f2f2),
    65  		"scrollhandle_normal": imageutil.Shade(cint(0xf2f2f2), 0.20),
    66  		"scrollhandle_hover":  imageutil.Shade(cint(0xf2f2f2), 0.30),
    67  		"scrollhandle_select": imageutil.Shade(cint(0xf2f2f2), 0.40),
    68  
    69  		"column_norows_rect":  cint(0xffffff),
    70  		"columns_nocols_rect": cint(0xffffff),
    71  		"colseparator_rect":   cint(0x0),
    72  		"rowseparator_rect":   cint(0x0),
    73  		"shadowsep_rect":      cint(0x0),
    74  
    75  		"columnsquare": cint(0xccccd8),
    76  		"rowsquare":    cint(0xccccd8),
    77  
    78  		"mm_text_bg":          cint(0xecf0f1),
    79  		"mm_button_hover_bg":  cint(0xcccccc),
    80  		"mm_button_down_bg":   cint(0xbbbbbb),
    81  		"mm_button_sticky_fg": cint(0xffffff),
    82  		"mm_button_sticky_bg": cint(0x0),
    83  		"mm_border":           cint(0x0),
    84  		"mm_content_pad":      cint(0xecf0f1),
    85  		"mm_content_border":   cint(0x0),
    86  
    87  		"contextfloatbox_border": cint(0x0),
    88  	}
    89  
    90  	pal.Merge(rowSquarePalette())
    91  	pal.Merge(userPalette())
    92  	node.Embed().SetThemePalette(pal)
    93  }
    94  
    95  //----------
    96  
    97  func darkThemeColors(node widget.Node) {
    98  	pal := widget.Palette{
    99  		"text_cursor_fg":            cint(0xffffff),
   100  		"text_fg":                   cint(0xffffff),
   101  		"text_bg":                   cint(0x0),
   102  		"text_selection_fg":         cint(0xffffff),
   103  		"text_selection_bg":         cint(0xafa753), // yellow
   104  		"text_colorize_string_fg":   nil,
   105  		"text_colorize_comments_fg": cint(0xb8b8b8),
   106  		"text_highlightword_bg":     cint(0x58842d), // green
   107  		"text_wrapline_fg":          cint(0xffffff),
   108  		"text_wrapline_bg":          cint(0x595959),
   109  
   110  		"toolbar_text_fg":          cint(0xffffff),
   111  		"toolbar_text_bg":          cint(0x808080),
   112  		"toolbar_text_wrapline_bg": imageutil.Shade(cint(0x808080), 0.20),
   113  
   114  		"scrollbar_bg":        imageutil.Tint(cint(0x0), 0.20),
   115  		"scrollhandle_normal": imageutil.Tint(cint(0x0), 0.40),
   116  		"scrollhandle_hover":  imageutil.Tint(cint(0x0), 0.50),
   117  		"scrollhandle_select": imageutil.Tint(cint(0x0), 0.60),
   118  
   119  		"column_norows_rect":  imageutil.Tint(cint(0x0), 0.10),
   120  		"columns_nocols_rect": imageutil.Tint(cint(0x0), 0.10),
   121  		"colseparator_rect":   cint(0x0),
   122  		"rowseparator_rect":   cint(0x0),
   123  		"shadowsep_rect":      cint(0x0),
   124  
   125  		"columnsquare": imageutil.Shade(cint(0x808080), 0.20),
   126  		"rowsquare":    imageutil.Shade(cint(0x808080), 0.20),
   127  
   128  		"mm_text_bg":          cint(0x808080),
   129  		"mm_button_hover_bg":  imageutil.Tint(cint(0x808080), 0.10),
   130  		"mm_button_down_bg":   imageutil.Tint(cint(0x808080), 0.20),
   131  		"mm_button_sticky_bg": imageutil.Tint(cint(0x808080), 0.40),
   132  		"mm_border":           cint(0x0),
   133  		"mm_content_pad":      cint(0x808080),
   134  		"mm_content_border":   cint(0x0),
   135  
   136  		"contextfloatbox_border": cint(0xffffff),
   137  	}
   138  
   139  	pal.Merge(rowSquarePalette())
   140  	pal.Merge(userPalette())
   141  	node.Embed().SetThemePalette(pal)
   142  }
   143  
   144  //----------
   145  
   146  func acmeThemeColors(node widget.Node) {
   147  	pal := widget.Palette{
   148  		"text_cursor_fg":            cint(0x0),
   149  		"text_fg":                   cint(0x0),
   150  		"text_bg":                   cint(0xffffea),
   151  		"text_selection_fg":         nil,
   152  		"text_selection_bg":         cint(0xeeee9e), // yellow
   153  		"text_colorize_string_fg":   nil,
   154  		"text_colorize_comments_fg": cint(0x008b00), // green
   155  		"text_highlightword_fg":     nil,
   156  		"text_highlightword_bg":     cint(0xc6ee9e), // green
   157  		"text_wrapline_fg":          cint(0x0),
   158  		"text_wrapline_bg":          cint(0xd8d8c6),
   159  
   160  		"toolbar_text_bg":          cint(0xeaffff),
   161  		"toolbar_text_wrapline_bg": cint(0xc6d8d8),
   162  
   163  		"scrollbar_bg":        cint(0xf2f2de),
   164  		"scrollhandle_normal": cint(0xc1c193),
   165  		"scrollhandle_hover":  cint(0xadad6f),
   166  		"scrollhandle_select": cint(0x99994c),
   167  
   168  		"column_norows_rect":  cint(0xffffea),
   169  		"columns_nocols_rect": cint(0xffffff),
   170  		"colseparator_rect":   cint(0x0),
   171  		"rowseparator_rect":   cint(0x0),
   172  		"shadowsep_rect":      cint(0x0),
   173  
   174  		"columnsquare": cint(0xc6d8d8),
   175  		"rowsquare":    cint(0xc6d8d8),
   176  
   177  		"mm_text_bg":          cint(0xeaffff),
   178  		"mm_button_hover_bg":  imageutil.Shade(cint(0xeaffff), 0.10),
   179  		"mm_button_down_bg":   imageutil.Shade(cint(0xeaffff), 0.20),
   180  		"mm_button_sticky_bg": imageutil.Shade(cint(0xeaffff), 0.40),
   181  		"mm_border":           cint(0x0),
   182  		"mm_content_pad":      cint(0xeaffff),
   183  		"mm_content_border":   cint(0x0),
   184  
   185  		"contextfloatbox_border": cint(0x0),
   186  	}
   187  
   188  	pal.Merge(rowSquarePalette())
   189  	pal.Merge(userPalette())
   190  	node.Embed().SetThemePalette(pal)
   191  }
   192  
   193  //----------
   194  
   195  func rowSquarePalette() widget.Palette {
   196  	pal := widget.Palette{
   197  		"rs_active":              cint(0x0),
   198  		"rs_executing":           cint(0x0fad00),                       // dark green
   199  		"rs_edited":              cint(0x0000ff),                       // blue
   200  		"rs_disk_changes":        cint(0xff0000),                       // red
   201  		"rs_not_exist":           cint(0xff9900),                       // orange
   202  		"rs_duplicate":           cint(0x8888cc),                       // blueish
   203  		"rs_duplicate_highlight": cint(0xffff00),                       // yellow
   204  		"rs_annotations":         cint(0xd35400),                       // pumpkin
   205  		"rs_annotations_edited":  imageutil.Tint(cint(0xd35400), 0.45), // pumpkin (brighter)
   206  	}
   207  	return pal
   208  }
   209  
   210  //----------
   211  
   212  var ColorThemeCycler cycler = cycler{
   213  	entries: []cycleEntry{
   214  		{"light", lightThemeColors},
   215  		{"dark", darkThemeColors},
   216  		{"acme", acmeThemeColors},
   217  	},
   218  }
   219  
   220  //----------
   221  
   222  var FontThemeCycler cycler = cycler{
   223  	entries: []cycleEntry{
   224  		{"regular", regularThemeFont},
   225  		{"medium", mediumThemeFont},
   226  		{"mono", monoThemeFont},
   227  	},
   228  }
   229  
   230  //----------
   231  
   232  func regularThemeFont(node widget.Node) {
   233  	loadThemeFont("regular", node)
   234  }
   235  func mediumThemeFont(node widget.Node) {
   236  	loadThemeFont("medium", node)
   237  }
   238  func monoThemeFont(node widget.Node) {
   239  	loadThemeFont("mono", node)
   240  }
   241  
   242  //----------
   243  
   244  func AddUserFont(filename string) error {
   245  	// test now if it will load when needed
   246  	_, err := ThemeFontFace(filename)
   247  	if err != nil {
   248  		return err
   249  	}
   250  
   251  	// prepare callback and add to font cycler
   252  	f := func(node widget.Node) {
   253  		_ = loadThemeFont(filename, node)
   254  	}
   255  	e := cycleEntry{filename, f}
   256  	FontThemeCycler.entries = append(FontThemeCycler.entries, e)
   257  	FontThemeCycler.CurName = filename
   258  	return nil
   259  }
   260  
   261  //----------
   262  
   263  func loadThemeFont(name string, node widget.Node) error {
   264  	// close previous faces
   265  	ff0 := node.Embed().TreeThemeFontFace()
   266  	ff0.Font.ClearFacesCache()
   267  
   268  	ff, err := ThemeFontFace(name)
   269  	if err != nil {
   270  		return err
   271  	}
   272  	node.Embed().SetThemeFontFace(ff)
   273  	return nil
   274  }
   275  
   276  //----------
   277  
   278  var TTFontOptions truetype.Options
   279  
   280  func ThemeFontFace(name string) (*fontutil.FontFace, error) {
   281  	return ThemeFontFace2(name, 0)
   282  }
   283  func ThemeFontFace2(name string, size float64) (*fontutil.FontFace, error) {
   284  	b, err := fontBytes(name)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	f, err := fontutil.FontsMan.Font(b)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	opt := TTFontOptions // copy
   293  	if size != 0 {
   294  		opt.Size = size
   295  	}
   296  	return f.FontFace(opt), nil
   297  }
   298  
   299  func fontBytes(name string) ([]byte, error) {
   300  	switch name {
   301  	case "regular":
   302  		return goregular.TTF, nil
   303  	case "medium":
   304  		return gomedium.TTF, nil
   305  	case "mono":
   306  		return gomono.TTF, nil
   307  	default:
   308  		return ioutil.ReadFile(name)
   309  	}
   310  }
   311  
   312  //----------
   313  
   314  type cycler struct {
   315  	CurName string
   316  	entries []cycleEntry
   317  }
   318  
   319  func (c *cycler) GetIndex(name string) (int, bool) {
   320  	for i, e := range c.entries {
   321  		if e.name == name {
   322  			return i, true
   323  		}
   324  	}
   325  	return -1, false
   326  }
   327  
   328  func (c *cycler) Cycle(node widget.Node) {
   329  	i := 0
   330  	if c.CurName != "" {
   331  		k, ok := c.GetIndex(c.CurName)
   332  		if !ok {
   333  			panic(fmt.Sprintf("cycle name not found: %v", c.CurName))
   334  		}
   335  		i = (k + 1) % len(c.entries)
   336  	}
   337  	c.Set(c.entries[i].name, node)
   338  }
   339  
   340  func (c *cycler) Set(name string, node widget.Node) {
   341  	i, ok := c.GetIndex(name)
   342  	if !ok {
   343  		panic(fmt.Sprintf("cycle name not found: %v", name))
   344  	}
   345  	c.CurName = name
   346  	c.entries[i].fn(node)
   347  }
   348  
   349  type cycleEntry struct {
   350  	name string
   351  	fn   func(widget.Node)
   352  }
   353  
   354  //----------
   355  
   356  var UIThemeUtil uiThemeUtil
   357  
   358  type uiThemeUtil struct{}
   359  
   360  func (uitu *uiThemeUtil) RowMinimumHeight(ff *fontutil.FontFace) int {
   361  	return ff.LineHeightInt()
   362  }
   363  func (uitu *uiThemeUtil) RowSquareSize(ff *fontutil.FontFace) image.Point {
   364  	lh := ff.LineHeightFloat()
   365  	w := int(lh * 3 / 4)
   366  	return image.Point{w, int(lh)}
   367  }
   368  
   369  func (uitu *uiThemeUtil) GetScrollBarWidth(ff *fontutil.FontFace) int {
   370  	if ScrollBarWidth != 0 {
   371  		return ScrollBarWidth
   372  	}
   373  	lh := ff.LineHeightFloat()
   374  	w := int(lh * 3 / 4)
   375  	return w
   376  }
   377  
   378  func (uitu *uiThemeUtil) ShadowHeight(ff *fontutil.FontFace) int {
   379  	lh := ff.LineHeightFloat()
   380  	return int(lh * 2 / 5)
   381  }
   382  
   383  //----------
   384  
   385  func cint(c int) color.RGBA {
   386  	return imageutil.RgbaFromInt(c)
   387  }