[+] Random custom coloring
This commit is contained in:
parent
7bd96e422f
commit
a1d687d3bd
3 changed files with 184 additions and 45 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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([
|
||||||
|
|
Loading…
Reference in a new issue