diff --git a/docs/settings.md b/docs/settings.md index f128b7b..9d0fcd8 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -50,3 +50,62 @@ sensitive = true visibility = "unlisted" scheduled_in = "30 minutes" ``` + +## TUI color palette + +TUI uses Urwid which provides several color modes. See +[Urwid documentation](https://urwid.org/manual/displayattributes.html) +for more details. + +By default, TUI operates in 16-color mode which can be changed by setting the +`color` setting in the `[tui]` section to one of the following values: + +* `1` (monochrome) +* `16` (default) +* `88` +* `256` +* `16777216` (24 bit) + +TUI defines a list of colors which can be customized, currently they can be seen +[in the source code](https://github.com/ihabunek/toot/blob/master/toot/tui/constants.py). They can be overriden in the `[tui.palette]` section. + +Each color is defined as a list of upto 5 values: + +* foreground color (16 color mode) +* background color (16 color mode) +* monochrome color (monochrome mode) +* foreground color (high-color mode) +* background color (high-color mode) + +Any colors which are not used by your desired color mode can be skipped or set +to an empty string. + +For example, to change the button colors in 16 color mode: + +```toml +[tui.palette] +button = ["dark red,bold", ""] +button_focused = ["light gray", "green"] +``` + +In monochrome mode: + +```toml +[tui] +colors = 1 + +[tui.palette] +button = ["", "", "bold"] +button_focused = ["", "", "italics"] +``` + +In 256 color mode: + +```toml +[tui] +colors = 256 + +[tui.palette] +button = ["", "", "", "#aaa", "#bbb"] +button_focused = ["", "", "", "#aaa", "#bbb"] +``` diff --git a/tests/test_constants.py b/tests/test_constants.py deleted file mode 100644 index 60a15cf..0000000 --- a/tests/test_constants.py +++ /dev/null @@ -1,15 +0,0 @@ -from toot.tui.constants import PALETTE, MONO_PALETTE - - -def test_palette(): - # for every entry in PALETTE, there must be - # a corresponding entry in MONO_PALETTE - for pal in PALETTE: - matches = [item for item in MONO_PALETTE if item[0] == pal[0]] - assert len(matches) > 0, f"{pal}, present in PALETTE, missing from MONO_PALETTE" - - # for every entry in MONO_PALETTE, there must be - # a corresponding entry in PALETTE - for pal in MONO_PALETTE: - matches = [item for item in PALETTE if item[0] == pal[0]] - assert len(matches) > 0, f"{pal}, present in MONO_PALETTE, missing from PALETTE" diff --git a/toot/tui/app.py b/toot/tui/app.py index cac8454..9d78b12 100644 --- a/toot/tui/app.py +++ b/toot/tui/app.py @@ -3,12 +3,12 @@ import urwid from concurrent.futures import ThreadPoolExecutor -from toot import api, config, __version__ +from toot import api, config, __version__, settings from toot.console import get_default_visibility from toot.exceptions import ApiError from .compose import StatusComposer -from .constants import PALETTE, MONO_PALETTE +from .constants import PALETTE from .entities import Status from .overlays import ExceptionStackTrace, GotoMenu, Help, StatusSource, StatusLinks, StatusZoom from .overlays import StatusDeleteConfirmation, Account @@ -78,19 +78,20 @@ class TUI(urwid.Frame): loop: urwid.MainLoop screen: urwid.BaseScreen - @classmethod - def create(cls, app, user, args): + @staticmethod + def create(app, user, args): """Factory method, sets up TUI and an event loop.""" - screen = urwid.raw_display.Screen() - tui = cls(app, user, screen, args) + screen = TUI.create_screen(args) + tui = TUI(app, user, screen, args) - if args.no_color: - screen.set_terminal_properties(1) - screen.reset_default_terminal_palette() + palette = PALETTE.copy() + overrides = settings.get_setting("tui.palette", dict, {}) + for name, styles in overrides.items(): + palette.append(tuple([name] + styles)) loop = urwid.MainLoop( tui, - palette=MONO_PALETTE if args.no_color else PALETTE, + palette=palette, event_loop=urwid.AsyncioEventLoop(), unhandled_input=tui.unhandled_input, screen=screen, @@ -99,6 +100,18 @@ class TUI(urwid.Frame): return tui + @staticmethod + def create_screen(args): + screen = urwid.raw_display.Screen() + + # Determine how many colors to use + default_colors = 1 if args.no_color else 16 + colors = settings.get_setting("tui.colors", int, default_colors) + logger.debug(f"Setting colors to {colors}") + screen.set_terminal_properties(colors) + + return screen + def __init__(self, app, user, screen, args): self.app = app self.user = user diff --git a/toot/tui/constants.py b/toot/tui/constants.py index e672947..91bb3b7 100644 --- a/toot/tui/constants.py +++ b/toot/tui/constants.py @@ -1,8 +1,19 @@ -# name, fg, bg, mono, fg_h, bg_h +# Color definitions are tuples of: +# - name +# - foreground (normal mode) +# - background (normal mode) +# - foreground (monochrome mode) +# - foreground (high color mode) +# - background (high color mode) +# +# See: +# http://urwid.org/tutorial/index.html#display-attributes +# http://urwid.org/manual/displayattributes.html#using-display-attributes + PALETTE = [ # Components ('button', 'white', 'black'), - ('button_focused', 'light gray', 'dark magenta'), + ('button_focused', 'light gray', 'dark magenta', 'bold,underline'), ('card_author', 'yellow', ''), ('card_title', 'dark green', ''), ('columns_divider', 'white', 'dark blue'), @@ -14,7 +25,7 @@ PALETTE = [ ('footer_status', 'white', 'dark blue'), ('footer_status_bold', 'white, bold', 'dark blue'), ('header', 'white', 'dark blue'), - ('header_bold', 'white,bold', 'dark blue'), + ('header_bold', 'white,bold', 'dark blue', 'bold'), ('intro_bigtext', 'yellow', ''), ('intro_smalltext', 'light blue', ''), ('poll_bar', 'white', 'dark blue'), @@ -22,16 +33,17 @@ PALETTE = [ ('status_detail_bookmarked', 'light red', ''), ('status_detail_timestamp', 'light blue', ''), ('status_list_account', 'dark green', ''), - ('status_list_selected', 'white,bold', 'dark green'), + ('status_list_selected', 'white,bold', 'dark green', 'bold,underline'), ('status_list_timestamp', 'light blue', ''), # Functional - ('hashtag', 'light cyan,bold', ''), - ('hashtag_followed', 'yellow,bold', ''), - ('link', ',italics', ''), - ('link_focused', ',italics', 'dark magenta'), + ('account', 'dark green', ''), + ('hashtag', 'light cyan,bold', '', 'bold'), + ('hashtag_followed', 'yellow,bold', '', 'bold'), + ('link', ',italics', '', ',italics'), + ('link_focused', ',italics', 'dark magenta', "underline,italics"), ('shortcut', 'light blue', ''), - ('shortcut_highlight', 'white,bold', ''), + ('shortcut_highlight', 'white,bold', '', 'bold'), ('warning', 'light red', ''), # Visiblity @@ -47,54 +59,6 @@ PALETTE = [ ('success', 'dark green', ''), ] -MONO_PALETTE = [ - # Components - ('button', 'white', 'black'), - ('button_focused', 'black', 'white'), - ('card_author', 'white', ''), - ('card_title', 'white, bold', ''), - ('columns_divider', 'white', 'black'), - ('content_warning', 'white', 'black'), - ('editbox', 'white', 'black'), - ('editbox_focused', 'black', 'white'), - ('footer_message', 'white', 'black'), - ('footer_message_error', 'white,bold', 'black'), - ('footer_status', 'black', 'white'), - ('footer_status_bold', 'black,bold', 'white'), - ('header', 'black', 'white'), - ('header_bold', 'black,bold', 'white'), - ('intro_bigtext', 'white', 'black'), - ('intro_smalltext', 'white', 'black'), - ('poll_bar', 'black', 'white'), - ('status_detail_account', 'white', ''), - ('status_detail_bookmarked', 'white', ''), - ('status_detail_timestamp', 'white', ''), - ('status_list_account', 'white', ''), - ('status_list_selected', 'white,bold', ''), - ('status_list_timestamp', 'white', ''), - ('warning', 'white,bold', 'black'), - - # Functional - ('hashtag_followed', 'white,bold', ''), - ('hashtag', 'white,bold', ''), - ('link', ',italics', ''), - ('link_focused', ',bold,italics', ''), - ('shortcut', 'white', ''), - ('shortcut_highlight', 'white,bold', ''), - - # Visiblity - ('visibility_public', 'white', ''), - ('visibility_unlisted', 'white', ''), - ('visibility_private', 'white', ''), - ('visibility_direct', 'white', ''), - - # Styles - ('bold', ',bold', ''), - ('dim', 'light gray', ''), - ('highlight', ',bold', ''), - ('success', '', ''), -] - VISIBILITY_OPTIONS = [ ("public", "Public", "Post to public timelines"), ("unlisted", "Unlisted", "Do not post to public timelines"), diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 8c46a00..13b867f 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -326,9 +326,9 @@ class StatusDetails(urwid.Pile): yield ("pack", urwid.AttrMap(urwid.Divider("-"), "dim")) if status.author.display_name: - yield ("pack", urwid.Text(("status_detail_author", status.author.display_name))) + yield ("pack", urwid.Text(("bold", status.author.display_name))) - account_color = "highlight" if status.author.account in self.followed_accounts else "dim" + account_color = "highlight" if status.author.account in self.followed_accounts else "account" yield ("pack", urwid.Text((account_color, status.author.account))) yield ("pack", urwid.Divider())