feat(themes): add color derivation and themed app templates
Add script/omarchy-theme-color and expand omarchy-theme-set-templates to derive a full palette from each theme's colors.toml, then render new themed templates (shell, pi, vscode theme, gum, neovim, claude, hermes, t3code) from it. Adds backgrounds and palette updates for existing themes plus new themes (last-horizon, lupine, solitude, tidewater-*, miasma, ...).
This commit is contained in:
@@ -7,7 +7,8 @@ TEMPLATES_DIR="$DOTFILES_DIR/default/themed"
|
||||
USER_TEMPLATES_DIR="$HOME/.config/omarchy/themed"
|
||||
NEXT_THEME_DIR="$HOME/.local/state/omarchy/current/next-theme"
|
||||
COLORS_FILE="$NEXT_THEME_DIR/colors.toml"
|
||||
THEME_MODE="$NEXT_THEME_DIR/light.mode"
|
||||
|
||||
declare -A THEME_COLORS
|
||||
|
||||
# Convert hex color to decimal RGB (e.g., "#1e1e2e" -> "30,30,46")
|
||||
hex_to_rgb() {
|
||||
@@ -15,35 +16,379 @@ hex_to_rgb() {
|
||||
printf "%d,%d,%d" "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
||||
}
|
||||
|
||||
# Mix two hex colors. Amount may be a fraction (0.30) or percentage (30%).
|
||||
mix_color() {
|
||||
local start="${1#\#}"
|
||||
local end="${2#\#}"
|
||||
local amount="$3"
|
||||
|
||||
awk -v start="$start" -v end="$end" -v amount="$amount" '
|
||||
function hex_value(char) {
|
||||
return index("0123456789abcdef", tolower(char)) - 1
|
||||
}
|
||||
|
||||
function hex_pair_to_int(hex, idx) {
|
||||
return hex_value(substr(hex, idx, 1)) * 16 + hex_value(substr(hex, idx + 1, 1))
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
if (amount ~ /%$/) {
|
||||
sub(/%$/, "", amount)
|
||||
amount = amount / 100
|
||||
} else {
|
||||
amount += 0
|
||||
if (amount > 1) amount = amount / 100
|
||||
}
|
||||
|
||||
if (amount < 0) amount = 0
|
||||
if (amount > 1) amount = 1
|
||||
|
||||
start_r = hex_pair_to_int(start, 1)
|
||||
start_g = hex_pair_to_int(start, 3)
|
||||
start_b = hex_pair_to_int(start, 5)
|
||||
end_r = hex_pair_to_int(end, 1)
|
||||
end_g = hex_pair_to_int(end, 3)
|
||||
end_b = hex_pair_to_int(end, 5)
|
||||
|
||||
red = int(start_r * (1 - amount) + end_r * amount + 0.5)
|
||||
green = int(start_g * (1 - amount) + end_g * amount + 0.5)
|
||||
blue = int(start_b * (1 - amount) + end_b * amount + 0.5)
|
||||
|
||||
printf "#%02x%02x%02x\n", red, green, blue
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
trim() {
|
||||
local value="$1"
|
||||
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
printf "%s" "$value"
|
||||
}
|
||||
|
||||
resolve_theme_ref() {
|
||||
local ref="$1"
|
||||
local fallback="${2:-}"
|
||||
|
||||
if [[ -n ${THEME_COLORS[$ref]+_} ]]; then
|
||||
printf "%s" "${THEME_COLORS[$ref]}"
|
||||
elif [[ -n $fallback && -n ${THEME_COLORS[$fallback]+_} ]]; then
|
||||
printf "%s" "${THEME_COLORS[$fallback]}"
|
||||
elif [[ -n $fallback ]]; then
|
||||
printf "%s" "$fallback"
|
||||
else
|
||||
printf "%s" "$ref"
|
||||
fi
|
||||
}
|
||||
|
||||
resolve_gradient_color() {
|
||||
local color
|
||||
|
||||
color=$(trim "$1")
|
||||
if [[ -n ${THEME_COLORS[$color]+_} ]]; then
|
||||
color="${THEME_COLORS[$color]}"
|
||||
fi
|
||||
|
||||
printf "%s" "$color"
|
||||
}
|
||||
|
||||
parse_gradient() {
|
||||
local spec="$1"
|
||||
local part color
|
||||
local -a parts
|
||||
|
||||
GRADIENT_COLORS=()
|
||||
GRADIENT_ANGLE=""
|
||||
|
||||
read -ra parts <<<"$spec"
|
||||
for part in "${parts[@]}"; do
|
||||
[[ -n $part ]] || continue
|
||||
|
||||
if [[ $part =~ ^-?[0-9]+([.][0-9]+)?deg$ ]]; then
|
||||
GRADIENT_ANGLE="${part%deg}"
|
||||
else
|
||||
color=$(resolve_gradient_color "$part")
|
||||
GRADIENT_COLORS+=("$color")
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
color_to_shell_hex() {
|
||||
local color r g b
|
||||
|
||||
color=$(resolve_gradient_color "$1")
|
||||
|
||||
if [[ $color =~ ^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$ ]]; then
|
||||
printf "#%s" "${color:1:6}"
|
||||
elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?\)$ ]]; then
|
||||
printf "#%s" "${BASH_REMATCH[1]}"
|
||||
elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9]+),([0-9]+),([0-9]+)(,[0-9.]+)?\)$ ]]; then
|
||||
r=${BASH_REMATCH[1]}
|
||||
g=${BASH_REMATCH[2]}
|
||||
b=${BASH_REMATCH[3]}
|
||||
(( r > 255 )) && r=255
|
||||
(( g > 255 )) && g=255
|
||||
(( b > 255 )) && b=255
|
||||
printf "#%02x%02x%02x" "$r" "$g" "$b"
|
||||
elif [[ $color =~ ^0x[0-9A-Fa-f]{8}$ ]]; then
|
||||
printf "#%s" "${color:4:6}"
|
||||
else
|
||||
printf "%s" "$color"
|
||||
fi
|
||||
}
|
||||
|
||||
hypr_gradient_value() {
|
||||
local spec color index
|
||||
|
||||
spec=$(resolve_theme_ref "$1" "${2:-}")
|
||||
parse_gradient "$spec"
|
||||
|
||||
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
||||
printf '"%s"' "$spec"
|
||||
elif (( ${#GRADIENT_COLORS[@]} == 1 )); then
|
||||
printf '"%s"' "${GRADIENT_COLORS[0]}"
|
||||
else
|
||||
printf '{ colors = {'
|
||||
for index in "${!GRADIENT_COLORS[@]}"; do
|
||||
(( index > 0 )) && printf ','
|
||||
printf ' "%s"' "${GRADIENT_COLORS[$index]}"
|
||||
done
|
||||
printf ' }'
|
||||
[[ -n $GRADIENT_ANGLE ]] && printf ', angle = %s' "$GRADIENT_ANGLE"
|
||||
printf ' }'
|
||||
fi
|
||||
}
|
||||
|
||||
gradient_start_value() {
|
||||
local spec
|
||||
|
||||
spec=$(resolve_theme_ref "$1" "${2:-}")
|
||||
parse_gradient "$spec"
|
||||
|
||||
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
||||
color_to_shell_hex "$spec"
|
||||
else
|
||||
color_to_shell_hex "${GRADIENT_COLORS[0]}"
|
||||
fi
|
||||
}
|
||||
|
||||
shell_gradient_value() {
|
||||
local spec index
|
||||
|
||||
spec=$(resolve_theme_ref "$1" "${2:-}")
|
||||
parse_gradient "$spec"
|
||||
|
||||
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
||||
printf "%s" "$spec"
|
||||
return
|
||||
fi
|
||||
|
||||
for index in "${!GRADIENT_COLORS[@]}"; do
|
||||
(( index > 0 )) && printf " "
|
||||
printf "%s" "${GRADIENT_COLORS[$index]}"
|
||||
done
|
||||
|
||||
[[ -n $GRADIENT_ANGLE ]] && printf " %sdeg" "$GRADIENT_ANGLE"
|
||||
}
|
||||
|
||||
add_template_value() {
|
||||
local key="$1"
|
||||
local value="$2"
|
||||
local rgb
|
||||
|
||||
printf 's|{{ %s }}|%s|g\n' "$key" "$value" >>"$sed_script"
|
||||
printf 's|{{ %s_strip }}|%s|g\n' "$key" "${value#\#}" >>"$sed_script"
|
||||
|
||||
if [[ $value =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
||||
rgb=$(hex_to_rgb "$value")
|
||||
printf 's|{{ %s_rgb }}|%s|g\n' "$key" "$rgb" >>"$sed_script"
|
||||
fi
|
||||
}
|
||||
|
||||
add_mix_value() {
|
||||
local token="$1"
|
||||
local content fn start_key end_key amount start end value
|
||||
|
||||
content="${token#\{\{}"
|
||||
content="${content%\}\}}"
|
||||
read -r fn start_key end_key amount <<<"$content"
|
||||
|
||||
start="${THEME_COLORS[$start_key]:-}"
|
||||
end="${THEME_COLORS[$end_key]:-}"
|
||||
|
||||
[[ $start =~ ^#[0-9A-Fa-f]{6}$ && $end =~ ^#[0-9A-Fa-f]{6}$ ]] || return
|
||||
|
||||
value=$(mix_color "$start" "$end" "$amount")
|
||||
|
||||
case "$fn" in
|
||||
mix)
|
||||
;;
|
||||
mix_strip)
|
||||
value="${value#\#}"
|
||||
;;
|
||||
mix_rgb)
|
||||
value=$(hex_to_rgb "$value")
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
printf 's|%s|%s|g\n' "$token" "$value" >>"$sed_script"
|
||||
}
|
||||
|
||||
add_mix_values() {
|
||||
local tpl token
|
||||
local -A seen=()
|
||||
|
||||
for tpl in "${template_files[@]}"; do
|
||||
while IFS= read -r token; do
|
||||
[[ -n ${seen[$token]:-} ]] && continue
|
||||
seen[$token]=1
|
||||
add_mix_value "$token"
|
||||
done < <(grep -hEo '\{\{[[:space:]]*mix(_strip|_rgb)?[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[0-9]+([.][0-9]+)?%?[[:space:]]*\}\}' "$tpl" 2>/dev/null || true)
|
||||
done
|
||||
}
|
||||
|
||||
add_gradient_function_value() {
|
||||
local token="$1"
|
||||
local content fn key fallback value
|
||||
|
||||
content="${token#\{\{}"
|
||||
content="${content%\}\}}"
|
||||
read -r fn key fallback <<<"$content"
|
||||
|
||||
case "$fn" in
|
||||
hypr_gradient)
|
||||
value=$(hypr_gradient_value "$key" "$fallback")
|
||||
;;
|
||||
gradient_start)
|
||||
value=$(gradient_start_value "$key" "$fallback")
|
||||
;;
|
||||
shell_gradient)
|
||||
value=$(shell_gradient_value "$key" "$fallback")
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
printf 's|%s|%s|g\n' "$token" "$value" >>"$sed_script"
|
||||
}
|
||||
|
||||
add_gradient_function_values() {
|
||||
local tpl token
|
||||
local -A seen=()
|
||||
|
||||
for tpl in "${template_files[@]}"; do
|
||||
while IFS= read -r token; do
|
||||
[[ -n ${seen[$token]:-} ]] && continue
|
||||
seen[$token]=1
|
||||
add_gradient_function_value "$token"
|
||||
done < <(grep -hEo '\{\{[[:space:]]*(hypr_gradient|gradient_start|shell_gradient)[[:space:]]+[^}]+[[:space:]]*\}\}' "$tpl" 2>/dev/null || true)
|
||||
done
|
||||
}
|
||||
|
||||
strip_shell_section_header() {
|
||||
local section="$1"
|
||||
local file="$2"
|
||||
|
||||
awk -v section="$section" '
|
||||
BEGIN { skipping_header = 0 }
|
||||
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
||||
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
||||
skipping_header = 1
|
||||
next
|
||||
}
|
||||
}
|
||||
{ print }
|
||||
' "$file"
|
||||
}
|
||||
|
||||
apply_shell_section_override() {
|
||||
local override="$1"
|
||||
local section tmp body
|
||||
|
||||
[[ -f $NEXT_THEME_DIR/shell.toml ]] || return
|
||||
[[ -f $override ]] || return
|
||||
|
||||
section=$(basename "$override")
|
||||
section="${section#shell.}"
|
||||
section="${section%.toml}"
|
||||
[[ $section =~ ^[A-Za-z0-9_-]+$ ]] || return
|
||||
|
||||
tmp=$(mktemp)
|
||||
body=$(mktemp)
|
||||
|
||||
strip_shell_section_header "$section" "$override" >"$body"
|
||||
|
||||
awk -v section="$section" -v body="$body" '
|
||||
function emit_override() {
|
||||
if (emitted) return
|
||||
print "[" section "]"
|
||||
while ((getline line < body) > 0) print line
|
||||
close(body)
|
||||
emitted = 1
|
||||
}
|
||||
|
||||
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
||||
if (in_section) {
|
||||
emit_override()
|
||||
in_section = 0
|
||||
}
|
||||
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
||||
in_section = 1
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
!in_section { print }
|
||||
|
||||
END {
|
||||
if (in_section || !emitted) {
|
||||
if (NR > 0) print ""
|
||||
emit_override()
|
||||
}
|
||||
}
|
||||
' "$NEXT_THEME_DIR/shell.toml" >"$tmp"
|
||||
|
||||
mv "$tmp" "$NEXT_THEME_DIR/shell.toml"
|
||||
rm "$body"
|
||||
}
|
||||
|
||||
apply_shell_section_overrides() {
|
||||
local override
|
||||
|
||||
shopt -s nullglob
|
||||
for override in "$NEXT_THEME_DIR"/shell.*.toml; do
|
||||
[[ $(basename "$override") != "shell.toml" ]] || continue
|
||||
apply_shell_section_override "$override"
|
||||
done
|
||||
}
|
||||
|
||||
# Only generate dynamic templates for themes with a colors.toml definition
|
||||
if [[ -f $COLORS_FILE ]]; then
|
||||
sed_script=$(mktemp)
|
||||
|
||||
key="theme_mode";
|
||||
if [[ -f $THEME_MODE ]]; then
|
||||
printf 's|{{ %s }}|%s|g\n' "$key" "light" # {{ key }} -> value
|
||||
else
|
||||
printf 's|{{ %s }}|%s|g\n' "$key" "dark" # {{ key }} -> value
|
||||
fi >"$sed_script"
|
||||
|
||||
while IFS='=' read -r key value; do
|
||||
key="${key//[\"\' ]/}" # strip quotes and spaces from key
|
||||
[[ $key && $key != \#* ]] || continue # skip empty lines and comments
|
||||
value="${value#*[\"\']}"
|
||||
value="${value%%[\"\']*}" # extract value between quotes (ignores inline comments)
|
||||
|
||||
printf 's|{{ %s }}|%s|g\n' "$key" "$value" # {{ key }} -> value
|
||||
printf 's|{{ %s_strip }}|%s|g\n' "$key" "${value#\#}" # {{ key_strip }} -> value without leading #
|
||||
if [[ $value =~ ^# ]]; then
|
||||
rgb=$(hex_to_rgb "$value")
|
||||
echo "s|{{ ${key}_rgb }}|${rgb}|g"
|
||||
fi
|
||||
done <"$COLORS_FILE" >>"$sed_script"
|
||||
|
||||
shopt -s nullglob
|
||||
template_files=("$USER_TEMPLATES_DIR"/*.tpl "$TEMPLATES_DIR"/*.tpl)
|
||||
|
||||
# Parsing and alias/fallback resolution (legacy colorN names, derived
|
||||
# shades, mode/theme_type) is shared with every other colors.toml consumer.
|
||||
while IFS=$'\t' read -r key value; do
|
||||
THEME_COLORS[$key]="$value"
|
||||
done < <(omarchy-theme-color --file "$COLORS_FILE" --all)
|
||||
|
||||
for key in "${!THEME_COLORS[@]}"; do
|
||||
add_template_value "$key" "${THEME_COLORS[$key]}"
|
||||
done
|
||||
|
||||
add_mix_values
|
||||
add_gradient_function_values
|
||||
|
||||
# Process user templates first, then built-in templates (user overrides built-in)
|
||||
for tpl in "$USER_TEMPLATES_DIR"/*.tpl "$TEMPLATES_DIR"/*.tpl; do
|
||||
for tpl in "${template_files[@]}"; do
|
||||
filename=$(basename "$tpl" .tpl)
|
||||
output_path="$NEXT_THEME_DIR/$filename"
|
||||
|
||||
@@ -55,3 +400,5 @@ if [[ -f $COLORS_FILE ]]; then
|
||||
|
||||
rm "$sed_script"
|
||||
fi
|
||||
|
||||
apply_shell_section_overrides
|
||||
|
||||
Reference in New Issue
Block a user