diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..915509e --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,43 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Marble installation script + +on: + push: + branches: + - "main" + - "gdm" + pull_request: + branches: + - "main" + - "gdm" + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest scripts/tests.py diff --git a/.gitignore b/.gitignore index d8da396..b4d6a80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ __pycache__ +.pytest_cache .idea -.temp \ No newline at end of file +.temp +.tests \ No newline at end of file diff --git a/README.md b/README.md index 9338a8a..f62c712 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,32 @@ Icon theme: https://github.com/vinceliuice/Colloid-icon-theme 5. Select the shell theme you want. +## 🖥️ GDM theme + +![GDM theme](./readme-images/gdm.png?raw=true) + +1. Open the terminal. +2. Go to the directory with the theme. +3. Run the program with the `--gdm` option + ```shell + sudo python install.py --gdm (--your color) (--is filled) + ``` + - Example: + ```shell + sudo python install.py --gdm --blue --filled + ``` +4. After successful file restart GDM service: + ```shell + sudo systemctl restart gdm + ``` + +- 🗑️ If you want to remove the theme, run the program with the `--remove` option: + ```shell + sudo python install.py --gdm -r + ``` +- ☠️ If you got a death screen, you can switch to the console with the `Ctrl + Alt + F3` key combination, log in, go to the `Marble-shell-theme` directory and run the command above. If it doesn't help, try reinstalling `gnome-shell` package. + + ## 🏮 Installation tweaks #### Install default color diff --git a/colors.json b/colors.json index e22bf48..16735ab 100644 --- a/colors.json +++ b/colors.json @@ -147,6 +147,37 @@ } }, + "ACCENT-LOGIN-COLOR" : { + "_comment" : "Accent color for login screen user list", + + "light" : { + "s" : 65, + "l" : 65, + "a" : 1 + }, + + "dark" : { + "s" : 65, + "l" : 55, + "a" : 1 + } + }, + "ACCENT-LOGIN_HOVER" : { + "_comment" : "Hover color for login screen user list", + + "light" : { + "s" : 65, + "l" : 55, + "a" : 0.8 + }, + + "dark" : { + "s" : 65, + "l" : 55, + "a" : 0.8 + } + }, + "ACCENT-FILLED-COLOR" : { "_comment" : "Accent color but more vibrant", diff --git a/install.py b/install.py index 5203b4b..42a9ba4 100644 --- a/install.py +++ b/install.py @@ -15,241 +15,20 @@ # along with this program. If not, see . -import colorsys # convert hsl to rgv +import json # working with json files import os # system commands, working with files -import json # colors.json import argparse # command-line options +import shutil import textwrap # example text in argparse +from scripts import config # folder and files definitions -# folder definitions -temp_folder = "./.temp" -gnome_folder = "gnome-shell" -temp_gnome_folder = f"{temp_folder}/{gnome_folder}" -tweaks_folder = "./tweaks" -themes_folder = "~/.themes" +from scripts.utils import ( + remove_files, # delete already installed Marble theme + hex_to_rgba) # convert HEX to RGBA -# files definitions -gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css" - - -def generate_file(folder, final_file): - """ - Combines all files in a folder into a single file - :param folder: source folder - :param final_file: location where file will be created - """ - - opened_file = open(final_file, "w") - - for file in os.listdir(folder): - opened_file.write(open(folder + file).read() + '\n') - - opened_file.close() - - -def concatenate_files(file, edit_file): - """ - Merge two files - :param file: file you want to append - :param edit_file: where it will be appended - """ - - open(edit_file, 'a').write('\n' + open(file).read()) - - -def remove_files(): - """ - Delete already installed Marble theme - """ - - paths = (themes_folder, "~/.local/share/themes") - - print("💡 You do not need to delete files if you want to update theme.\n") - - confirmation = input(f"Do you want to delete all \"Marble\" folders in {' and in '.join(paths)}? (y/N) ").lower() - - if confirmation == "y": - for path in paths: - - # Check if the path exists - if os.path.exists(os.path.expanduser(path)): - - # Get the list of folders in the path - folders = os.listdir(os.path.expanduser(path)) - - # toggle if folder has no marble theme - found_folder = False - - for folder in folders: - if folder.startswith("Marble"): - folder_path = os.path.join(os.path.expanduser(path), folder) - print(f"Deleting folder {folder_path}...", end='') - - try: - os.system(f"rm -r {folder_path}") - - except Exception as e: - print(f"Error deleting folder {folder_path}: {e}") - - else: - found_folder = True - print("Done.") - - if not found_folder: - print(f"No folders starting with \"Marble\" found in {path}.") - - else: - print(f"The path {path} does not exist.") - - -def destination_return(path_name, theme_mode): - """ - Copied/modified theme location - :param path_name: color name - :param theme_mode: theme name (light or dark) - :return: copied files' folder location - """ - - return f"{themes_folder}/Marble-{path_name}-{theme_mode}/" - - -def copy_files(source, destination): - """ - Copy files from the source to another directory - :param source: where files will be copied - :param destination: where files will be pasted - """ - - destination_dirs = destination.split("/") # list of folders - loop_create_dirs = f"{destination_dirs[0]}/" - - # traverse through folders and create them - for i in range(1, len(destination_dirs)): - loop_create_dirs += f"{destination_dirs[i]}/" - os.system(f"mkdir -p {loop_create_dirs}") - - os.system(f"cp -aT {source} {destination}") - - -def replace_keywords(file, *args): - """ - Replace file with several keywords - :param file: file name where keywords must be replaced - :param args: (keyword, replacement), (...), ... - """ - - # skip binary files in project - if not file.lower().endswith(('.css', '.scss', '.svg')): - return - - with open(file, "r") as read_file: - content = read_file.read() - - for keyword, replacement in args: - content = content.replace(keyword, replacement) - - with open(file, "w") as write_file: - write_file.write(content) - - -def apply_colors(hue, destination, theme_mode, apply_file, sat=None): - """ - Install accent colors from colors.json to different file - :param hue - :param destination: file directory - :param theme_mode: theme name (light or dark) - :param apply_file: file name - :param sat: color saturation (optional) - """ - - # list of (keyword, replaced value) - replaced_colors = list() - - # colorsys works in range(0, 1) - h = hue / 360 - for element in colors["elements"]: - # if color is has default color and hasn't been replaced - if theme_mode not in colors["elements"][element] and colors["elements"][element]["default"]: - default_element = colors["elements"][element]["default"] - default_color = colors["elements"][default_element][theme_mode] - colors["elements"][element][theme_mode] = default_color - - # convert sla to range(0, 1) - lightness = int(colors["elements"][element][theme_mode]["l"]) / 100 - saturation = int(colors["elements"][element][theme_mode]["s"]) / 100 if sat is None else \ - int(colors["elements"][element][theme_mode]["s"]) * (sat / 100) / 100 - alpha = colors["elements"][element][theme_mode]["a"] - - # convert hsl to rgb and multiply every item - red, green, blue = [int(item * 256) for item in colorsys.hls_to_rgb(h, lightness, saturation)] - - replaced_colors.append((element, f"rgba({red}, {green}, {blue}, {alpha})")) - - # replace colors - replace_keywords(os.path.expanduser(f"{destination}/{apply_file}"), *replaced_colors) - - -def apply_theme(hue, destination, theme_mode, sat=None): - """ - Apply theme to all files listed in "apply-theme-files" (colors.json) - :param hue - :param destination: file directory - :param theme_mode: theme name (light or dark) - :param sat: color saturation (optional) - """ - - for apply_file in os.listdir(f"{temp_gnome_folder}/"): - apply_colors(hue, destination, theme_mode, apply_file, sat=sat) - - -def install_color(hue, name, theme_mode, sat=None): - """ - Copy files and generate theme with different accent color - :param hue - :param name: theme name - :param theme_mode: light or dark mode - :param sat: color saturation (optional) - """ - - print(f"Creating {name} {', '.join(theme_mode)} theme...", end=" ") - - try: - for mode in theme_mode: - destination = destination_return(name, mode) - - copy_files(temp_folder, destination) - apply_theme(hue, f"{destination}/{gnome_folder}", mode, sat=sat) - - except Exception as err: - print("\nError: " + str(err)) - - else: - print("Done.") - - -def hex_to_rgba(hex_color): - """ - Convert hex(a) to rgba - :param hex_color: input value - """ - - try: - if len(hex_color) in range(6, 10): - hex_color = hex_color.lstrip('#') + "ff" - # if is convertable - int(hex_color[:], 16) - else: - raise ValueError - - except ValueError: - raise ValueError(f'Error: Invalid HEX color code: {hex_color}') - - else: - return int(hex_color[0:2], 16), \ - int(hex_color[2:4], 16), \ - int(hex_color[4:6], 16), \ - int(hex_color[6:8], 16) / 255 +from scripts.theme import Theme +from scripts.gdm import GlobalTheme def main(): @@ -293,6 +72,10 @@ def main(): color_tweaks.add_argument('--sat', type=int, choices=range(0, 251), help='custom color saturation (<100%% - reduce, >100%% - increase)', metavar='(0 - 250)%') + gdm_theming = parser.add_argument_group('GDM theming') + gdm_theming.add_argument('--gdm', action='store_true', help='install GDM theme. \ + Requires root privileges. You must specify a specific color.') + panel_args = parser.add_argument_group('Panel tweaks') panel_args.add_argument('-Pds', '--panel_default_size', action='store_true', help='set default panel size') panel_args.add_argument('-Pnp', '--panel_no_pill', action='store_true', help='remove panel button background') @@ -303,80 +86,114 @@ def main(): args = parser.parse_args() - # is used as list because of install_color - mode = [args.mode] if args.mode else ['light', 'dark'] + colors = json.load(open(config.colors_json)) - # move files to temp folder - copy_files(f"./theme/{gnome_folder}/", f"{temp_gnome_folder}") - generate_file(f"./theme/{gnome_folder}_css/", gnome_shell_css) + if args.gdm: + gdm_status = 1 + if os.geteuid() != 0: + print("You must run this script as root to install GDM theme.") + return 1 - # remove marble theme - if args.remove: - remove_files() + if args.all: + print("Error: You can't install all colors for GDM theme. Use specific color.") + return 1 - # panel tweaks - if args.panel_default_size: - concatenate_files(f"{tweaks_folder}/panel/def-size.css", gnome_shell_css) + gdm_theme = GlobalTheme(colors, f"{config.raw_theme_folder}/{config.gnome_folder}", + config.global_gnome_shell_theme, config.gnome_shell_gresource, + config.temp_folder, is_filled=args.filled) - if args.panel_no_pill: - concatenate_files(f"{tweaks_folder}/panel/no-pill.css", gnome_shell_css) + if args.remove: + gdm_rm_status = gdm_theme.remove() + if gdm_rm_status == 0: + print("GDM theme removed successfully.") + return 0 - if args.panel_text_color: - open(f"{temp_gnome_folder}/{gnome_folder}.css", "a") \ - .write(".panel-button,\ - .clock,\ - .clock-display StIcon {\ - color: rgba(" + ', '.join(map(str, hex_to_rgba(args.panel_text_color))) + ");\ - }") + if args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray: + for color in colors["colors"]: + if getattr(args, color): + hue = colors["colors"][color]["h"] + sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat - # dock tweaks - if args.launchpad: - concatenate_files(f"{tweaks_folder}/launchpad/launchpad.css", gnome_shell_css) - os.system(f"cp {tweaks_folder}/launchpad/launchpad.png {temp_gnome_folder}/") + gdm_status = gdm_theme.install(hue, sat) - # color tweaks - if args.filled: - for apply_file in os.listdir(f"{temp_gnome_folder}/"): - replace_keywords(f"{temp_gnome_folder}/{apply_file}", - ("BUTTON-COLOR", "ACCENT-FILLED-COLOR"), - ("BUTTON_HOVER", "ACCENT-FILLED_HOVER"), - ("BUTTON_INSENSITIVE", "ACCENT-FILLED_INSENSITIVE"), - ("BUTTON-TEXT-COLOR", "TEXT-BLACK-COLOR"), - ("BUTTON-TEXT_SECONDARY", "TEXT-BLACK_SECONDARY")) + elif args.hue: + hue = args.hue - # what argument colors defined - if args.all: - # install hue colors listed in colors.json - for color in colors["colors"]: - hue = colors["colors"][color]["h"] - # if saturation already defined in color (gray) - sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat + gdm_status = gdm_theme.install(hue, args.sat) - install_color(hue, color, mode, sat) + else: + print('No color arguments specified. Use -h or --help to see the available options.') - elif args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray: - for color in colors["colors"]: - if getattr(args, color): # if argument name is in defined colors + if gdm_status == 0: + print("\nGDM theme installed successfully.") + print("You need to restart gdm.service to apply changes.") + print("Run \"systemctl restart gdm.service\" to restart GDM.") + + # if not GDM theme + else: + # remove marble theme + if args.remove: + remove_files() + + gnome_shell_theme = Theme("gnome-shell", colors, f"{config.raw_theme_folder}/{config.gnome_folder}", + config.themes_folder, config.temp_folder, + mode=args.mode, is_filled=args.filled) + + # panel tweaks + if args.panel_default_size: + with open(f"{config.tweaks_folder}/panel/def-size.css", "r") as f: + gnome_shell_theme += f.read() + + if args.panel_no_pill: + with open(f"{config.tweaks_folder}/panel/no-pill.css", "r") as f: + gnome_shell_theme += f.read() + + if args.panel_text_color: + gnome_shell_theme += ".panel-button,\ + .clock,\ + .clock-display StIcon {\ + color: rgba(" + ', '.join(map(str, hex_to_rgba(args.panel_text_color))) + ");\ + }" + + # dock tweaks + if args.launchpad: + with open(f"{config.tweaks_folder}/launchpad/launchpad.css", "r") as f: + gnome_shell_theme += f.read() + + gnome_shell_theme *= f"{config.tweaks_folder}/launchpad/launchpad.png" + + # what argument colors defined + if args.all: + # install hue colors listed in colors.json + for color in colors["colors"]: hue = colors["colors"][color]["h"] # if saturation already defined in color (gray) sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat - install_color(hue, color, mode, sat) + gnome_shell_theme.install(hue, color, sat) - # custom color - elif args.hue: - hue = args.hue - theme_name = args.name if args.name else f'hue{hue}' # if defined name + elif args.red or args.pink or args.purple or args.blue or args.green or args.yellow or args.gray: + # install selected colors + for color in colors["colors"]: + if getattr(args, color): # if argument name is in defined colors + hue = colors["colors"][color]["h"] + # if saturation already defined in color (gray) + sat = colors["colors"][color]["s"] if colors["colors"][color]["s"] is not None else args.sat - install_color(hue, theme_name, mode, args.sat) + gnome_shell_theme.install(hue, color, sat) - else: - print('No arguments or no color arguments specified. Use -h or --help to see the available options.') + # custom color + elif args.hue: + hue = args.hue + theme_name = args.name if args.name else f'hue{hue}' # if defined name + + gnome_shell_theme.install(hue, theme_name, args.sat) + + else: + print('No arguments or no color arguments specified. Use -h or --help to see the available options.') if __name__ == "__main__": - colors = json.load(open("colors.json")) # used as database for replacing colors - main() - os.system(f"rm -r {temp_folder}") + shutil.rmtree(config.temp_folder, ignore_errors=True) diff --git a/readme-images/gdm.png b/readme-images/gdm.png new file mode 100644 index 0000000..1490c13 Binary files /dev/null and b/readme-images/gdm.png differ diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/config.py b/scripts/config.py new file mode 100644 index 0000000..0833347 --- /dev/null +++ b/scripts/config.py @@ -0,0 +1,17 @@ +# folder definitions +temp_folder = ".temp" +gnome_folder = "gnome-shell" +temp_gnome_folder = f"{temp_folder}/{gnome_folder}" +tweaks_folder = "tweaks" +themes_folder = "~/.themes" +raw_theme_folder = "theme" +scripts_folder = "scripts" + +# GDM definitions +global_gnome_shell_theme = "/usr/share/gnome-shell" +gnome_shell_gresource = "gnome-shell-theme.gresource" +extracted_gdm_folder = "theme" + +# files definitions +gnome_shell_css = f"{temp_gnome_folder}/gnome-shell.css" +colors_json = "colors.json" diff --git a/scripts/gdm.py b/scripts/gdm.py new file mode 100644 index 0000000..ea4fa3e --- /dev/null +++ b/scripts/gdm.py @@ -0,0 +1,213 @@ +import os +import subprocess +import shutil + +from .theme import Theme +from .utils import label_files, remove_properties, remove_keywords +from . import config + + +class GlobalTheme: + def __init__(self, colors_json, theme_folder, destination_folder, destination_file, temp_folder, + is_filled=False): + """ + Initialize GlobalTheme class + :param colors_json: location of a json file with colors + :param theme_folder: raw theme location + :param destination_folder: folder where themes will be installed + :param temp_folder: folder where files will be collected + :param is_filled: if True, theme will be filled + """ + + self.colors_json = colors_json + self.theme_folder = theme_folder + self.destination_folder = destination_folder + self.destination_file = destination_file + self.temp_folder = f"{temp_folder}/gdm" + + self.backup_file = f"{self.destination_file}.backup" + self.backup_trigger = "\n/* Marble theme */\n" # trigger to check if theme is installed + self.extracted_theme = f"{self.temp_folder}/{config.extracted_gdm_folder}" + self.gst = f"{self.destination_folder}/{self.destination_file}" # use backup file if theme is installed + self.extracted_light_theme = f"{self.extracted_theme}/gnome-shell-light.css" + self.extracted_dark_theme = f"{self.extracted_theme}/gnome-shell-dark.css" + + os.makedirs(self.temp_folder, exist_ok=True) # create temp folder + + # create light and dark themes + self.light_theme = Theme("gnome-shell-light", self.colors_json, self.theme_folder, + self.extracted_theme, self.temp_folder, mode='light', is_filled=is_filled) + self.dark_theme = Theme("gnome-shell-dark", self.colors_json, self.theme_folder, + self.extracted_theme, self.temp_folder, mode='dark', is_filled=is_filled) + + def __del__(self): + """ + Delete temp folder + """ + + del self.light_theme + del self.dark_theme + + shutil.rmtree(self.temp_folder) + + def __is_installed(self): + """ + Check if theme is installed + :return: True if theme is installed, False otherwise + """ + + with open(f"{self.destination_folder}/{self.destination_file}", "rb") as f: + content = f.read() + return self.backup_trigger.encode() in content + + def __extract(self): + """ + Extract gresource files to temp folder + """ + + print("Extracting gresource files...") + + gst = self.gst + workdir = self.temp_folder + + # Get the list of resources + resources = subprocess.getoutput(f"gresource list {gst}").split("\n") + + # Create directories + for r in resources: + r = r.replace("/org/gnome/shell/", "") + directory = os.path.join(workdir, os.path.dirname(r)) + os.makedirs(directory, exist_ok=True) + + # Extract resources + for r in resources: + output_path = os.path.join(workdir, r.replace("/org/gnome/shell/", "")) + subprocess.run(f"gresource extract {gst} {r} > {output_path}", shell=True) + + def __add_gnome_styles(self, theme): + """ + Add gnome styles to the start of the file + :param theme: Theme object + """ + + with open(f"{self.extracted_theme}/{theme.theme_type}.css", 'r') as gnome_theme: + gnome_styles = gnome_theme.read() + self.backup_trigger + theme.add_to_start(gnome_styles) + + def __prepare(self, hue, color, sat=None): + """ + Generate theme files for gnome-shell-theme.gresource.xml + :param hue: color hue + :param color: color name + :param sat: color saturation + """ + + # add -light label to light theme files because they are installed to the same folder + label_files(self.light_theme.temp_folder, "light", self.light_theme.main_styles) + + # remove !important from the gnome file + remove_keywords(self.extracted_light_theme, "!important") + remove_keywords(self.extracted_dark_theme, "!important") + + # remove properties from the gnome file + props_to_remove = ("background-color", "color", "box-shadow", "border-radius") + remove_properties(self.extracted_light_theme, *props_to_remove) + remove_properties(self.extracted_dark_theme, *props_to_remove) + + # add gnome styles to the start of the file + self.__add_gnome_styles(self.light_theme) + self.__add_gnome_styles(self.dark_theme) + + # build code for gnome-shell-theme.gresource.xml + self.light_theme.install(hue, color, sat, destination=self.extracted_theme) + self.dark_theme.install(hue, color, sat, destination=self.extracted_theme) + + def __backup(self): + """ + Backup installed theme + """ + + if self.__is_installed(): + return + + # backup installed theme + print("Backing up default theme...") + os.system(f"cp -aT {self.gst} {self.gst}.backup") + + def __generte_gresource_xml(self): + """ + Generates.gresource.xml + """ + + # list of files to add to gnome-shell-theme.gresource.xml + files = list(f"{file}" for file in os.listdir(self.extracted_theme)) + nl = "\n" # fstring doesn't support newline character + + ready_xml = f""" + + + {nl.join(files)} + +""" + + return ready_xml + + def install(self, hue, sat=None): + """ + Install theme globally + :param hue: color hue + :param sat: color saturation + """ + + if self.__is_installed(): + print("Theme is installed. Reinstalling...") + self.gst += ".backup" + + self.__extract() + + # generate theme files for global theme + self.__prepare(hue, 'Marble', sat) + + # generate gnome-shell-theme.gresource.xml + with open(f"{self.extracted_theme}/{self.destination_file}.xml", 'w') as gresource_xml: + generated_xml = self.__generte_gresource_xml() + gresource_xml.write(generated_xml) + + # compile gnome-shell-theme.gresource.xml + print("Compiling theme...") + subprocess.run(f"glib-compile-resources {self.destination_file}.xml", + shell=True, cwd=self.extracted_theme) + + # backup installed theme + self.__backup() + + # install theme + print("Installing theme...") + os.system(f"sudo cp -f {self.extracted_theme}/{self.destination_file} " + f"{self.destination_folder}/{self.destination_file}") + + return 0 + + def remove(self): + """ + Remove installed theme + """ + + # use backup file if theme is installed + if self.__is_installed(): + print("Theme is installed. Removing...") + + if os.path.isfile(f"{self.destination_folder}/{self.backup_file}"): + subprocess.run(f"sudo mv {self.backup_file} {self.destination_file}", + shell=True, cwd=self.destination_folder) + + else: + print("Backup file not found. Try reinstalling gnome-shell package.") + return 1 + + else: + print("Theme is not installed. Nothing to remove.") + print("If theme is still installed globally, try reinstalling gnome-shell package.") + return 1 + + return 0 diff --git a/scripts/tests.py b/scripts/tests.py new file mode 100644 index 0000000..b8ff790 --- /dev/null +++ b/scripts/tests.py @@ -0,0 +1,56 @@ +import unittest +import os +import json +import shutil + +from . import config +from .theme import Theme + +# folders +tests_folder = '.tests' +project_folder = '.' + + +class TestInstall(unittest.TestCase): + + def test_install_theme(self): + """ + Test if theme is installed correctly (colors are replaced) + """ + + # folders + themes_folder = f"{tests_folder}/.themes" + temp_folder = f"{tests_folder}/.temp" + + # colors from colors.json + colors_json = open(f"{project_folder}/{config.colors_json}") + colors = json.load(colors_json) + colors_json.close() + + # create test theme + test_theme = Theme("gnome-shell", colors, + f"{project_folder}/{config.raw_theme_folder}/{config.gnome_folder}", + themes_folder, temp_folder, + mode='light', is_filled=True) + + # install test theme + test_theme.install(120, 'test', 70) + + # folder with installed theme (.tests/.themes/Marble-test-light/gnome-shell) + installed_theme = f"{themes_folder}/{os.listdir(themes_folder)[0]}/{config.gnome_folder}" + + # check if files are installed + for file in os.listdir(installed_theme): + with open(f"{installed_theme}/{file}") as f: + read_file = f.read() + + for color in colors["elements"]: + self.assertNotIn(color, read_file, msg=f"Color {color} is not replaced in {file}") + + # delete test theme + del test_theme + shutil.rmtree(tests_folder) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/theme.py b/scripts/theme.py new file mode 100644 index 0000000..abb4f22 --- /dev/null +++ b/scripts/theme.py @@ -0,0 +1,163 @@ +import os +import shutil +import colorsys # colorsys.hls_to_rgb(h, l, s) + +from .utils import ( + replace_keywords, # replace keywords in file + copy_files, # copy files from source to destination + destination_return, # copied/modified theme location + generate_file) # combine files from folder to one file + + +class Theme: + def __init__(self, theme_type, colors_json, theme_folder, destination_folder, temp_folder, + mode=None, is_filled=False): + """ + Initialize Theme class + :param colors_json: location of a json file with colors + :param theme_type: theme type (gnome-shell, gtk, etc.) + :param theme_folder: raw theme location + :param destination_folder: folder where themes will be installed + :param temp_folder: folder where files will be collected + :param mode: theme mode (light or dark) + :param is_filled: if True, theme will be filled + """ + + self.colors = colors_json + self.temp_folder = f"{temp_folder}/{theme_type}" + self.theme_folder = theme_folder + self.theme_type = theme_type + self.mode = [mode] if mode else ['light', 'dark'] + self.destination_folder = destination_folder + self.main_styles = f"{self.temp_folder}/{theme_type}.css" + + # move files to temp folder + copy_files(self.theme_folder, self.temp_folder) + generate_file(f"{self.theme_folder}_css/", self.main_styles) + + # if theme is filled + if is_filled: + for apply_file in os.listdir(f"{self.temp_folder}/"): + replace_keywords(f"{self.temp_folder}/{apply_file}", + ("BUTTON-COLOR", "ACCENT-FILLED-COLOR"), + ("BUTTON_HOVER", "ACCENT-FILLED_HOVER"), + ("BUTTON_INSENSITIVE", "ACCENT-FILLED_INSENSITIVE"), + ("BUTTON-TEXT-COLOR", "TEXT-BLACK-COLOR"), + ("BUTTON-TEXT_SECONDARY", "TEXT-BLACK_SECONDARY")) + + def __add__(self, other): + """ + Add to main styles another styles + :param other: styles to add + :return: new Theme object + """ + + with open(self.main_styles, 'a') as main_styles: + main_styles.write('\n' + other) + return self + + def __mul__(self, other): + """ + Copy files to temp folder + :param other: file or folder + :return: new Theme object + """ + + if os.path.isfile(other): + shutil.copy(other, self.temp_folder) + else: + shutil.copytree(other, self.temp_folder) + + return self + + def __del__(self): + # delete temp folder + os.system(f"rm -r {self.temp_folder}") + + def __apply_colors(self, hue, destination, theme_mode, apply_file, sat=None): + """ + Install accent colors from colors.json to different file + :param hue + :param destination: file directory + :param theme_mode: theme name (light or dark) + :param apply_file: file name + :param sat: color saturation (optional) + """ + + # list of (keyword, replaced value) + replaced_colors = list() + + # colorsys works in range(0, 1) + h = hue / 360 + for element in self.colors["elements"]: + # if color has default color and hasn't been replaced + if theme_mode not in self.colors["elements"][element] and self.colors["elements"][element]["default"]: + default_element = self.colors["elements"][element]["default"] + default_color = self.colors["elements"][default_element][theme_mode] + self.colors["elements"][element][theme_mode] = default_color + + # convert sla to range(0, 1) + lightness = int(self.colors["elements"][element][theme_mode]["l"]) / 100 + saturation = int(self.colors["elements"][element][theme_mode]["s"]) / 100 if sat is None else \ + int(self.colors["elements"][element][theme_mode]["s"]) * (sat / 100) / 100 + alpha = self.colors["elements"][element][theme_mode]["a"] + + # convert hsl to rgb and multiply every item + red, green, blue = [int(item * 256) for item in colorsys.hls_to_rgb(h, lightness, saturation)] + + replaced_colors.append((element, f"rgba({red}, {green}, {blue}, {alpha})")) + + # replace colors + replace_keywords(os.path.expanduser(f"{destination}/{apply_file}"), *replaced_colors) + + def __apply_theme(self, hue, source, destination, theme_mode, sat=None): + """ + Apply theme to all files in directory + :param hue + :param source + :param destination: file directory + :param theme_mode: theme name (light or dark) + :param sat: color saturation (optional) + """ + + for apply_file in os.listdir(f"{source}/"): + self.__apply_colors(hue, destination, theme_mode, apply_file, sat=sat) + + def install(self, hue, name, sat=None, destination=None): + """ + Copy files and generate theme with different accent color + :param hue + :param name: theme name + :param sat: color saturation (optional) + :param destination: folder where theme will be installed + """ + + is_dest = bool(destination) + + print(f"Creating {name} {', '.join(self.mode)} theme...", end=" ") + + try: + for mode in self.mode: + if not is_dest: + destination = destination_return(self.destination_folder, name, mode, self.theme_type) + + copy_files(self.temp_folder + '/', destination) + self.__apply_theme(hue, self.temp_folder, destination, mode, sat=sat) + + except Exception as err: + print("\nError: " + str(err)) + + else: + print("Done.") + + def add_to_start(self, content): + """ + Add content to the start of main styles + :param content: content to add + """ + + with open(self.main_styles, 'r+') as main_styles: + main_content = main_styles.read() + + main_styles.seek(0) + main_styles.write(content + '\n' + main_content) diff --git a/scripts/utils.py b/scripts/utils.py new file mode 100644 index 0000000..3f7fbf9 --- /dev/null +++ b/scripts/utils.py @@ -0,0 +1,223 @@ +import os +from . import config # name of folders and files + + +def generate_file(folder, final_file): + """ + Combines all files in a folder into a single file + :param folder: source folder + :param final_file: location where file will be created + """ + + opened_file = open(final_file, "w") + + for file in os.listdir(folder): + with open(folder + file) as f: + opened_file.write(f.read() + '\n') + + opened_file.close() + + +def concatenate_files(edit_file, file): + """ + Merge two files + :param edit_file: where it will be appended + :param file: file you want to append + """ + + with open(file, 'r') as read_file: + file_content = read_file.read() + + with open(edit_file, 'a') as write_file: + write_file.write('\n' + file_content) + + +def remove_files(): + """ + Delete already installed Marble theme + """ + + paths = (config.themes_folder, "~/.local/share/themes") + + print("💡 You do not need to delete files if you want to update theme.\n") + + confirmation = input(f"Do you want to delete all \"Marble\" folders in {' and in '.join(paths)}? (y/N) ").lower() + + if confirmation == "y": + for path in paths: + + # Check if the path exists + if os.path.exists(os.path.expanduser(path)): + + # Get the list of folders in the path + folders = os.listdir(os.path.expanduser(path)) + + # toggle if folder has no marble theme + found_folder = False + + for folder in folders: + if folder.startswith("Marble"): + folder_path = os.path.join(os.path.expanduser(path), folder) + print(f"Deleting folder {folder_path}...", end='') + + try: + os.system(f"rm -r {folder_path}") + + except Exception as e: + print(f"Error deleting folder {folder_path}: {e}") + + else: + found_folder = True + print("Done.") + + if not found_folder: + print(f"No folders starting with \"Marble\" found in {path}.") + + else: + print(f"The path {path} does not exist.") + + +def destination_return(themes_folder, path_name, theme_mode, theme_type): + """ + Copied/modified theme location + :param themes_folder: themes folder location + :param path_name: color name + :param theme_mode: theme name (light or dark) + :param theme_type: theme type (gnome-shell, gtk-4.0, ...) + :return: copied files' folder location + """ + + return f"{themes_folder}/Marble-{path_name}-{theme_mode}/{theme_type}/" + + +def copy_files(source, destination): + """ + Copy files from the source to another directory + :param source: where files will be copied + :param destination: where files will be pasted + """ + + destination = os.path.expanduser(destination) # expand ~ to /home/user + os.makedirs(destination, exist_ok=True) + os.system(f"cp -aT {source} {destination}") + + +def replace_keywords(file, *args): + """ + Replace file with several keywords + :param file: file name where keywords must be replaced + :param args: (keyword, replacement), (...), ... + """ + + # skip binary files in project + if not file.lower().endswith(('.css', '.scss', '.svg')): + return + + with open(file, "r") as read_file: + content = read_file.read() + + for keyword, replacement in args: + content = content.replace(keyword, replacement) + + with open(file, "w") as write_file: + write_file.write(content) + + +def hex_to_rgba(hex_color): + """ + Convert hex(a) to rgba + :param hex_color: input value + """ + + try: + if len(hex_color) in range(6, 10): + hex_color = hex_color.lstrip('#') + "ff" + # if is convertable + int(hex_color[:], 16) + else: + raise ValueError + + except ValueError: + raise ValueError(f'Error: Invalid HEX color code: {hex_color}') + + else: + return int(hex_color[0:2], 16), \ + int(hex_color[2:4], 16), \ + int(hex_color[4:6], 16), \ + int(hex_color[6:8], 16) / 255 + + +def label_files(directory, label, *args): + """ + Add a label to all files in a directory + :param directory: folder where files are located + :param label: label to add + :param args: files to change links to labeled files + :return: + """ + + # Open all files + files = [open(file, 'r+') for file in args] + read_files = [file.read() for file in files] + + for filename in os.listdir(directory): + # Skip if the file is already labeled + if label in filename: + continue + + # Split the filename into name and extension + name, extension = os.path.splitext(filename) + + # Form the new filename and rename the file + new_filename = f"{name}-{label}{extension}" + os.rename(os.path.join(directory, filename), os.path.join(directory, new_filename)) + + # Replace the filename in all files + for file in read_files: + file.replace(filename, new_filename) + + # Write the changes to the files and close them + for i, file in enumerate(files): + file.seek(0) + file.write(read_files[i]) + file.close() + + +def remove_properties(file, *args): + """ + Remove properties from a file + :param file: file name + :param args: properties to remove + """ + + new_content = "" + + with open(file, "r+") as read_file: + content = read_file.read() + + for line in content.splitlines(): + if not any(prop in line for prop in args): + new_content += line + "\n" + elif "}" in line: + new_content += "}\n" + + read_file.seek(0) + read_file.write(new_content) + + +def remove_keywords(file, *args): + """ + Remove keywords from a file + :param file: file name + :param args: keywords to remove + """ + + with open(file, "r+") as read_file: + content = read_file.read() + + for arg in args: + content = content.replace(arg, "") + + read_file.seek(0) + read_file.write(content) + \ No newline at end of file diff --git a/theme/gnome-shell_css/apps.css b/theme/gnome-shell_css/apps.css index 259a213..3bdda4b 100644 --- a/theme/gnome-shell_css/apps.css +++ b/theme/gnome-shell_css/apps.css @@ -104,9 +104,9 @@ /* Page arrow */ - .page-navigation-arrow { - border-radius: 99px; + border-radius: 15px; + padding: 12px; transition-duration: 100ms; } diff --git a/theme/gnome-shell_css/controls.css b/theme/gnome-shell_css/controls.css index 7edb00f..a4f58c7 100644 --- a/theme/gnome-shell_css/controls.css +++ b/theme/gnome-shell_css/controls.css @@ -81,6 +81,7 @@ .button:insensitive { box-shadow: none; color: TEXT-SECONDARY-COLOR; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; } .button:hover, @@ -104,12 +105,10 @@ .slider, .level { - height: 16px; -barlevel-height: 16px; -barlevel-background-color: ACCENT-DISABLED-COLOR; - box-shadow: inset 0 0 0 1px BORDER-SHADOW; - /* fill */ -barlevel-active-background-color: BUTTON-COLOR; + -barlevel-border-width: 0px; /* overfill */ -barlevel-overdrive-color: #c01c28; -barlevel-overdrive-separator-width: 2px; diff --git a/theme/gnome-shell_css/entries.css b/theme/gnome-shell_css/entries.css index ceb6a31..b654227 100644 --- a/theme/gnome-shell_css/entries.css +++ b/theme/gnome-shell_css/entries.css @@ -1,6 +1,7 @@ /* Entries */ -StEntry { +StEntry, +.search-entry { border-radius: 13px; padding: 8px; transition-duration: 100ms; @@ -31,14 +32,4 @@ StEntry:active { StEntry:focus { color: TEXT-PRIMARY-COLOR; -} - - -/* Entry in lock screen */ -.unlock-dialog StEntry { - border: none !important; -} - -.unlock-dialog StEntry:focus { - box-shadow: inset 0 0 0 1px TEXT-DISABLED-COLOR; } \ No newline at end of file diff --git a/theme/gnome-shell_css/loginlock.css b/theme/gnome-shell_css/loginlock.css new file mode 100644 index 0000000..74da88d --- /dev/null +++ b/theme/gnome-shell_css/loginlock.css @@ -0,0 +1,179 @@ +/* Lock Screen / Login screen */ + +#lockDialogGroup { + background-color: #000; +} + +.login-dialog { + background-color: BACKGROUND-OPAQUE-COLOR; +} + + +.login-dialog .caps-lock-warning-label, +.login-dialog .login-dialog-message-warning, +.unlock-dialog .caps-lock-warning-label, +.unlock-dialog .login-dialog-message-warning { + color: TEXT-SECONDARY-COLOR; +} + + +/* not listed button */ +.login-dialog-not-listed-label { + color: TEXT-SECONDARY-COLOR; +} +.login-dialog-not-listed-button:focus .login-dialog-not-listed-label, +.login-dialog-not-listed-button:hover .login-dialog-not-listed-label { + color: TEXT-PRIMARY-COLOR; +} +.login-dialog-not-listed-button:focus .login-dialog-not-listed-label { + text-decoration: underline; +} + + +/* users button */ +.login-dialog-user-list .login-dialog-user-list-item { + background-color: SECTION-COLOR; + border-radius: 20px; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; +} + +.login-dialog-user-list:expanded .login-dialog-user-list-item { + transition-duration: 200ms; +} + +.login-dialog-user-list:expanded .login-dialog-user-list-item:hover { + background-color: ACCENT-LOGIN-COLOR; +} + +.login-dialog-user-list:expanded .login-dialog-user-list-item:focus, +.login-dialog-user-list:expanded .login-dialog-user-list-item:selected { + background-color: ACCENT-LOGIN-COLOR; + /* color: BUTTON-TEXT-COLOR; don't work*/ + box-shadow: inset 0 0 0 1px BORDER-MENU-SHADOW; +} + +.login-dialog-user-list:expanded .login-dialog-user-list-item:focus:hover, +.login-dialog-user-list:expanded .login-dialog-user-list-item:selected:hover { + background-color: ACCENT-LOGIN_HOVER; +} + + +/* login buttons */ +.login-dialog-button.cancel-button, +.login-dialog-button.switch-user-button, +.login-dialog-button.login-dialog-session-list-button { + background-color: ACCENT-DISABLED-COLOR; + color: TEXT-PRIMARY-COLOR; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; + border-radius: 99px; + transition-duration: 100ms; +} + +.login-dialog-button.cancel-button:hover, +.login-dialog-button.switch-user-button:hover, +.login-dialog-button.login-dialog-session-list-button:hover { + background-color: ACCENT-DISABLED_HOVER; +} + +.login-dialog-button.cancel-button:focus, +.login-dialog-button.switch-user-button:focus, +.login-dialog-button.login-dialog-session-list-button:focus { + background-color: ACCENT-DISABLED_HOVER; + box-shadow: inset 0 0 0 2px ACCENT-SECONDARY-COLOR !important; +} + +.login-dialog-button.cancel-button:focus:active, +.login-dialog-button.switch-user-button:focus:active, +.login-dialog-button.login-dialog-session-list-button:focus:active, +.login-dialog-button.cancel-button:focus:hover, +.login-dialog-button.switch-user-button:focus:hover, +.login-dialog-button.login-dialog-session-list-button:focus:hover { + background-color: ACCENT-DISABLED_HOVER; + box-shadow: inset 0 0 0 1px ACCENT-SECONDARY-COLOR !important; +} + +.login-dialog-button.cancel-button:active, +.login-dialog-button.switch-user-button:active, +.login-dialog-button.login-dialog-session-list-button:active { + background-color: ACCENT-DISABLED_HOVER; +} + + +/* password */ +.login-dialog-prompt-entry { + background-color: ACCENT-DISABLED-COLOR; + border: none; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; +} + +.login-dialog-prompt-entry:focus { + background-color: ACCENT-DISABLED-COLOR !important; + border: none; + box-shadow: inset 0 0 0 1px TEXT-DISABLED-COLOR !important; + border-color: TEXT-DISABLED-COLOR; +} + +.login-dialog-prompt-entry:insensitive { + background-color: ACCENT-DISABLED-COLOR; + color: TEXT-DISABLED-COLOR; +} + + +/* Clock */ +.unlock-dialog-clock { + color: TEXT-PRIMARY-COLOR; + spacing: 1em; +} + +.unlock-dialog-clock .unlock-dialog-clock-time { + font-feature-settings: "normal"; + font-weight: 500; +} + +.unlock-dialog-clock .unlock-dialog-clock-date { + font-weight: 500; + font-size: 16pt; +} + +.unlock-dialog-clock .unlock-dialog-clock-hint { + margin-top: 1em; + font-weight: 600; + font-size: 10pt; + color: TEXT-SECONDARY-COLOR; +} + + +/* Notifications */ +.unlock-dialog-notifications-container .notification, +.unlock-dialog-notifications-container .unlock-dialog-notification-source { + padding: 12px 16px; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; + background-color: ACCENT-DISABLED-COLOR; + color: TEXT-PRIMARY-COLOR; + border-radius: 16px; +} +.unlock-dialog-notifications-container .notification.critical, +.unlock-dialog-notifications-container .unlock-dialog-notification-source.critical { + background-color: ACCENT_HOVER; +} + +/* messages counter */ +.unlock-dialog-notification-count-text { + color: TEXT-SECONDARY-COLOR; + background-color: ACCENT-DISABLED-COLOR; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; + border-radius: 99px; +} + + +/* User widget */ +.user-widget .user-widget-label { + color: TEXT-PRIMARY-COLOR; +} + +.user-widget.horizontal .user-icon StIcon, +.user-widget.vertical .user-icon StIcon { + color: TEXT-PRIMARY-COLOR; + background-color: ACCENT-DISABLED-COLOR; + box-shadow: inset 0 0 0 1px BORDER-SHADOW; +} \ No newline at end of file diff --git a/theme/gnome-shell_css/messages.css b/theme/gnome-shell_css/messages.css index c9faeaa..4b4b947 100644 --- a/theme/gnome-shell_css/messages.css +++ b/theme/gnome-shell_css/messages.css @@ -2,8 +2,8 @@ /* datemenu message-list */ .message-list { - border: none; /* remove border between messages and calendar section */ - padding: 0; + border-color: transparent; /* remove border between messages and calendar section */ + padding: 0 !important; } /* "No Notifications" text */ diff --git a/theme/gnome-shell_css/osd.css b/theme/gnome-shell_css/osd.css index fadc413..0246a72 100644 --- a/theme/gnome-shell_css/osd.css +++ b/theme/gnome-shell_css/osd.css @@ -42,6 +42,15 @@ /* ctrl + alt + arr_left/arr_right */ .ws-switcher-indicator { background-color: TEXT-SECONDARY-COLOR; + border-radius: 99px; + padding: 2.5px; + margin: 7.5px 5px; +} + +.ws-switcher-indicator:active { + background-color: TEXT-PRIMARY-COLOR; + padding: 5px 20px; + margin: 5px; } diff --git a/theme/gnome-shell_css/overview.css b/theme/gnome-shell_css/overview.css index bb8806c..704a059 100644 --- a/theme/gnome-shell_css/overview.css +++ b/theme/gnome-shell_css/overview.css @@ -35,9 +35,8 @@ /* thumbnails on overview (workspaces list under search) */ .workspace-thumbnail { - border: none; /* because of impossibility round fullscreen apps */ - background-color: ACCENT-DISABLED-COLOR; - border: 1px solid BORDER-SHADOW; + background-color: ACCENT-DISABLED-COLOR !important; + border: 1px solid BORDER-SHADOW !important; } /* drag and drop indicator */ diff --git a/theme/gnome-shell_css/panel.css b/theme/gnome-shell_css/panel.css index 44af478..00ed913 100644 --- a/theme/gnome-shell_css/panel.css +++ b/theme/gnome-shell_css/panel.css @@ -20,7 +20,7 @@ .clock-display StIcon { /* DND / new messages icon */ color: TEXT-PRIMARY-COLOR; border-radius: 14px; - border: 4px solid transparent; + border: 4px solid transparent !important; background-color: ACCENT-DISABLED-COLOR; box-shadow: inset 0 0 0 1px BORDER-SHADOW; } @@ -35,6 +35,7 @@ /* workspaces indicator in activities button (45+) */ #panel .workspace-dot { background-color: TEXT-PRIMARY-COLOR; + border-radius: 99px; } /* indicator for active */ diff --git a/theme/gnome-shell_css/popovers.css b/theme/gnome-shell_css/popovers.css index 360df9b..937dba2 100644 --- a/theme/gnome-shell_css/popovers.css +++ b/theme/gnome-shell_css/popovers.css @@ -13,6 +13,7 @@ .popup-menu-item, /* menu items */ .app-menu { /* right-click (and panel) app menu */ margin: 3px 0; + border-radius: 12px; transition-duration: 150ms; } @@ -64,6 +65,7 @@ margin: 0; color: TEXT-PRIMARY-COLOR; background-color: transparent; + border-radius: 0; } /* make rounded borders for last element */ diff --git a/theme/gnome-shell_css/quick-settings.css b/theme/gnome-shell_css/quick-settings.css index 881907b..3242ea8 100644 --- a/theme/gnome-shell_css/quick-settings.css +++ b/theme/gnome-shell_css/quick-settings.css @@ -12,6 +12,7 @@ background-color: ACCENT-DISABLED-COLOR; color: TEXT-PRIMARY-COLOR; transition-duration: 100ms; + border-radius: 99px; box-shadow: inset 0 0 0 1px BORDER-SHADOW; } @@ -67,6 +68,7 @@ .quick-toggle:hover, .quick-menu-toggle .quick-toggle:hover, .quick-menu-toggle .quick-toggle-arrow:hover { + color: TEXT-PRIMARY-COLOR; background-color: ACCENT-DISABLED_HOVER; } @@ -81,16 +83,18 @@ .quick-toggle:checked:hover, .quick-menu-toggle .quick-toggle:checked:hover, .quick-menu-toggle .quick-toggle-arrow:checked:hover { + color: BUTTON-TEXT-COLOR; background-color: BUTTON_HOVER; } /* quick slider section */ -/* fix margins in quick slider dection */ +/* fix margins in quick slider section */ .quick-slider .slider-bin { - padding: 2px 1px; + padding: 4px; margin: 0; color: BUTTON-TEXT-COLOR; + border-radius: 99px; } .quick-slider .slider-bin:focus { diff --git a/theme/gnome-shell_css/screenshot.css b/theme/gnome-shell_css/screenshot.css index 8e02201..b3e47c7 100644 --- a/theme/gnome-shell_css/screenshot.css +++ b/theme/gnome-shell_css/screenshot.css @@ -53,6 +53,10 @@ background-color: SECTION-COLOR; } +.screenshot-ui-window-selector-window-border { + border-radius: 16px; +} + /* window border when selecting */ .screenshot-ui-window-selector-window:hover .screenshot-ui-window-selector-window-border { border-color: BUTTON-COLOR; @@ -63,10 +67,13 @@ background-color: ACCENT-OPACITY-COLOR; } +.screenshot-ui-window-selector-check { color: transparent; } + /* check icon when selected window */ .screenshot-ui-window-selector-window:checked .screenshot-ui-window-selector-check { color: BUTTON-TEXT-COLOR; background-color: BUTTON-COLOR; + border-radius: 99px; } @@ -114,12 +121,14 @@ /* screenshot / screencast buttons section */ .screenshot-ui-shot-cast-container { background-color: ACCENT-DISABLED-COLOR; + border-radius: 99px; box-shadow: inset 0 0 0 1px BORDER-SHADOW; } /* screenshot / screencast buttons */ .screenshot-ui-shot-cast-button { background-color: transparent; + border-radius: 99px; transition-duration: 100ms; } @@ -135,6 +144,32 @@ color: TEXT-INVERTED-COLOR; } +.screenshot-ui-area-indicator-shade { + background-color: rgba(0, 0, 0, 0.5); +} + +/* screen selection borders */ +.screenshot-ui-area-selector-handle { + border-radius: 99px; + background-color: white; + box-shadow: 0 1px 3px 2px rgba(0, 0, 0, 0.05); +} + + +.screenshot-ui-screen-selector { + background-color: rgba(0, 0, 0, 0.5); +} +.screenshot-ui-screen-selector:hover { + background-color: rgba(0, 0, 0, 0.3); +} +.screenshot-ui-screen-selector:active { + background-color: rgba(0, 0, 0, 0.7); +} +.screenshot-ui-screen-selector:checked { + background-color: transparent; +} + + /* tooltip for elements */ .screenshot-ui-tooltip { @@ -152,6 +187,7 @@ background-color: BUTTON-CLOSE-COLOR; color: TEXT-PRIMARY-COLOR; border: 1px solid BORDER-SHADOW; + border-radius: 99px; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); transition-duration: 100ms; }