602 lines
18 KiB
Python
602 lines
18 KiB
Python
# pylint: disable=invalid-name
|
|
import os
|
|
from typing import (
|
|
TYPE_CHECKING, List, Dict, Any, Callable, Union, Mapping
|
|
)
|
|
|
|
from .i18n import translate
|
|
from .config import TERMINAL_TEMPLATE_DIR
|
|
from .plugin_loader import PluginLoader
|
|
|
|
if TYPE_CHECKING:
|
|
from typing_extensions import TypedDict, NotRequired
|
|
from .plugin_api import OomoxPlugin # noqa
|
|
from .theme_file import ThemeValueT
|
|
Option = TypedDict('Option', {
|
|
'value': Union[str, int],
|
|
'display_name': NotRequired[str],
|
|
'description': NotRequired[str],
|
|
}, total=False)
|
|
ThemeModelValue = TypedDict('ThemeModelValue', {
|
|
"key": str,
|
|
"type": str,
|
|
"fallback_key": NotRequired[str],
|
|
"fallback_value": NotRequired[Any],
|
|
"fallback_function": NotRequired[Callable[[Dict[str, Any]], Any]],
|
|
"display_name": NotRequired[str],
|
|
"min_value": NotRequired[Any],
|
|
"max_value": NotRequired[Any],
|
|
"options": NotRequired[List[Option]],
|
|
"value_filter": NotRequired[Dict[str, list[ThemeValueT] | ThemeValueT]],
|
|
"filter": NotRequired[Callable[[Dict[str, Any]], bool]],
|
|
"reload_theme": NotRequired[bool],
|
|
"reload_options": NotRequired[bool],
|
|
}, total=False)
|
|
ThemeModelSection = List[ThemeModelValue]
|
|
ThemeModel = Dict[str, ThemeModelSection]
|
|
|
|
|
|
def sorted_dict(_dict: dict) -> dict: # type: ignore[type-arg]
|
|
return dict(sorted(_dict.items(), key=lambda x: x))
|
|
|
|
|
|
def get_key_indexes(base_theme_model: 'List[ThemeModelValue]') -> 'Dict[str, int]':
|
|
return {
|
|
theme_value['key']: index
|
|
for index, theme_value in enumerate(base_theme_model)
|
|
if 'key' in theme_value
|
|
}
|
|
|
|
|
|
def merge_model_with_plugin(
|
|
theme_model_name: str,
|
|
theme_plugin: 'OomoxPlugin',
|
|
base_theme_model: 'List[ThemeModelValue]',
|
|
value_filter_key: 'str | None' = None
|
|
) -> 'ThemeModelSection':
|
|
result: 'ThemeModelSection' = []
|
|
plugin_theme_model = getattr(theme_plugin, "theme_model_"+theme_model_name, None)
|
|
if not plugin_theme_model:
|
|
return result
|
|
plugin_theme_model_keys = []
|
|
for theme_value in plugin_theme_model:
|
|
if isinstance(theme_value, str):
|
|
plugin_theme_model_keys.append(theme_value)
|
|
else:
|
|
result.append(theme_value)
|
|
if not value_filter_key:
|
|
return result
|
|
plugin_enabled_keys = getattr(
|
|
theme_plugin, "enabled_keys_"+theme_model_name, []
|
|
)
|
|
key_indexes = get_key_indexes(base_theme_model)
|
|
for base_theme_value in [
|
|
theme_option for theme_option in plugin_theme_model
|
|
if not isinstance(theme_option, str)
|
|
] + [
|
|
base_theme_model[key_indexes[key]]
|
|
for key in plugin_theme_model_keys + plugin_enabled_keys
|
|
if key in key_indexes
|
|
]:
|
|
value_filter = base_theme_value.setdefault('value_filter', {})
|
|
value_filter_theme_style = value_filter.setdefault(value_filter_key, [])
|
|
if not isinstance(value_filter_theme_style, list):
|
|
value_filter_theme_style = [value_filter_theme_style, ]
|
|
if theme_plugin.name in value_filter_theme_style:
|
|
continue
|
|
value_filter_theme_style.append(theme_plugin.name)
|
|
base_theme_value['value_filter'][value_filter_key] = value_filter_theme_style
|
|
return result
|
|
|
|
|
|
def merge_model_with_plugins(
|
|
theme_model_name: str,
|
|
plugins: 'Mapping[str, OomoxPlugin]',
|
|
base_theme_model: 'ThemeModelSection | None' = None,
|
|
value_filter_key: 'str | None' = None
|
|
) -> 'ThemeModelSection':
|
|
if base_theme_model is None:
|
|
base_theme_model = []
|
|
whole_theme_model = base_theme_model[::]
|
|
|
|
for theme_plugin in plugins.values():
|
|
plugin_theme_model = merge_model_with_plugin(
|
|
theme_model_name=theme_model_name,
|
|
theme_plugin=theme_plugin,
|
|
base_theme_model=base_theme_model,
|
|
value_filter_key=value_filter_key
|
|
)
|
|
whole_theme_model += plugin_theme_model
|
|
|
|
return whole_theme_model
|
|
|
|
|
|
def get_theme_model_uncached() -> 'ThemeModel':
|
|
# @TODO: refactor theme_model loader into class
|
|
|
|
def merge_theme_model_with_plugins(
|
|
theme_model_name: str,
|
|
base_theme_model: 'ThemeModelSection | None' = None
|
|
) -> 'ThemeModelSection':
|
|
return merge_model_with_plugins(
|
|
theme_model_name=theme_model_name,
|
|
base_theme_model=base_theme_model,
|
|
value_filter_key='THEME_STYLE',
|
|
plugins=PluginLoader.get_theme_plugins(),
|
|
)
|
|
|
|
THEME_MODEL: 'ThemeModel' = {}
|
|
|
|
THEME_MODEL['import'] = merge_model_with_plugins(
|
|
theme_model_name='import',
|
|
value_filter_key='FROM_PLUGIN',
|
|
plugins=PluginLoader.get_import_plugins(),
|
|
)
|
|
|
|
THEME_MODEL['base'] = [
|
|
{
|
|
'type': 'separator',
|
|
'display_name': translate('Theme Style'),
|
|
},
|
|
{
|
|
'key': 'THEME_STYLE',
|
|
'type': 'options',
|
|
'options': [
|
|
{
|
|
'value': theme_plugin.name,
|
|
'display_name': theme_plugin.display_name,
|
|
'description': theme_plugin.description,
|
|
}
|
|
for theme_plugin in sorted_dict(PluginLoader.get_theme_plugins()).values()
|
|
],
|
|
'fallback_value': 'oomox',
|
|
'display_name': translate('Style for UI elements'),
|
|
},
|
|
]
|
|
|
|
BASE_THEME_MODEL_GTK: 'ThemeModelSection' = [
|
|
{
|
|
'type': 'separator',
|
|
'display_name': translate('Theme Colors'),
|
|
},
|
|
{
|
|
'key': 'BG',
|
|
'type': 'color',
|
|
'display_name': translate('Background')
|
|
},
|
|
{
|
|
'key': 'FG',
|
|
'type': 'color',
|
|
'display_name': translate('Text')
|
|
},
|
|
{
|
|
'key': 'HDR_BG',
|
|
'fallback_key': 'MENU_BG',
|
|
'type': 'color',
|
|
'display_name': translate('Header Background')
|
|
},
|
|
{
|
|
'key': 'HDR_FG',
|
|
'fallback_key': 'MENU_FG',
|
|
'type': 'color',
|
|
'display_name': translate('Header Text'),
|
|
},
|
|
{
|
|
'key': 'SEL_BG',
|
|
'fallback_key': 'FG',
|
|
'type': 'color',
|
|
'display_name': translate('Selected Background')
|
|
},
|
|
{
|
|
'key': 'SEL_FG',
|
|
'fallback_key': 'BG',
|
|
'type': 'color',
|
|
'display_name': translate('Selected Text'),
|
|
},
|
|
{
|
|
'key': 'ACCENT_BG',
|
|
'fallback_key': 'SEL_BG',
|
|
'type': 'color',
|
|
'display_name': translate('Accent Color (Checkboxes, Radios)'),
|
|
},
|
|
{
|
|
'key': 'TXT_BG',
|
|
'fallback_key': 'BG',
|
|
'type': 'color',
|
|
'display_name': translate('Textbox Background')
|
|
},
|
|
{
|
|
'key': 'TXT_FG',
|
|
'fallback_key': 'FG',
|
|
'type': 'color',
|
|
'display_name': translate('Textbox Text'),
|
|
},
|
|
{
|
|
'key': 'BTN_BG',
|
|
'fallback_key': 'BG',
|
|
'type': 'color',
|
|
'display_name': translate('Button Background')
|
|
},
|
|
{
|
|
'key': 'BTN_FG',
|
|
'fallback_key': 'FG',
|
|
'type': 'color',
|
|
'display_name': translate('Button Text'),
|
|
},
|
|
{
|
|
'key': 'HDR_BTN_BG',
|
|
'fallback_key': 'BTN_BG',
|
|
'type': 'color',
|
|
'display_name': translate('Header Button Background'),
|
|
},
|
|
{
|
|
'key': 'HDR_BTN_FG',
|
|
'fallback_key': 'BTN_FG',
|
|
'type': 'color',
|
|
'display_name': translate('Header Button Text'),
|
|
},
|
|
{
|
|
'key': 'WM_BORDER_FOCUS',
|
|
'fallback_key': 'SEL_BG',
|
|
'type': 'color',
|
|
'display_name': translate('Focused Window Border'),
|
|
},
|
|
{
|
|
'key': 'WM_BORDER_UNFOCUS',
|
|
'fallback_key': 'HDR_BG',
|
|
'type': 'color',
|
|
'display_name': translate('Unfocused Window Border'),
|
|
},
|
|
# migration of old names:
|
|
{
|
|
'key': 'MENU_BG',
|
|
'fallback_key': 'BG',
|
|
'type': 'color',
|
|
'filter': lambda _v: False
|
|
},
|
|
{
|
|
'key': 'MENU_FG',
|
|
'fallback_key': 'FG',
|
|
'type': 'color',
|
|
'filter': lambda _v: False
|
|
},
|
|
]
|
|
THEME_MODEL['colors'] = merge_model_with_plugins(
|
|
theme_model_name='gtk',
|
|
base_theme_model=merge_theme_model_with_plugins('gtk', BASE_THEME_MODEL_GTK),
|
|
value_filter_key='FROM_PLUGIN',
|
|
plugins=PluginLoader.get_import_plugins(),
|
|
)
|
|
|
|
BASE_THEME_MODEL_OPTIONS: 'ThemeModelSection' = [
|
|
{
|
|
'type': 'separator',
|
|
'display_name': translate('Theme Options'),
|
|
},
|
|
{
|
|
'key': 'ROUNDNESS',
|
|
'type': 'int',
|
|
'fallback_value': 2,
|
|
'display_name': translate('Roundness'),
|
|
},
|
|
{
|
|
'key': 'GRADIENT',
|
|
'type': 'float',
|
|
'fallback_value': 0.0,
|
|
'max_value': 2.0,
|
|
'display_name': translate('Gradient'),
|
|
},
|
|
]
|
|
THEME_MODEL['theme_options'] = merge_theme_model_with_plugins('options', BASE_THEME_MODEL_OPTIONS)
|
|
|
|
BASE_ICON_THEME_MODEL: 'ThemeModelSection' = [
|
|
{
|
|
'type': 'separator',
|
|
'display_name': translate('Iconset')
|
|
},
|
|
{
|
|
'key': 'ICONS_STYLE',
|
|
'type': 'options',
|
|
'options': [
|
|
{
|
|
'value': icons_plugin.name,
|
|
'display_name': icons_plugin.display_name,
|
|
}
|
|
for icons_plugin in sorted_dict(PluginLoader.get_icons_plugins()).values()
|
|
],
|
|
'fallback_value': 'gnome_colors',
|
|
'display_name': translate('Icons Style')
|
|
},
|
|
]
|
|
THEME_MODEL['icons'] = merge_model_with_plugins(
|
|
base_theme_model=BASE_ICON_THEME_MODEL,
|
|
theme_model_name='icons',
|
|
value_filter_key='ICONS_STYLE',
|
|
plugins=PluginLoader.get_icons_plugins(),
|
|
)
|
|
|
|
THEME_MODEL['terminal'] = [
|
|
{
|
|
'type': 'separator',
|
|
'display_name': translate('Terminal')
|
|
},
|
|
{
|
|
'key': 'TERMINAL_THEME_MODE',
|
|
'type': 'options',
|
|
'options': [
|
|
{'value': 'auto', 'display_name': translate('Auto')},
|
|
{'value': 'basic', 'display_name': translate('Basic')},
|
|
{'value': 'smarty', 'display_name': translate('Advanced')},
|
|
{'value': 'manual', 'display_name': translate('Manual')},
|
|
],
|
|
'fallback_value': 'auto',
|
|
'display_name': translate('Theme Options')
|
|
},
|
|
{
|
|
'key': 'TERMINAL_BASE_TEMPLATE',
|
|
'type': 'options',
|
|
'options': [
|
|
{'value': template_name}
|
|
for template_name in sorted(os.listdir(TERMINAL_TEMPLATE_DIR))
|
|
],
|
|
'fallback_value': 'monovedek',
|
|
'display_name': translate('Theme Style'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['auto', 'basic', 'smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_BACKGROUND',
|
|
'type': 'color',
|
|
'fallback_key': 'TXT_BG',
|
|
# 'fallback_key': 'HDR_BG',
|
|
'display_name': translate('Background'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['basic', 'manual', 'smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_FOREGROUND',
|
|
'type': 'color',
|
|
'fallback_key': 'TXT_FG',
|
|
# 'fallback_key': 'HDR_FG',
|
|
'display_name': translate('Foreground'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['basic', 'manual', 'smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_CURSOR',
|
|
'type': 'color',
|
|
'fallback_key': 'TERMINAL_FOREGROUND',
|
|
'display_name': translate('Cursor'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['basic', 'manual', 'smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_ACCENT_COLOR',
|
|
'type': 'color',
|
|
'fallback_key': 'SEL_BG',
|
|
'display_name': translate('Accent Color'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['basic', ],
|
|
},
|
|
},
|
|
|
|
{
|
|
'key': 'TERMINAL_THEME_AUTO_BGFG',
|
|
'type': 'bool',
|
|
'fallback_value': True,
|
|
'display_name': translate('Auto-Swap BG/FG'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['auto', 'basic', 'smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_THEME_EXTEND_PALETTE',
|
|
'type': 'bool',
|
|
'fallback_value': False,
|
|
'display_name': translate('Extend Palette with More Lighter/Darker Colors'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['smarty', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_THEME_ACCURACY',
|
|
'type': 'int',
|
|
'fallback_value': 128,
|
|
'display_name': translate('Palette Generation Accuracy'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['smarty', ],
|
|
},
|
|
'min_value': 8,
|
|
'max_value': 255,
|
|
},
|
|
|
|
# Black
|
|
{
|
|
'key': 'TERMINAL_COLOR0',
|
|
'type': 'color',
|
|
'display_name': translate('Black'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR8',
|
|
'type': 'color',
|
|
'display_name': translate('Black Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Red
|
|
{
|
|
'key': 'TERMINAL_COLOR1',
|
|
'type': 'color',
|
|
'display_name': translate('Red'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR9',
|
|
'type': 'color',
|
|
'display_name': translate('Red Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Green
|
|
{
|
|
'key': 'TERMINAL_COLOR2',
|
|
'type': 'color',
|
|
'display_name': translate('Green'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR10',
|
|
'type': 'color',
|
|
'display_name': translate('Green Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Yellow
|
|
{
|
|
'key': 'TERMINAL_COLOR3',
|
|
'type': 'color',
|
|
'display_name': translate('Yellow'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR11',
|
|
'type': 'color',
|
|
'display_name': translate('Yellow Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Blue
|
|
{
|
|
'key': 'TERMINAL_COLOR4',
|
|
'type': 'color',
|
|
'display_name': translate('Blue'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR12',
|
|
'type': 'color',
|
|
'display_name': translate('Blue Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Purple
|
|
{
|
|
'key': 'TERMINAL_COLOR5',
|
|
'type': 'color',
|
|
'display_name': translate('Purple'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR13',
|
|
'type': 'color',
|
|
'display_name': translate('Purple Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# Cyan
|
|
{
|
|
'key': 'TERMINAL_COLOR6',
|
|
'type': 'color',
|
|
'display_name': translate('Cyan'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR14',
|
|
'type': 'color',
|
|
'display_name': translate('Cyan Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
# White
|
|
{
|
|
'key': 'TERMINAL_COLOR7',
|
|
'type': 'color',
|
|
'display_name': translate('White'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
{
|
|
'key': 'TERMINAL_COLOR15',
|
|
'type': 'color',
|
|
'display_name': translate('White Highlight'),
|
|
'value_filter': {
|
|
'TERMINAL_THEME_MODE': ['manual', ],
|
|
},
|
|
},
|
|
]
|
|
|
|
_theme_export_plugins = merge_theme_model_with_plugins('extra')
|
|
THEME_MODEL['export'] = merge_model_with_plugins(
|
|
base_theme_model=_theme_export_plugins,
|
|
theme_model_name='extra',
|
|
plugins=PluginLoader.get_export_plugins(),
|
|
)
|
|
|
|
return THEME_MODEL
|
|
|
|
|
|
class CachedThemeModel:
|
|
|
|
_theme_model = None
|
|
|
|
@classmethod
|
|
def get(cls) -> 'ThemeModel':
|
|
if not cls._theme_model:
|
|
cls._theme_model = get_theme_model_uncached()
|
|
return cls._theme_model
|
|
|
|
|
|
def get_theme_model() -> 'ThemeModel':
|
|
return CachedThemeModel.get()
|
|
|
|
|
|
def get_theme_options_by_key(
|
|
key: str,
|
|
fallback: 'ThemeModelValue | None' = None
|
|
) -> 'list[ThemeModelValue]':
|
|
result = []
|
|
for _section_id, section in get_theme_model().items():
|
|
for theme_option in section:
|
|
if key == theme_option.get('key'):
|
|
result.append(theme_option)
|
|
if not result and fallback:
|
|
return [fallback]
|
|
return result
|
|
|
|
|
|
def get_first_theme_option(
|
|
key: str,
|
|
fallback: 'ThemeModelValue | None' = None
|
|
) -> 'ThemeModelValue':
|
|
result = get_theme_options_by_key(key, fallback=fallback)
|
|
if result:
|
|
return result[0]
|
|
return {}
|