mirror of
https://github.com/GradienceTeam/Gradience.git
synced 2024-09-18 18:02:32 +00:00
daudix-UFO
4bc2cb408f
Use less saturated colors more similar to original M3, make all objects distinguishable and fix various issues such as black card and headerbar outlines
352 lines
15 KiB
Python
352 lines
15 KiB
Python
# preset_utils.py
|
|
#
|
|
# Change the look of Adwaita, with ease
|
|
# Copyright (C) 2022 Gradience Team
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import json
|
|
|
|
from pathlib import Path
|
|
|
|
from gi.repository import GLib, Gio
|
|
|
|
from gradience.backend.models.preset import Preset
|
|
from gradience.backend.utils.colors import argb_to_color_code
|
|
|
|
from gradience.backend.globals import presets_dir, get_gtk_theme_dir
|
|
|
|
from gradience.backend.logger import Logger
|
|
|
|
logging = Logger()
|
|
|
|
|
|
class PresetUtils:
|
|
def __init__(self):
|
|
self.preset = Preset()
|
|
|
|
def generate_gtk_css(self, app_type: str, preset: Preset) -> str:
|
|
variables = preset.variables
|
|
palette = preset.palette
|
|
custom_css = preset.custom_css
|
|
|
|
final_css = ""
|
|
|
|
for key in variables.keys():
|
|
final_css += f"@define-color {key} {variables[key]};\n"
|
|
|
|
for prefix_key in palette.keys():
|
|
for key in palette[prefix_key].keys():
|
|
final_css += f"@define-color {prefix_key + key} {palette[prefix_key][key]};\n"
|
|
|
|
final_css += custom_css.get(app_type, "")
|
|
|
|
return final_css
|
|
|
|
def new_preset_from_monet(self, name=None, monet_palette=None, props=None, obj_only=False) -> Preset or None:
|
|
if props:
|
|
tone = props[0]
|
|
theme = props[1]
|
|
else:
|
|
raise AttributeError("Properties 'tone' and/or 'theme' missing")
|
|
|
|
if not monet_palette:
|
|
raise AttributeError("Property 'monet_palette' missing")
|
|
|
|
if theme == "light":
|
|
light_theme = monet_palette["schemes"]["light"]
|
|
variable = {
|
|
"accent_color": argb_to_color_code(light_theme.primary),
|
|
"accent_bg_color": argb_to_color_code(light_theme.primary),
|
|
"accent_fg_color": argb_to_color_code(light_theme.onPrimary),
|
|
"destructive_color": argb_to_color_code(light_theme.error),
|
|
"destructive_bg_color": argb_to_color_code(light_theme.errorContainer),
|
|
"destructive_fg_color": argb_to_color_code(light_theme.onErrorContainer),
|
|
"success_color": argb_to_color_code(light_theme.tertiary),
|
|
"success_bg_color": argb_to_color_code(light_theme.tertiaryContainer),
|
|
"success_fg_color": argb_to_color_code(light_theme.onTertiaryContainer),
|
|
"warning_color": argb_to_color_code(light_theme.secondary),
|
|
"warning_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
|
"warning_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
|
"error_color": argb_to_color_code(light_theme.error),
|
|
"error_bg_color": argb_to_color_code(light_theme.errorContainer),
|
|
"error_fg_color": argb_to_color_code(light_theme.onError),
|
|
"window_bg_color": argb_to_color_code(light_theme.surface),
|
|
"window_fg_color": argb_to_color_code(light_theme.onSurface),
|
|
"view_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
|
"view_fg_color": argb_to_color_code(light_theme.onSurface),
|
|
"headerbar_bg_color": argb_to_color_code(light_theme.primary, "0.08"),
|
|
"headerbar_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
|
"headerbar_border_color": argb_to_color_code(light_theme.onSurface, "0.8"),
|
|
"headerbar_backdrop_color": "@window_bg_color",
|
|
"headerbar_shade_color": argb_to_color_code(light_theme.onSurface, "0.07"),
|
|
"card_bg_color": argb_to_color_code(light_theme.primary, "0.05"),
|
|
"card_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
|
"card_shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
|
"dialog_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
|
"dialog_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
|
"popover_bg_color": argb_to_color_code(light_theme.secondaryContainer),
|
|
"popover_fg_color": argb_to_color_code(light_theme.onSecondaryContainer),
|
|
"shade_color": argb_to_color_code(light_theme.shadow, "0.07"),
|
|
"scrollbar_outline_color": argb_to_color_code(light_theme.outline),
|
|
}
|
|
elif theme == "dark":
|
|
dark_theme = monet_palette["schemes"]["dark"]
|
|
variable = {
|
|
"accent_color": argb_to_color_code(dark_theme.primary),
|
|
"accent_bg_color": argb_to_color_code(dark_theme.primary),
|
|
"accent_fg_color": argb_to_color_code(dark_theme.onPrimary),
|
|
"destructive_color": argb_to_color_code(dark_theme.error),
|
|
"destructive_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
|
"destructive_fg_color": argb_to_color_code(dark_theme.onErrorContainer),
|
|
"success_color": argb_to_color_code(dark_theme.tertiary),
|
|
"success_bg_color": argb_to_color_code(dark_theme.tertiaryContainer),
|
|
"success_fg_color": argb_to_color_code(dark_theme.onTertiaryContainer),
|
|
"warning_color": argb_to_color_code(dark_theme.secondary),
|
|
"warning_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
|
"warning_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
|
"error_color": argb_to_color_code(dark_theme.error),
|
|
"error_bg_color": argb_to_color_code(dark_theme.errorContainer),
|
|
"error_fg_color": argb_to_color_code(dark_theme.onError),
|
|
"window_bg_color": argb_to_color_code(dark_theme.surface),
|
|
"window_fg_color": argb_to_color_code(dark_theme.onSurface),
|
|
"view_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
|
"view_fg_color": argb_to_color_code(dark_theme.onSurface),
|
|
"headerbar_bg_color": argb_to_color_code(dark_theme.primary, "0.08"),
|
|
"headerbar_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
|
"headerbar_border_color": argb_to_color_code(dark_theme.onSurface, "0.8"),
|
|
"headerbar_backdrop_color": "@window_bg_color",
|
|
"headerbar_shade_color": argb_to_color_code(dark_theme.onSurface, "0.07"),
|
|
"card_bg_color": argb_to_color_code(dark_theme.primary, "0.05"),
|
|
"card_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
|
"card_shade_color": argb_to_color_code(dark_theme.shadow, "0.07"),
|
|
"dialog_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
|
"dialog_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
|
"popover_bg_color": argb_to_color_code(dark_theme.secondaryContainer),
|
|
"popover_fg_color": argb_to_color_code(dark_theme.onSecondaryContainer),
|
|
"shade_color": argb_to_color_code(dark_theme.shadow, "0.36"),
|
|
"scrollbar_outline_color": argb_to_color_code(dark_theme.outline, "0.5"),
|
|
}
|
|
|
|
if obj_only == False and not name:
|
|
raise AttributeError("You either need to set 'obj_only' property to True, or add value to 'name' property")
|
|
|
|
if obj_only:
|
|
if name:
|
|
logging.debug("with name, obj_only")
|
|
self.preset.new(variables=variable, display_name=name)
|
|
else:
|
|
logging.debug("no name, obj_only")
|
|
self.preset.new(variables=variable)
|
|
return self.preset
|
|
|
|
if obj_only == False:
|
|
logging.debug("no obj_only, name")
|
|
self.preset.new(variables=variable, display_name=name)
|
|
|
|
try:
|
|
self.preset.save_to_file()
|
|
except OSError:
|
|
raise
|
|
|
|
def get_presets_list(self, repo=None, full_list=False) -> dict:
|
|
presets_list = {}
|
|
|
|
def get_repo_presets(repo):
|
|
if repo.is_dir():
|
|
for file_name in repo.iterdir():
|
|
file_name = str(file_name)
|
|
if file_name.endswith(".json"):
|
|
try:
|
|
with open(
|
|
os.path.join(presets_dir, file_name),
|
|
"r",
|
|
encoding="utf-8",
|
|
) as file:
|
|
preset_text = file.read()
|
|
file.close()
|
|
except (OSError, KeyError) as e:
|
|
logging.error("Failed to load preset information.", exc=e)
|
|
raise
|
|
else:
|
|
preset = json.loads(preset_text)
|
|
if preset.get("variables") is None:
|
|
raise KeyError("'variables' section missing in loaded preset file")
|
|
if preset.get("palette") is None:
|
|
raise KeyError("'palette' section missing in loaded preset file")
|
|
presets_list[file_name] = preset[
|
|
"name"
|
|
]
|
|
elif repo.is_file():
|
|
# this exists to keep compatibility with old presets
|
|
if repo.name.endswith(".json"):
|
|
logging.warning("Legacy preset found. Moving to new structure.")
|
|
|
|
try:
|
|
if not os.path.isdir(os.path.join(presets_dir, "user")):
|
|
os.mkdir(os.path.join(presets_dir, "user"))
|
|
|
|
os.rename(repo, os.path.join(
|
|
presets_dir, "user", repo.name))
|
|
|
|
with open(
|
|
os.path.join(presets_dir, "user", repo),
|
|
"r",
|
|
encoding="utf-8",
|
|
) as file:
|
|
preset_text = file.read()
|
|
file.close()
|
|
except (OSError, KeyError) as e:
|
|
logging.error("Failed to load preset information.", exc=e)
|
|
raise
|
|
else:
|
|
preset = json.loads(preset_text)
|
|
if preset.get("variables") is None:
|
|
raise KeyError("'variables' section missing in loaded preset file")
|
|
if preset.get("palette") is None:
|
|
raise KeyError("'palette' section missing in loaded preset file")
|
|
presets_list["user"][file_name] = preset[
|
|
"name"
|
|
]
|
|
|
|
if full_list:
|
|
for repo in Path(presets_dir).iterdir():
|
|
logging.debug(f"presets_dir.iterdir: {repo}")
|
|
get_repo_presets(repo)
|
|
return presets_list
|
|
elif repo:
|
|
get_repo_presets(repo)
|
|
return presets_list
|
|
else:
|
|
raise AttributeError("You either need to set 'repo' property, or change 'full_list' property to True")
|
|
|
|
def apply_preset(self, app_type: str, preset: Preset) -> None:
|
|
if app_type == "gtk4":
|
|
theme_dir = get_gtk_theme_dir(app_type)
|
|
|
|
if not os.path.exists(theme_dir):
|
|
os.makedirs(theme_dir)
|
|
|
|
gtk4_css = self.generate_gtk_css("gtk4", preset)
|
|
contents = ""
|
|
|
|
try:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css"), "r", encoding="utf-8"
|
|
) as file:
|
|
contents = file.read()
|
|
except FileNotFoundError: # first run
|
|
pass
|
|
else:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css.bak"), "w", encoding="utf-8"
|
|
) as file:
|
|
file.write(contents)
|
|
finally:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css"), "w", encoding="utf-8"
|
|
) as file:
|
|
file.write(gtk4_css)
|
|
elif app_type == "gtk3":
|
|
theme_dir = get_gtk_theme_dir(app_type)
|
|
|
|
if not os.path.exists(theme_dir):
|
|
os.makedirs(theme_dir)
|
|
|
|
gtk3_css = self.generate_gtk_css("gtk3", preset)
|
|
contents = ""
|
|
|
|
try:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css"), "r", encoding="utf-8"
|
|
) as file:
|
|
contents = file.read()
|
|
except FileNotFoundError: # first run
|
|
pass
|
|
else:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css.bak"), "w", encoding="utf-8"
|
|
) as file:
|
|
file.write(contents)
|
|
finally:
|
|
with open(
|
|
os.path.join(theme_dir, "gtk.css"), "w", encoding="utf-8"
|
|
) as file:
|
|
file.write(gtk3_css)
|
|
|
|
def restore_gtk4_preset(self) -> None:
|
|
try:
|
|
with open(
|
|
os.path.join(
|
|
os.environ.get(
|
|
"XDG_CONFIG_HOME", os.environ["HOME"] +
|
|
"/.config"
|
|
),
|
|
"gtk-4.0/gtk.css.bak",
|
|
),
|
|
"r",
|
|
encoding="utf-8",
|
|
) as backup:
|
|
contents = backup.read()
|
|
backup.close()
|
|
|
|
with open(
|
|
os.path.join(
|
|
os.environ.get(
|
|
"XDG_CONFIG_HOME", os.environ["HOME"] +
|
|
"/.config"
|
|
),
|
|
"gtk-4.0/gtk.css",
|
|
),
|
|
"w",
|
|
encoding="utf-8",
|
|
) as gtk4css:
|
|
gtk4css.write(contents)
|
|
gtk4css.close()
|
|
except OSError as e:
|
|
logging.error("Unable to restore Gtk4 backup.", exc=e)
|
|
raise
|
|
|
|
def reset_preset(self, app_type: str) -> None:
|
|
if app_type == "gtk4":
|
|
file = Gio.File.new_for_path(
|
|
os.path.join(
|
|
os.environ.get(
|
|
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
|
|
),
|
|
"gtk-4.0/gtk.css",
|
|
)
|
|
)
|
|
|
|
try:
|
|
file.delete()
|
|
except GLib.GError as e:
|
|
logging.error("Unable to delete current preset.", exc=e)
|
|
raise
|
|
elif app_type == "gtk3":
|
|
file = Gio.File.new_for_path(
|
|
os.path.join(
|
|
os.environ.get(
|
|
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
|
|
),
|
|
"gtk-3.0/gtk.css",
|
|
)
|
|
)
|
|
|
|
try:
|
|
file.delete()
|
|
except GLib.GError as e:
|
|
logging.error("Unable to delete current preset.", exc=e)
|
|
raise
|