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 }