| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- // Copyright 2024 The tk9.0-go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package tk9_0 // import "modernc.org/tk9.0"
- import (
- "errors"
- "fmt"
- "reflect"
- "sort"
- "strings"
- )
- var (
- // Themes register Tk themes. User code must not directly mutate Themes.
- Themes = map[ThemeKey]Theme{}
- AlreadyActivated = errors.New("Already activated")
- AlreadyRegistered = errors.New("Already registered")
- Finalized = errors.New("Finalized")
- NotActivated = errors.New("Not activated")
- NotFound = errors.New("Not found")
- currentTheme Theme
- currentThemeKey ThemeKey
- _ Theme = (*theme)(nil)
- _ Theme = (*builtinTheme)(nil)
- _ ThemeContext = themeContext{}
- )
- // https://tkdocs.com/tutorial/styles.html
- //
- // Besides the built-in themes (alt, default, clam, and classic), macOS
- // includes a theme named aqua to match the system-wide style, while Windows
- // includes themes named vista, winxpnative, and winnative.
- func init() {
- RegisterTheme("alt", &builtinTheme{"alt"})
- RegisterTheme("default", &builtinTheme{"default"})
- RegisterTheme("clam", &builtinTheme{"clam"})
- RegisterTheme("classic", &builtinTheme{"classic"})
- switch goos {
- case "darwin":
- RegisterTheme("aqua", &builtinTheme{"aqua"})
- case "windows":
- RegisterTheme("vista", &builtinTheme{"vista"})
- RegisterTheme("winxpnative", &builtinTheme{"winxpnative"})
- RegisterTheme("winnative", &builtinTheme{"winnative"})
- }
- }
- type builtinTheme struct {
- name string
- }
- func (t *builtinTheme) Activate(context ThemeContext) error {
- StyleThemeUse(t.name)
- return nil
- }
- func (t *builtinTheme) Deactivate(context ThemeContext) error {
- StyleThemeUse("default")
- return nil
- }
- func (t *builtinTheme) Finalize(context ThemeContext) error {
- return nil
- }
- func (t *builtinTheme) Initialize(context ThemeContext) error {
- return nil
- }
- // CurrentTheme returns the currently activated theme, if any.
- func CurrentTheme() Theme {
- return currentTheme
- }
- // CurrentThemeName returns the name of the currently activated theme, if any.
- func CurrentThemeName() (r string) {
- return currentThemeKey.Name
- }
- // ActivateTheme searches [Themes] to find first theme named like 'name' and
- // call its Activate method. The search is case insensitive, using
- // strings.ToLower, and white space is normalized. If there's no match,
- // ActivateTheme returns [NotFound].
- //
- // Any package can register themes but only the main package can activate a
- // theme.
- func ActivateTheme(name string) (err error) {
- if !isCalledFromMain() {
- return NotActivated
- }
- var keys []ThemeKey
- for k := range Themes {
- keys = append(keys, k)
- }
- sort.Slice(keys, func(a, b int) bool {
- c, d := keys[a], keys[b]
- e, f := matchName(c.Name), matchName(d.Name)
- if e < f {
- return true
- }
- if e > f {
- return false
- }
- return c.Type < d.Type
- })
- name = matchName(name)
- for _, k := range keys {
- if matchName(k.Name) == name {
- return Themes[k].Activate(nil)
- }
- }
- return NotFound
- }
- func matchName(s string) string {
- return strings.Join(strings.Fields(strings.ToLower(s)), " ")
- }
- // RegisterTheme registers t.
- func RegisterTheme(name string, t Theme) (r ThemeKey, err error) {
- k := ThemeKey{Type: typeName(t), Name: name}
- if _, ok := Themes[k]; ok {
- return r, AlreadyRegistered
- }
- Themes[k] = &theme{inner: t, k: k}
- return k, nil
- }
- // ThemeKey indexes Themes
- type ThemeKey struct {
- Type string
- Name string
- }
- // ThemeContext provides context to Theme methods.
- type ThemeContext interface {
- Eval(tcl string) (r string, err error)
- }
- type themeContext struct{}
- func newThemeContext() (r themeContext) {
- evalFunc = eval
- return r
- }
- // Eval evaluates 'tcl' and returns a result value and an error, if any.
- func (themeContext) Eval(tcl string) (r string, err error) {
- return evalFunc(tcl)
- }
- var evalFunc func(string) (string, error)
- // Theme provides handling of a Tk theme. When calling Theme methods registered
- // in Theme, the context argument is ignored and an instance is created
- // automatically.
- type Theme interface {
- // Activate makes the theme active/in use. The Activate method of themes in
- // Themes automatically call Initialize if it was not called before.
- Activate(context ThemeContext) error
- // Deactivate makes the theme not active. Deactivate cannot be called before
- // Activate().
- Deactivate(context ThemeContext) error
- // Finalize is called to perform any cleanup. After Finalize returns, the theme
- // cannot be used. The Finalize method of themes in Themes automatically remove
- // the theme from Themes after Finalize completes.
- Finalize(context ThemeContext) error
- // Initialize is called to perform any one-time initialization of a theme. The
- // Initialize method of themes in Themes can be called multiple times but only
- // the first successful call to Initialize will have any effect.
- Initialize(context ThemeContext) error
- }
- type theme struct {
- inner Theme
- k ThemeKey
- activated bool
- finalized bool
- initialized bool
- }
- func (t *theme) Activate(context ThemeContext) (err error) {
- if currentTheme != nil {
- currentTheme.Deactivate(nil)
- currentTheme = nil
- currentThemeKey = ThemeKey{}
- }
- context = newThemeContext()
- defer func() {
- evalFunc = nil
- if err == nil {
- t.activated = true
- currentTheme = t
- currentThemeKey = t.k
- }
- }()
- if t.finalized {
- return Finalized
- }
- if t.activated {
- return AlreadyActivated
- }
- if !t.initialized {
- if err = t.inner.Initialize(context); err != nil {
- return err
- }
- }
- evalFunc = eval
- return t.inner.Activate(context)
- }
- func (t *theme) Deactivate(context ThemeContext) (err error) {
- context = newThemeContext()
- defer func() {
- evalFunc = nil
- t.activated = false
- currentTheme = nil
- currentThemeKey = ThemeKey{}
- }()
- if t.finalized {
- return Finalized
- }
- if !t.activated {
- return NotActivated
- }
- return t.inner.Deactivate(context)
- }
- func (t *theme) Finalize(context ThemeContext) (err error) {
- context = newThemeContext()
- defer func() {
- evalFunc = nil
- t.finalized = true
- delete(Themes, t.k)
- currentTheme = nil
- currentThemeKey = ThemeKey{}
- }()
- if !t.finalized {
- err = t.inner.Finalize(context)
- }
- return err
- }
- func (t *theme) Initialize(context ThemeContext) (err error) {
- context = newThemeContext()
- defer func() {
- evalFunc = nil
- if err == nil {
- t.initialized = true
- }
- }()
- if t.finalized {
- return Finalized
- }
- if !t.initialized {
- err = t.inner.Initialize(context)
- }
- return err
- }
- func typeName(th any) string {
- t := reflect.TypeOf(th)
- if t.Kind() == reflect.Pointer {
- t = t.Elem()
- }
- return fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
- }
|