Refactor Hyprland configs, introduce Sithego for theming, and add supporting scripts
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
package themer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
Meta struct {
|
||||
Name string
|
||||
Type string
|
||||
Version string
|
||||
}
|
||||
Primitive map[string]string
|
||||
Colors struct {
|
||||
Semantic struct {
|
||||
Background Color
|
||||
Surface Color
|
||||
SurfaceAlt Color
|
||||
Disabled Color
|
||||
Border Color
|
||||
Text Color
|
||||
Accent Color
|
||||
Warn Color
|
||||
}
|
||||
} `toml:"Colors"`
|
||||
Spacing struct {
|
||||
Padding string
|
||||
PaddingSmall string
|
||||
PaddingLarge string
|
||||
Margin string
|
||||
MarginSmall string
|
||||
Radius string
|
||||
RadiusSmall string
|
||||
RadiusLarge string
|
||||
} `toml:"Spacing"`
|
||||
}
|
||||
|
||||
type Color struct {
|
||||
raw string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (c Color) Alpha(bg Color, amount float64) string {
|
||||
r1, g1, b1, ok1 := parseHexRGB(c.Value)
|
||||
r2, g2, b2, ok2 := parseHexRGB(bg.Value)
|
||||
if !ok1 || !ok2 {
|
||||
return c.Value
|
||||
}
|
||||
r := blend8(r2, r1, amount)
|
||||
g := blend8(g2, g1, amount)
|
||||
b := blend8(b2, b1, amount)
|
||||
return formatHexRGB(r, g, b)
|
||||
}
|
||||
|
||||
func (c *Color) UnmarshalText(text []byte) error {
|
||||
c.raw = strings.TrimSpace(string(text))
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadTheme(path string) (Theme, error) {
|
||||
theme := Theme{}
|
||||
if themeFile, err := os.ReadFile(path); err != nil {
|
||||
return theme, err
|
||||
} else {
|
||||
if err := toml.Unmarshal(themeFile, &theme); err != nil {
|
||||
return theme, err
|
||||
}
|
||||
}
|
||||
theme.resolveColor(&theme.Colors.Semantic.Background)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Surface)
|
||||
theme.resolveColor(&theme.Colors.Semantic.SurfaceAlt)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Disabled)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Border)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Text)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Accent)
|
||||
theme.resolveColor(&theme.Colors.Semantic.Warn)
|
||||
|
||||
return theme, nil
|
||||
}
|
||||
|
||||
func (t Theme) resolveColor(color *Color) {
|
||||
raw, shadeRaw, hasShade := strings.Cut(color.raw, "|")
|
||||
base := strings.TrimSpace(raw)
|
||||
shade, err := strconv.Atoi(strings.TrimSpace(shadeRaw))
|
||||
if primitive, ok := t.Primitive[base]; ok {
|
||||
base = primitive
|
||||
} else {
|
||||
base = raw
|
||||
}
|
||||
base = normalizeHex(base)
|
||||
if !hasShade || err != nil {
|
||||
color.Value = base
|
||||
return
|
||||
}
|
||||
if shade < 0 {
|
||||
shade = 0
|
||||
}
|
||||
if shade > 1000 {
|
||||
shade = 1000
|
||||
}
|
||||
|
||||
r, g, b, ok := parseHexRGB(base)
|
||||
if !ok {
|
||||
color.Value = base
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case shade == 500:
|
||||
// unchanged
|
||||
case shade < 500:
|
||||
amount := float64(500-shade) / 500.0
|
||||
r = blend8(r, 255, amount)
|
||||
g = blend8(g, 255, amount)
|
||||
b = blend8(b, 255, amount)
|
||||
default:
|
||||
amount := float64(shade-500) / 500.0
|
||||
r = blend8(r, 0, amount)
|
||||
g = blend8(g, 0, amount)
|
||||
b = blend8(b, 0, amount)
|
||||
}
|
||||
color.Value = formatHexRGB(r, g, b)
|
||||
}
|
||||
|
||||
func normalizeHex(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
if strings.HasPrefix(s, "#") {
|
||||
return s
|
||||
}
|
||||
return "#" + s
|
||||
}
|
||||
|
||||
func parseHexRGB(s string) (uint8, uint8, uint8, bool) {
|
||||
s = strings.TrimSpace(s)
|
||||
if strings.HasPrefix(s, "#") {
|
||||
s = s[1:]
|
||||
}
|
||||
if len(s) != 6 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
// Manual hex parsing to avoid extra deps; strconv handles base 16 fine.
|
||||
rv, err1 := strconv.ParseUint(s[0:2], 16, 8)
|
||||
gv, err2 := strconv.ParseUint(s[2:4], 16, 8)
|
||||
bv, err3 := strconv.ParseUint(s[4:6], 16, 8)
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
return uint8(rv), uint8(gv), uint8(bv), true
|
||||
}
|
||||
func formatHexRGB(r, g, b uint8) string {
|
||||
const hex = "0123456789abcdef"
|
||||
out := make([]byte, 7)
|
||||
out[0] = '#'
|
||||
out[1] = hex[r>>4]
|
||||
out[2] = hex[r&0x0f]
|
||||
out[3] = hex[g>>4]
|
||||
out[4] = hex[g&0x0f]
|
||||
out[5] = hex[b>>4]
|
||||
out[6] = hex[b&0x0f]
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func blend8(a, b uint8, t float64) uint8 {
|
||||
// result = a*(1-t) + b*t, rounded
|
||||
v := float64(a)*(1.0-t) + float64(b)*t
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
if v > 255 {
|
||||
v = 255
|
||||
}
|
||||
return uint8(v + 0.5)
|
||||
}
|
||||
Reference in New Issue
Block a user