Refactor Hyprland configs, introduce Sithego for theming, and add supporting scripts
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
module Sithego
|
||||
|
||||
go 1.26
|
||||
|
||||
require github.com/pelletier/go-toml/v2 v2.2.4
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"Sithego/themer"
|
||||
"Sithego/themer/adapters/gtk"
|
||||
"Sithego/themer/adapters/intellij"
|
||||
"Sithego/themer/adapters/qt"
|
||||
"Sithego/themer/adapters/starship"
|
||||
)
|
||||
|
||||
func main() {
|
||||
theme, err := themer.LoadTheme("theme.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := gtk.New(gtk.WithOutputDir("../dotfiles/.themes/")).Generate(theme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := qt.New(qt.WithOutputDir("../dotfiles/.config/")).Generate(theme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := intellij.New(intellij.WithOutputDir("../dotfiles/.themes/")).Generate(theme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := starship.New(starship.WithOutputDir("../dotfiles/.config/")).Generate(theme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[Meta]
|
||||
Name="Serpensortia"
|
||||
Type="dark"
|
||||
Version="0.1"
|
||||
|
||||
[Primitive]
|
||||
black="#131d13"
|
||||
silver="#c0c0c0"
|
||||
emerald="#2ecc71"
|
||||
crimson="#dc143c"
|
||||
|
||||
[Colors.Semantic]
|
||||
background="black|800"
|
||||
surface="black"
|
||||
surfaceAlt="black|400"
|
||||
disabled="black|400"
|
||||
border="black|700"
|
||||
text="silver"
|
||||
accent="emerald|700"
|
||||
warn="crimson"
|
||||
|
||||
[Spacing]
|
||||
paddingSmall="1px 2px"
|
||||
padding="2px 4px"
|
||||
paddingLarge="4px 8px"
|
||||
MarginSmall="1px"
|
||||
Margin="2px"
|
||||
RadiusSmall="4px"
|
||||
Radius="8px"
|
||||
RadiusLarge="16px"
|
||||
@@ -0,0 +1,6 @@
|
||||
package themer
|
||||
|
||||
type Adapter interface {
|
||||
Name() string
|
||||
Generate(theme Theme)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
* {
|
||||
-GtkHTML-cursor-color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
-GtkIMHtml-cursor-color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
-GtkTextView-error-underline-color: {{ .Theme.Colors.Semantic.Warn.Value }};
|
||||
-W3C-focus-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
outline-color: {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.5 }};
|
||||
background-clip: padding-box;
|
||||
-gtk-secondary-caret-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
.background {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
}
|
||||
|
||||
:disabled {
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
|
||||
window,
|
||||
.window-frame {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border-radius: {{ .Theme.Spacing.RadiusLarge }};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
button {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
padding: {{ .Theme.Spacing.Padding }};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
}
|
||||
|
||||
button:active,
|
||||
button:checked {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
|
||||
button.suggested-action {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
button.destructive-action {
|
||||
background-color: {{ .Theme.Colors.Semantic.Warn.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Warn.Value }};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
check, radio {
|
||||
margin: 0 {{ .Theme.Spacing.Margin }};
|
||||
min-height: 18px;
|
||||
min-width: 18px;
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
color: transparent;
|
||||
-gtk-icon-source: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
radio {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
check:checked {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
background-image:
|
||||
linear-gradient(45deg, transparent 45%, {{ .Theme.Colors.Semantic.Accent.Value }} 45%, {{ .Theme.Colors.Semantic.Accent.Value }} 55%, transparent 55%),
|
||||
linear-gradient(-45deg, transparent 45%, {{ .Theme.Colors.Semantic.Accent.Value }} 45%, {{ .Theme.Colors.Semantic.Accent.Value }} 55%, transparent 55%);
|
||||
background-size: 70% 70%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
radio:checked {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
background-image: radial-gradient(circle, {{ .Theme.Colors.Semantic.Accent.Value }} 35%, transparent 40%);
|
||||
}
|
||||
|
||||
check:indeterminate {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
background-image: linear-gradient(to right, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }});
|
||||
background-size: 60% 2px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
check:disabled,
|
||||
radio:disabled {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
color: transparent;
|
||||
background-image: none;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
entry {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
padding: {{ .Theme.Spacing.PaddingSmall }};
|
||||
caret-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
entry:focus {
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
box-shadow: inset 0 0 0 1px {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
entry:disabled {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
headerbar {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border-bottom: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
box-shadow: none;
|
||||
padding: {{ .Theme.Spacing.PaddingSmall }};
|
||||
}
|
||||
|
||||
headerbar .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
list,
|
||||
menu {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
menubar {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border-bottom: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
padding: {{ .Theme.Spacing.PaddingSmall }};
|
||||
}
|
||||
|
||||
menubar > menuitem {
|
||||
padding: {{ .Theme.Spacing.Padding }};
|
||||
min-height: 24px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
menuitem {
|
||||
min-height: 24px;
|
||||
padding: {{ .Theme.Spacing.Padding }};
|
||||
}
|
||||
|
||||
menubar > menuitem:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-radius: {{ .Theme.Spacing.RadiusSmall }};
|
||||
}
|
||||
|
||||
list row:selected,
|
||||
treeview.view:selected,
|
||||
.view:selected,
|
||||
iconview.view:selected,
|
||||
cell:selected,
|
||||
*:selected {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
menuitem:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-radius: {{ .Theme.Spacing.RadiusSmall }};
|
||||
}
|
||||
|
||||
menuitem:disabled {
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
notebook {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
}
|
||||
|
||||
notebook header {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
}
|
||||
|
||||
notebook tab {
|
||||
padding: {{ .Theme.Spacing.PaddingLarge }};
|
||||
border: 1px solid transparent;
|
||||
border-radius: {{ .Theme.Spacing.Radius }} {{ .Theme.Spacing.Radius }} 0 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
notebook tab:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
}
|
||||
|
||||
notebook tab:checked {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-bottom-color: transparent;
|
||||
border-bottom: 2px solid {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
box-shadow: inset 0 -2px 0 {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
notebook tab label {
|
||||
font-weight: bold;
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
scrollbar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
scrollbar trough {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
}
|
||||
|
||||
scrollbar slider {
|
||||
background-color: {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border: 2px solid transparent;
|
||||
border-radius: {{ .Theme.Spacing.RadiusLarge }};
|
||||
min-width: 8px;
|
||||
min-height: 8px;
|
||||
}
|
||||
|
||||
scrollbar slider:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
scrollbar button {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* Selection */
|
||||
selection,
|
||||
row:selected,
|
||||
iconview:selected,
|
||||
treeview.view:selected,
|
||||
.view:selected,
|
||||
iconview.view:selected,
|
||||
cell:selected,
|
||||
*:selected {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
/* Sliders (Scales) */
|
||||
scale trough {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
min-height: 4px;
|
||||
}
|
||||
|
||||
scale highlight {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
scale slider {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: 50%;
|
||||
min-height: 16px;
|
||||
min-width: 16px;
|
||||
margin: -6px 0;
|
||||
}
|
||||
|
||||
scale slider:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
/* Switches */
|
||||
switch {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
switch:checked {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
switch slider {
|
||||
background-color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: 50%;
|
||||
margin: {{ .Theme.Spacing.MarginSmall }};
|
||||
min-width: 18px;
|
||||
min-height: 18px;
|
||||
}
|
||||
|
||||
scale:disabled trough,
|
||||
scale:disabled highlight,
|
||||
scale:disabled slider,
|
||||
switch:disabled {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
|
||||
switch:disabled slider {
|
||||
background-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package gtk
|
||||
|
||||
import (
|
||||
"Sithego/themer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
type Option func(*Adapter)
|
||||
|
||||
func WithOutputDir(dir string) Option {
|
||||
return func(a *Adapter) {
|
||||
a.OutputDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
func New(opt ...Option) *Adapter {
|
||||
home, _ := os.UserHomeDir()
|
||||
a := &Adapter{
|
||||
OutputDir: home + "/.themes/",
|
||||
}
|
||||
for _, o := range opt {
|
||||
o(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) Name() string {
|
||||
return "GTK"
|
||||
}
|
||||
|
||||
func (a *Adapter) Generate(t themer.Theme) error {
|
||||
components := []string{
|
||||
"template.css",
|
||||
"components/base.css",
|
||||
"components/headerbar.css",
|
||||
"components/buttons.css",
|
||||
"components/entries.css",
|
||||
"components/menus.css",
|
||||
"components/widgets.css",
|
||||
"components/checks.css",
|
||||
"components/scrollbars.css",
|
||||
"components/notebook.css",
|
||||
}
|
||||
|
||||
outputDir := a.OutputDir + t.Meta.Name + "/gtk-3.0"
|
||||
_ = os.RemoveAll(outputDir)
|
||||
outputGTK4 := a.OutputDir + t.Meta.Name + "/gtk-4.0"
|
||||
_ = os.RemoveAll(outputGTK4)
|
||||
|
||||
if err := os.MkdirAll(outputDir, 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, component := range components {
|
||||
templatePath := filepath.Join("themer/adapters/gtk", component)
|
||||
templ, err := template.ParseFiles(templatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputName := filepath.Base(component)
|
||||
if outputName == "template.css" {
|
||||
outputName = "gtk.css"
|
||||
}
|
||||
outputPath := filepath.Join(outputDir, outputName)
|
||||
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = templ.Execute(file, struct {
|
||||
Theme themer.Theme
|
||||
}{
|
||||
Theme: t,
|
||||
})
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(outputGTK4, 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.CopyFS(outputGTK4, os.DirFS(outputDir))
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[Desktop Entry]
|
||||
Type=X-GNOME-Metatheme
|
||||
Name=Serpensortia
|
||||
Comment=Generated Serpent Theme
|
||||
Encoding=UTF-8
|
||||
|
||||
[X-GNOME-Metatheme]
|
||||
GtkTheme=Serpensortia
|
||||
IconTheme=Tela-circle-dark
|
||||
CursorTheme=Vimix-dark
|
||||
ButtonLayout=close,maximize:menu
|
||||
@@ -0,0 +1,9 @@
|
||||
@import url("base.css");
|
||||
@import url("headerbar.css");
|
||||
@import url("buttons.css");
|
||||
@import url("entries.css");
|
||||
@import url("menus.css");
|
||||
@import url("widgets.css");
|
||||
@import url("checks.css");
|
||||
@import url("scrollbars.css");
|
||||
@import url("notebook.css");
|
||||
@@ -0,0 +1,112 @@
|
||||
package intellij
|
||||
|
||||
import (
|
||||
"Sithego/themer"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
type Option func(*Adapter)
|
||||
|
||||
func WithOutputDir(dir string) Option {
|
||||
abs, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return func(a *Adapter) {
|
||||
a.OutputDir = abs
|
||||
}
|
||||
}
|
||||
|
||||
func New(opt ...Option) *Adapter {
|
||||
home, _ := os.UserHomeDir()
|
||||
a := &Adapter{
|
||||
OutputDir: home + "/.themes/",
|
||||
}
|
||||
for _, o := range opt {
|
||||
o(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) Name() string {
|
||||
return "IntelliJ"
|
||||
}
|
||||
|
||||
func (a *Adapter) Generate(t themer.Theme) error {
|
||||
// 1. Create temporary directory structure for JAR bundling
|
||||
tmpDir, err := os.MkdirTemp("", "intellij-theme-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(path string) {
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
}(tmpDir)
|
||||
|
||||
metaInfDir := filepath.Join(tmpDir, "META-INF")
|
||||
if err := os.MkdirAll(metaInfDir, 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"lower": strings.ToLower,
|
||||
}
|
||||
|
||||
// 2. Generate plugin.xml
|
||||
pluginTempl := template.New("template.plugin.xml").Funcs(funcMap)
|
||||
pluginTempl, err = pluginTempl.ParseFiles("themer/adapters/intellij/template.plugin.xml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pluginFile, err := os.Create(filepath.Join(metaInfDir, "plugin.xml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pluginTempl.Execute(pluginFile, struct{ Theme themer.Theme }{Theme: t}); err != nil {
|
||||
_ = pluginFile.Close()
|
||||
return err
|
||||
}
|
||||
_ = pluginFile.Close()
|
||||
|
||||
// 3. Generate .theme.json
|
||||
themeTempl, err := template.ParseFiles("themer/adapters/intellij/template.theme.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
themeName := t.Meta.Name + ".theme.json"
|
||||
themeFile, err := os.Create(filepath.Join(tmpDir, themeName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := themeTempl.Execute(themeFile, struct{ Theme themer.Theme }{Theme: t}); err != nil {
|
||||
_ = themeFile.Close()
|
||||
return err
|
||||
}
|
||||
_ = themeFile.Close()
|
||||
|
||||
// 4. Bundle everything into a JAR (using zip)
|
||||
jarName := t.Meta.Name + ".jar"
|
||||
outputDir := filepath.Join(a.OutputDir, t.Meta.Name, "intellij")
|
||||
if err := os.MkdirAll(outputDir, 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
jarPath := filepath.Join(outputDir, jarName)
|
||||
_ = os.Remove(jarPath) // Remove old JAR if exists
|
||||
cmd := exec.Command("zip", "-r", jarPath, ".")
|
||||
cmd.Dir = tmpDir
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<idea-plugin>
|
||||
<id>de.arindy.{{ .Theme.Meta.Name | lower }}</id>
|
||||
<name>{{ .Theme.Meta.Name }} Theme</name>
|
||||
<version>{{ .Theme.Meta.Version }}</version>
|
||||
<vendor email="support@mail.arindy.de">Arindy</vendor>
|
||||
|
||||
<description><![CDATA[
|
||||
Custom theme generated by Sithego for {{ .Theme.Meta.Name }}.
|
||||
]]></description>
|
||||
|
||||
<depends>com.intellij.modules.platform</depends>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<themeProvider id="de.arindy.{{ .Theme.Meta.Name | lower }}" path="/{{ .Theme.Meta.Name }}.theme.json"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "{{ .Theme.Meta.Name }}",
|
||||
"dark": {{ if eq .Theme.Meta.Type "dark" }}true{{ else }}false{{ end }},
|
||||
"parentTheme": "Islands Dark",
|
||||
"author": "Arindy",
|
||||
"colors": {
|
||||
"ScrollBar.thumb": "{{ .Theme.Colors.Semantic.Border.Value }}",
|
||||
"ScrollBar.track": "{{ .Theme.Colors.Semantic.Background.Value }}",
|
||||
"ScrollBar.hoverThumb": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"ScrollBar.hoverTrack": "{{ .Theme.Colors.Semantic.Surface.Value }}"
|
||||
},
|
||||
"ui": {
|
||||
"*": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Background.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"selectionBackground": "{{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }}",
|
||||
"selectionForeground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"focusColor": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"borderColor": "{{ .Theme.Colors.Semantic.Border.Value }}",
|
||||
"disabledBackground": "{{ .Theme.Colors.Semantic.Background.Value }}",
|
||||
"disabledForeground": "{{ .Theme.Colors.Semantic.Disabled.Value }}"
|
||||
},
|
||||
"Button": {
|
||||
"startBackground": "{{ .Theme.Colors.Semantic.SurfaceAlt.Value }}",
|
||||
"endBackground": "{{ .Theme.Colors.Semantic.SurfaceAlt.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"focusedBorderColor": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"default": {
|
||||
"startBackground": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"endBackground": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Background.Value }}",
|
||||
"focusedBorderColor": "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
}
|
||||
},
|
||||
"ComboBox": {
|
||||
"modifiedItemForeground": "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
},
|
||||
"List": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"selectionBackground": "{{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }}",
|
||||
"selectionForeground": "{{ .Theme.Colors.Semantic.Text.Value }}"
|
||||
},
|
||||
"Menu": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"selectionBackground": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"selectionForeground": "{{ .Theme.Colors.Semantic.Background.Value }}"
|
||||
},
|
||||
"MenuBar": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"selectionBackground": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"selectionForeground": "{{ .Theme.Colors.Semantic.Background.Value }}"
|
||||
},
|
||||
"Panel": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Background.Value }}"
|
||||
},
|
||||
"SidePanel": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}"
|
||||
},
|
||||
"TabbedPane": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"underlineColor": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"hoverColor": "{{ .Theme.Colors.Semantic.SurfaceAlt.Value }}"
|
||||
},
|
||||
"Table": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"gridColor": "{{ .Theme.Colors.Semantic.Border.Value }}"
|
||||
},
|
||||
"TextField": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"foreground": "{{ .Theme.Colors.Semantic.Text.Value }}",
|
||||
"caretColor": "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
},
|
||||
"ToolWindow": {
|
||||
"Header": {
|
||||
"background": "{{ .Theme.Colors.Semantic.Surface.Value }}",
|
||||
"inactiveBackground": "{{ .Theme.Colors.Semantic.Background.Value }}"
|
||||
},
|
||||
"Button": {
|
||||
"selectedBackground": "{{ .Theme.Colors.Semantic.Accent.Value }}",
|
||||
"selectedForeground": "{{ .Theme.Colors.Semantic.Background.Value }}"
|
||||
}
|
||||
},
|
||||
"Tree": {
|
||||
"rowHeight": 24,
|
||||
"selectionBackground": "{{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }}",
|
||||
"selectionForeground": "{{ .Theme.Colors.Semantic.Text.Value }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[ColorScheme]
|
||||
active_colors={{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.SurfaceAlt.Value }}, {{ .Theme.Colors.Semantic.Surface.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Border.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Warn.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }}
|
||||
disabled_colors={{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.SurfaceAlt.Value }}, {{ .Theme.Colors.Semantic.Surface.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Border.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Warn.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}
|
||||
inactive_colors={{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.SurfaceAlt.Value }}, {{ .Theme.Colors.Semantic.Surface.Value }}, {{ .Theme.Colors.Semantic.Disabled.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Border.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Warn.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Text.Value }}, {{ .Theme.Colors.Semantic.Accent.Value }}, {{ .Theme.Colors.Semantic.Background.Value }}, {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }}
|
||||
@@ -0,0 +1,105 @@
|
||||
package qt
|
||||
|
||||
import (
|
||||
"Sithego/themer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
type Option func(*Adapter)
|
||||
|
||||
func WithOutputDir(dir string) Option {
|
||||
return func(a *Adapter) {
|
||||
a.OutputDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
func New(opt ...Option) *Adapter {
|
||||
home, _ := os.UserHomeDir()
|
||||
a := &Adapter{
|
||||
OutputDir: home + "/.config/",
|
||||
}
|
||||
for _, o := range opt {
|
||||
o(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) Name() string {
|
||||
return "Qt"
|
||||
}
|
||||
|
||||
func (a *Adapter) Generate(t themer.Theme) error {
|
||||
qssComponents := []string{
|
||||
"template.qss",
|
||||
}
|
||||
outputDir := filepath.Join(a.OutputDir, "qt6ct")
|
||||
if err := os.MkdirAll(outputDir, 0775); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, component := range qssComponents {
|
||||
templatePath := filepath.Join("themer/adapters/qt", component)
|
||||
templ, err := template.ParseFiles(templatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qssOutputDir := filepath.Join(outputDir, "qss")
|
||||
err = os.MkdirAll(qssOutputDir, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputPath := filepath.Join(qssOutputDir, t.Meta.Name+".qss")
|
||||
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = templ.Execute(file, struct {
|
||||
Theme themer.Theme
|
||||
}{
|
||||
Theme: t,
|
||||
})
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Generate qt5ct/qt6ct color scheme (.conf)
|
||||
confTemplatePath := "themer/adapters/qt/colors.conf"
|
||||
if _, err := os.Stat(confTemplatePath); err == nil {
|
||||
templ, err := template.ParseFiles(confTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confOutputDir := filepath.Join(outputDir, "colors")
|
||||
err = os.MkdirAll(confOutputDir, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputPath := filepath.Join(confOutputDir, t.Meta.Name+".conf")
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = templ.Execute(file, struct {
|
||||
Theme themer.Theme
|
||||
}{
|
||||
Theme: t,
|
||||
})
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/* Global Styles */
|
||||
QWidget {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
selection-background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
selection-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
QPushButton {
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
padding: {{ .Theme.Spacing.Padding }};
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
background-color: {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
}
|
||||
|
||||
QPushButton:pressed {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
}
|
||||
|
||||
QPushButton:disabled {
|
||||
background-color: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
border-color: {{ .Theme.Colors.Semantic.Disabled.Value }};
|
||||
}
|
||||
|
||||
/* Input Fields */
|
||||
QLineEdit, QTextEdit, QPlainTextEdit {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
padding: {{ .Theme.Spacing.PaddingSmall }};
|
||||
}
|
||||
|
||||
QLineEdit:focus {
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
/* Menus and Lists */
|
||||
QListView, QTreeView, QTableView {
|
||||
background-color: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
alternate-background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
}
|
||||
|
||||
QListView::item:selected, QTreeView::item:selected, QTableView::item:selected {
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Alpha .Theme.Colors.Semantic.Background 0.3 }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
color: {{ .Theme.Colors.Semantic.Text.Value }};
|
||||
}
|
||||
|
||||
/* Scrollbars */
|
||||
QScrollBar:vertical {
|
||||
background: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
width: 12px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
background: {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
min-height: 20px;
|
||||
border-radius: {{ .Theme.Spacing.RadiusSmall }};
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
QTabBar::tab {
|
||||
background: {{ .Theme.Colors.Semantic.Surface.Value }};
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
padding: {{ .Theme.Spacing.Padding }};
|
||||
border-top-left-radius: {{ .Theme.Spacing.Radius }};
|
||||
border-top-right-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
QTabBar::tab:selected {
|
||||
background: {{ .Theme.Colors.Semantic.Background.Value }};
|
||||
border-bottom-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
/* Checkboxes and Radio Buttons */
|
||||
QCheckBox::indicator, QRadioButton::indicator {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid {{ .Theme.Colors.Semantic.Border.Value }};
|
||||
background-color: {{ .Theme.Colors.Semantic.SurfaceAlt.Value }};
|
||||
border-radius: {{ .Theme.Spacing.Radius }};
|
||||
}
|
||||
|
||||
QRadioButton::indicator {
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked {
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
|
||||
QRadioButton::indicator:checked {
|
||||
border-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
background-color: {{ .Theme.Colors.Semantic.Accent.Value }};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package starship
|
||||
|
||||
import (
|
||||
"Sithego/themer"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
type Option func(*Adapter)
|
||||
|
||||
func WithOutputDir(dir string) Option {
|
||||
return func(a *Adapter) {
|
||||
a.OutputDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
func New(opt ...Option) *Adapter {
|
||||
home, _ := os.UserHomeDir()
|
||||
a := &Adapter{
|
||||
OutputDir: home + "/.config/",
|
||||
}
|
||||
for _, o := range opt {
|
||||
o(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) Name() string {
|
||||
return "Starship"
|
||||
}
|
||||
|
||||
func (a *Adapter) Generate(t themer.Theme) error {
|
||||
templatePath := filepath.Join("themer/adapters/starship", "starship.toml")
|
||||
templ, err := template.ParseFiles(templatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputPath := filepath.Join(a.OutputDir, "starship.toml")
|
||||
|
||||
file, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = templ.Execute(file, struct {
|
||||
Theme themer.Theme
|
||||
}{
|
||||
Theme: t,
|
||||
})
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
"$schema" = 'https://starship.rs/config-schema.json'
|
||||
|
||||
format = """
|
||||
[](color_bg1)\
|
||||
[](bg:color_bg1 fg:color_fg0)\
|
||||
$username\
|
||||
[](fg:color_bg1)\
|
||||
$directory\
|
||||
[](bg:color_bg1 fg:color_black)\
|
||||
$git_branch\
|
||||
$git_status\
|
||||
[](fg:color_bg1 bg:color_blue)\
|
||||
$c\
|
||||
$cpp\
|
||||
$rust\
|
||||
$golang\
|
||||
$nodejs\
|
||||
$php\
|
||||
$java\
|
||||
$kotlin\
|
||||
$haskell\
|
||||
$python\
|
||||
[](fg:color_blue bg:color_bg3)\
|
||||
$docker_context\
|
||||
$conda\
|
||||
$pixi\
|
||||
[](fg:color_bg3 bg:color_bg1)\
|
||||
$time\
|
||||
[ ](fg:color_bg1)\
|
||||
$cmd_duration\
|
||||
$line_break$character"""
|
||||
|
||||
palette = 'serpensortia'
|
||||
|
||||
[palettes.serpensortia]
|
||||
color_black = "#000000"
|
||||
color_fg0 = "{{ .Theme.Colors.Semantic.Text.Value }}"
|
||||
color_bg1 = "{{ .Theme.Colors.Semantic.Surface.Value }}"
|
||||
color_bg3 = "{{ .Theme.Colors.Semantic.SurfaceAlt.Value }}"
|
||||
color_blue = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_aqua = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_accent = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_orange = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_purple = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_warn = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
color_yellow = "{{ .Theme.Colors.Semantic.Accent.Value }}"
|
||||
|
||||
[username]
|
||||
show_always = true
|
||||
style_user = "bg:color_bg1 fg:color_accent"
|
||||
style_root = "bg:color_warn fg:color_bg1"
|
||||
format = '[ $user ]($style)'
|
||||
|
||||
[directory]
|
||||
style = "fg:color_bg0 bg:color_text"
|
||||
format = "[ $path ]($style)[$read_only]($read_only_style) "
|
||||
read_only_style = "{{ .Theme.Colors.Semantic.Warn.Value }}"
|
||||
read_only = ""
|
||||
truncation_length = 0
|
||||
truncation_symbol = "…/"
|
||||
|
||||
[directory.substitutions]
|
||||
"Documents" = " "
|
||||
"Downloads" = " "
|
||||
"Music" = " "
|
||||
"Pictures" = " "
|
||||
"workspace" = " "
|
||||
|
||||
[git_branch]
|
||||
symbol = ""
|
||||
style = "bg:color_bg1"
|
||||
format = '[[ $symbol $branch ](fg:color_accent bg:color_bg1)]($style)'
|
||||
|
||||
[git_status]
|
||||
style = "bg:color_bg1"
|
||||
format = '[[($all_status$ahead_behind )](fg:color_accent bg:color_bg1)]($style)'
|
||||
|
||||
[nodejs]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[c]
|
||||
symbol = " "
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[cpp]
|
||||
symbol = " "
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[rust]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[golang]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[php]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[java]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[kotlin]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[haskell]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[python]
|
||||
symbol = ""
|
||||
style = "bg:color_blue"
|
||||
format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)'
|
||||
|
||||
[docker_context]
|
||||
symbol = ""
|
||||
style = "bg:color_bg3"
|
||||
format = '[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)'
|
||||
|
||||
[conda]
|
||||
style = "bg:color_bg3"
|
||||
format = '[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)'
|
||||
|
||||
[pixi]
|
||||
style = "bg:color_bg3"
|
||||
format = '[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)'
|
||||
|
||||
[time]
|
||||
disabled = false
|
||||
time_format = "%R"
|
||||
style = "bg:color_bg1"
|
||||
format = '[[ $time ](fg:color_fg0 bg:color_bg1)]($style)'
|
||||
|
||||
[line_break]
|
||||
disabled = false
|
||||
|
||||
[character]
|
||||
disabled = false
|
||||
success_symbol = '[❯](bold green)'
|
||||
error_symbol = '[✖](bold red)'
|
||||
|
||||
[cmd_duration]
|
||||
disabled = false
|
||||
min_time = 50
|
||||
show_milliseconds = true
|
||||
format = '[$duration](bold)'
|
||||
@@ -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