[+] Random custom coloring

This commit is contained in:
Azalea (on HyDEV-Daisy) 2022-06-19 20:50:09 -04:00
parent 7bd96e422f
commit a1d687d3bd
3 changed files with 184 additions and 45 deletions

View file

@ -2,20 +2,19 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import importlib
import json import json
import os import random
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from itertools import permutations
from typing import Iterable from typing import Iterable
from typing_extensions import Literal from typing_extensions import Literal
from . import constants from . import constants
from .color_util import AnsiMode, printc, color, clear_screen, RGB from .color_util import AnsiMode, printc, color, clear_screen
from .constants import CONFIG_PATH, VERSION, TERM_LEN, TEST_ASCII_WIDTH, TEST_ASCII from .constants import CONFIG_PATH, VERSION, TERM_LEN, TEST_ASCII_WIDTH, TEST_ASCII
from .neofetch_util import run_neofetch, replace_colors, get_custom_distro_ascii from .neofetch_util import run_neofetch, get_distro_ascii, ColorAlignment, ascii_size
from .presets import PRESETS, ColorProfile from .presets import PRESETS
from .serializer import json_stringify from .serializer import json_stringify
@ -25,6 +24,7 @@ class Config:
mode: AnsiMode mode: AnsiMode
light_dark: Literal['light', 'dark'] = 'dark' light_dark: Literal['light', 'dark'] = 'dark'
lightness: float | None = None lightness: float | None = None
color_align: ColorAlignment = ColorAlignment('horizontal')
def save(self): def save(self):
CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True) CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True)
@ -160,8 +160,8 @@ def create_config() -> Config:
num_cols = TERM_LEN // (TEST_ASCII_WIDTH + 2) num_cols = TERM_LEN // (TEST_ASCII_WIDTH + 2)
ratios = [col / (num_cols - 1) for col in range(num_cols)] ratios = [col / (num_cols - 1) for col in range(num_cols)]
ratios = [r * 0.6 + 0.2 for r in ratios] ratios = [r * 0.6 + 0.2 for r in ratios]
lines = [replace_colors(TEST_ASCII.replace('{txt}', f'{r * 100:.0f}%'.center(5)), lines = [ColorAlignment('horizontal').recolor_ascii(TEST_ASCII.replace(
_prs.set_light(r))[0].split('\n') for r in ratios] '{txt}', f'{r * 100:.0f}%'.center(5)), _prs.set_light(r)).split('\n') for r in ratios]
[printc(' '.join(line)) for line in zip(*lines)] [printc(' '.join(line)) for line in zip(*lines)]
while True: while True:
@ -182,8 +182,66 @@ def create_config() -> Config:
except Exception: except Exception:
printc('&cUnable to parse lightness value, please input it as a decimal or percentage (e.g. 0.5 or 50%)') printc('&cUnable to parse lightness value, please input it as a decimal or percentage (e.g. 0.5 or 50%)')
if lightness:
_prs = _prs.set_light(lightness)
title += f'\n&e4. Brightness: &r{f"{lightness:.2f}" if lightness else "unset"}'
#############################
# 5. Color arrangement
while True:
clear_screen(title)
printc(f'&a5. Let\'s choose a color arrangement!')
printc(f'You can choose standard horizontal or vertical alignment, or use one of the random color schemes, or assign colors yourself (TODO).')
print()
asc = get_distro_ascii()
asc_width = ascii_size(asc)[0]
asciis = [
['Horizontal'.center(asc_width), *ColorAlignment('horizontal').recolor_ascii(asc, _prs).split('\n')],
['Vertical'.center(asc_width), *ColorAlignment('vertical').recolor_ascii(asc, _prs).split('\n')],
]
# Random color schemes
# ascii_indices =
pis = list(range(len(_prs.unique_colors().colors)))
while len(pis) < 6:
pis += pis
perm = list(permutations(pis))
choices = random.sample(perm, 4)
choices = [{i: n for i, n in enumerate(c)} for c in choices]
asciis += [[f'Random {i}'.center(asc_width), *ColorAlignment('custom', r).recolor_ascii(asc, _prs).split('\n')]
for i, r in enumerate(choices)]
ascii_per_row = TERM_LEN // (asc_width + 2)
while asciis:
current = asciis[:ascii_per_row]
asciis = asciis[ascii_per_row:]
# Print by row
[printc(' '.join(line)) for line in zip(*current)]
print()
print('You can type "roll" to randomize again.')
print()
choice = literal_input(f'Your choice?', ['horizontal', 'vertical', 'roll', 'random1', 'random2', 'random3', 'random4'], 'horizontal')
if choice == 'roll':
continue
if choice in ['horizontal', 'vertical']:
color_alignment = ColorAlignment(choice)
elif choice.startswith('random'):
color_alignment = ColorAlignment('custom', choices[int(choice[6]) - 1])
else:
raise NotImplementedError()
break
title += f'\n&e5. Color Alignment: &r{color_alignment}'
# Create config # Create config
c = Config(preset, color_system, light_dark, lightness) clear_screen(title)
c = Config(preset, color_system, light_dark, lightness, color_alignment)
# Save config # Save config
print() print()
@ -238,13 +296,15 @@ def run():
preset = preset.lighten(args.scale) preset = preset.lighten(args.scale)
if args.light: if args.light:
preset = preset.set_light(args.light) preset = preset.set_light(args.light)
if config.lightness:
preset = preset.set_light(config.lightness)
# Test distro ascii art # Test distro ascii art
if args.test_distro: if args.test_distro:
asc = get_custom_distro_ascii(args.test_distro) asc = get_distro_ascii(args.test_distro)
print(asc) print(asc)
print(replace_colors(asc, preset, config.mode)[0]) print(ColorAlignment('horizontal').recolor_ascii(asc, preset))
return return
# Run # Run
run_neofetch(preset, config.mode) run_neofetch(preset, config.color_align)

View file

@ -4,17 +4,94 @@ import os
import platform import platform
import re import re
import subprocess import subprocess
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from subprocess import check_output from subprocess import check_output
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
import pkg_resources import pkg_resources
from hyfetch.color_util import color
from typing_extensions import Literal
from .color_util import AnsiMode
from .constants import COLOR_MODE
from .presets import ColorProfile from .presets import ColorProfile
RE_NEOFETCH_COLOR = re.compile('\\${c[0-9]}')
def ascii_size(asc: str) -> tuple[int, int]:
"""
Get distro ascii width, height ignoring color code
:param asc: Distro ascii
:return: Width, Height
"""
return max(len(line) for line in re.sub(RE_NEOFETCH_COLOR, '', asc).split('\n')), len(asc.split('\n'))
def normalize_ascii(asc: str) -> str:
"""
Make sure every line are the same width
"""
w = ascii_size(asc)[0]
return '\n'.join(line + ' ' * (w - ascii_size(line)[0]) for line in asc.split('\n'))
@dataclass
class ColorAlignment:
mode: Literal['horizontal', 'vertical', 'custom']
# custom_colors[ascii color index] = unique color index in preset
custom_colors: dict[int, int] = ()
def recolor_ascii(self, asc: str, preset: ColorProfile) -> str:
"""
Use the color alignment to recolor an ascii art
:return Colored ascii, Uncolored lines
"""
if self.mode in ['horizontal', 'vertical']:
# Remove existing colors
asc = re.sub(RE_NEOFETCH_COLOR, '', asc)
lines = asc.split('\n')
# Add new colors
if self.mode == 'horizontal':
colors = preset.with_length(len(lines))
asc = '\n'.join([colors[i].to_ansi() + l + color('&r') for i, l in enumerate(lines)])
else:
asc = '\n'.join(preset.color_text(line) + color('&r') for line in lines)
else:
preset = preset.unique_colors()
# Apply colors
new = []
start_color = None
color_map = {ai: preset.colors[pi].to_ansi() for ai, pi in self.custom_colors.items()}
for line in asc.split('\n'):
# Line has color placeholders
if len(RE_NEOFETCH_COLOR.findall(line)) > 0:
# Get the last placeholder for the next line
last = int(RE_NEOFETCH_COLOR.findall(line)[-1][3])
# Replace placeholders
for ascii_i, c in color_map.items():
line = line.replace(f'${{c{ascii_i}}}', c)
# Add to new ascii
new.append(f'{start_color or ""}{line}')
# Change next start color
start_color = color_map[last]
else:
new.append(f'{start_color or ""}{line}')
asc = '\n'.join(new)
return asc
def get_command_path() -> str: def get_command_path() -> str:
""" """
Get the absolute path of the neofetch command Get the absolute path of the neofetch command
@ -24,39 +101,25 @@ def get_command_path() -> str:
return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh') return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh')
def get_distro_ascii() -> str: def get_distro_ascii(distro: str | None = None) -> str:
""" """
Get the distro ascii Get the distro ascii of the current distro. Or if distro is specified, get the specific distro's
ascii art instead.
:return: Distro ascii :return: Distro ascii
""" """
return check_output([get_command_path(), "print_ascii"]).decode().strip() cmd = 'print_ascii'
if distro:
os.environ['CUSTOM_DISTRO'] = distro
cmd = 'print_custom_ascii'
return normalize_ascii(check_output([get_command_path(), cmd]).decode().strip())
def get_custom_distro_ascii(distro: str) -> str: def run_neofetch(preset: ColorProfile, alignment: ColorAlignment):
""" asc = get_distro_ascii()
Get the distro ascii of a specific distro w, h = ascii_size(asc)
asc = alignment.recolor_ascii(asc, preset)
:return: Distro ascii
"""
os.environ['CUSTOM_DISTRO'] = distro
return check_output([get_command_path(), "print_custom_ascii"]).decode().strip()
def replace_colors(asc: str, preset: ColorProfile, mode: AnsiMode = COLOR_MODE):
# Remove existing colors
asc = re.sub('\\${.*?}', '', asc)
# Add new colors
lines = asc.split('\n')
colors = preset.with_length(len(lines))
asc = '\n'.join([colors[i].to_ansi(mode) + l for i, l in enumerate(lines)])
return asc, lines
def run_neofetch(preset: ColorProfile, mode: AnsiMode):
asc, lines = replace_colors(get_distro_ascii(), preset, mode)
# Write temp file # Write temp file
with TemporaryDirectory() as tmp_dir: with TemporaryDirectory() as tmp_dir:
@ -65,8 +128,8 @@ def run_neofetch(preset: ColorProfile, mode: AnsiMode):
path.write_text(asc) path.write_text(asc)
# Call neofetch with the temp file # Call neofetch with the temp file
os.environ['ascii_len'] = str(max(len(l) for l in lines)) os.environ['ascii_len'] = str(w)
os.environ['ascii_lines'] = str(len(lines)) os.environ['ascii_lines'] = str(h)
if platform.system() != 'Windows': if platform.system() != 'Windows':
os.system(f'{get_command_path()} --ascii --source {path.absolute()} --ascii-colors') os.system(f'{get_command_path()} --ascii --source {path.absolute()} --ascii-colors')
@ -75,8 +138,7 @@ def run_neofetch(preset: ColorProfile, mode: AnsiMode):
cmd = get_command_path().replace("\\", "/").replace("C:/", "/c/") cmd = get_command_path().replace("\\", "/").replace("C:/", "/c/")
path_str = str(path.absolute()).replace('\\', '/').replace('C:/', '/c/') path_str = str(path.absolute()).replace('\\', '/').replace('C:/', '/c/')
cmd = f'ascii_len={max(len(l) for l in lines)} ascii_lines={len(lines)} ' \ cmd = f'ascii_len={w} ascii_lines={h} {cmd} --ascii --source {path_str} --ascii-colors'
f'{cmd} --ascii --source {path_str} --ascii-colors'
full_cmd = ['C:\\Program Files\\Git\\bin\\bash.exe', '-c', cmd] full_cmd = ['C:\\Program Files\\Git\\bin\\bash.exe', '-c', cmd]
# print(full_cmd) # print(full_cmd)

View file

@ -1,10 +1,21 @@
from __future__ import annotations from __future__ import annotations
from typing import Iterable
from typing_extensions import Literal from typing_extensions import Literal
from .color_util import RGB from .color_util import RGB
def remove_duplicates(seq: Iterable) -> list:
"""
Remove duplicate items from a sequence while preserving the order
"""
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
class ColorProfile: class ColorProfile:
raw: list[str] raw: list[str]
colors: list[RGB] colors: list[RGB]
@ -98,6 +109,12 @@ class ColorProfile:
""" """
return ColorProfile([c.set_light(light) for c in self.colors]) return ColorProfile([c.set_light(light) for c in self.colors])
def unique_colors(self) -> ColorProfile:
"""
Create another color profile with only the unique colors
"""
return ColorProfile(remove_duplicates(self.colors))
PRESETS: dict[str, ColorProfile] = { PRESETS: dict[str, ColorProfile] = {
'rainbow': ColorProfile([ 'rainbow': ColorProfile([