From 0f617dd8c417d62668f90cdbffd4eddc040707de Mon Sep 17 00:00:00 2001 From: Isaiah Odhner Date: Fri, 15 Sep 2023 21:11:40 -0400 Subject: [PATCH] Dynamically theme message box icons --- CHANGELOG.md | 1 + src/textual_paint/icons.py | 109 +++-- tests/__snapshots__/test_snapshots.ambr | 550 ++++++++++++------------ 3 files changed, 354 insertions(+), 306 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4194ac0..0006b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Made radio buttons rounder in `--ascii-only` mode, using parentheses instead of square brackets. +- Improved the appearance of the warning icon and question icon in `--ascii-only` mode with the dark theme, and made it update when toggling dark mode with Ctrl+D. (The question icon is used only when pasting content larger than the canvas.) - Split up code files (especially the huge `paint.py`) into lots of smaller modules, and refactored a bunch of things. ### Added diff --git a/src/textual_paint/icons.py b/src/textual_paint/icons.py index 31ebba7..e15a412 100644 --- a/src/textual_paint/icons.py +++ b/src/textual_paint/icons.py @@ -16,15 +16,76 @@ Two nice things about embedding it are: 2. it's easier to dynamically modify them to remove the background color. TODO: unify formats/authoring workflow? -TODO: dynamic dark mode (I already have alternate versions of some icons) """ +from rich.console import RenderableType +from rich.protocol import is_renderable from rich.text import Text +from textual.errors import RenderError from textual.widgets import Static from textual_paint.args import args from textual_paint.localization.i18n import get as _ + +def _check_renderable(renderable: object): + """Check if a renderable conforms to the Rich Console protocol + (https://rich.readthedocs.io/en/latest/protocol.html) + + Args: + renderable: A potentially renderable object. + + Raises: + RenderError: If the object can not be rendered. + """ + if not is_renderable(renderable): + raise RenderError( + f"unable to render {renderable!r}; a string, Text, or other Rich renderable is required" + ) + +class ThemedIcon(Static): + """A Static widget that changes its content based on the theme. + + Args: + light_renderable: A Rich renderable, or string containing console markup, for the light theme. + dark_renderable: A Rich renderable, or string containing console markup, for the dark theme. + name: Name of widget. + id: ID of Widget. + classes: Space separated list of class names. + disabled: Whether the static is disabled or not. + """ + + def __init__( + self, + light_renderable: RenderableType, + dark_renderable: RenderableType, + *, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ): + """Initialize the icon.""" + super().__init__("", + name=name, + id=id, + classes=classes, + disabled=disabled, + ) + self.light_renderable = light_renderable + self.dark_renderable = dark_renderable + _check_renderable(light_renderable) + _check_renderable(dark_renderable) + self.watch(self.app, "dark", self._on_dark_changed, init=False) + self._on_dark_changed(False, self.app.dark) + + def _on_dark_changed(self, old_value: bool, dark: bool) -> None: + if dark: + self.update(self.dark_renderable) + else: + self.update(self.light_renderable) + + # ASCII line art version: # get_warning_icon = lambda: Static("""[#ffff00] # _ @@ -133,9 +194,10 @@ warning_icon_markup_ascii_dark_mode = """[#ffff00] [/]""" def get_warning_icon() -> Static: - markup = warning_icon_markup_ascii if args.ascii_only else warning_icon_markup_unicode - # TODO: Use warning_icon_markup_ascii_dark_mode for a less blocky looking outline in dark mode. - return Static(markup, classes="warning_icon message_box_icon") + if args.ascii_only: + return ThemedIcon(warning_icon_markup_ascii, warning_icon_markup_ascii_dark_mode, classes="warning_icon message_box_icon") + else: + return Static(warning_icon_markup_unicode, classes="warning_icon message_box_icon") # question_icon_ansi = "" @@ -156,32 +218,6 @@ question_icon_console_markup = """ """ # make background transparent question_icon_console_markup = question_icon_console_markup.replace(" on rgb(128,128,128)", "") -# class QuestionIcon(Static): -# """A question mark icon.""" -# -# def __init__(self) -> None: -# """Initialize the icon.""" -# super().__init__("", classes="question_icon message_box_icon") -# # This assertion fails. -# # > type(self.app) -# # .PaintApp'> -# # > type(PaintApp()) -# # -# # from paint import PaintApp -# # assert isinstance(self.app, PaintApp), "QuestionIcon should be used in PaintApp, but got: " + repr(self.app) -# self.watch(self.app, "dark", self._on_dark_changed, init=False) -# self._on_dark_changed(False, self.app.dark) -# -# def _on_dark_changed(self, old_value: bool, dark: bool) -> None: -# # tweak colors according to the theme -# if dark: -# # Never happens? -# self.update(question_icon_console_markup.replace("rgb(0,0,0)", "rgb(255,0,255)")) -# else: -# self.update(question_icon_console_markup.replace("rgb(0,0,0)", "rgb(128,128,128)")) -# -# def get_question_icon() -> QuestionIcon: -# return QuestionIcon() # also the shadow is normally gray, I just drew it black because I was using gray as the background @@ -201,9 +237,18 @@ question_icon_console_markup_ascii = question_icon_console_markup_ascii.replace( # bold question mark question_icon_console_markup_ascii = question_icon_console_markup_ascii.replace("?", "[b]?[/b]") +# swap white and black, and brighten blue to cyan +question_icon_console_markup_ascii_dark_mode = question_icon_console_markup_ascii.replace("rgb(0,0,0)", "rgb(255,0,255)").replace("rgb(255,255,255)", "rgb(0,0,0)").replace("rgb(255,0,255)", "rgb(255,255,255)").replace("rgb(0,0,255)", "rgb(0,255,255)") + def get_question_icon() -> Static: - markup = question_icon_console_markup_ascii if args.ascii_only else question_icon_console_markup - return Static(markup, classes="question_icon message_box_icon") + if args.ascii_only: + return ThemedIcon( + question_icon_console_markup_ascii, + question_icon_console_markup_ascii_dark_mode, + classes="question_icon message_box_icon", + ) + else: + return Static(question_icon_console_markup, classes="question_icon message_box_icon") paint_icon_console_markup = """ diff --git a/tests/__snapshots__/test_snapshots.ambr b/tests/__snapshots__/test_snapshots.ambr index 61f69f6..a293169 100644 --- a/tests/__snapshots__/test_snapshots.ambr +++ b/tests/__snapshots__/test_snapshots.ambr @@ -4945,154 +4945,155 @@ font-weight: 700; } - .terminal-1235291690-matrix { + .terminal-1815055849-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1235291690-title { + .terminal-1815055849-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1235291690-r1 { fill: #c5c8c6 } - .terminal-1235291690-r2 { fill: #608ab1;font-weight: bold } - .terminal-1235291690-r3 { fill: #cc555a;font-weight: bold } - .terminal-1235291690-r4 { fill: #d0b344;font-weight: bold } - .terminal-1235291690-r5 { fill: #0d0d0d } - .terminal-1235291690-r6 { fill: #dfdfdf } - .terminal-1235291690-r7 { fill: #000000 } - .terminal-1235291690-r8 { fill: #e3e3e3 } - .terminal-1235291690-r9 { fill: #454a50 } - .terminal-1235291690-r10 { fill: #e2e3e3 } - .terminal-1235291690-r11 { fill: #000000;font-weight: bold } - .terminal-1235291690-r12 { fill: #24292f;font-weight: bold } - .terminal-1235291690-r13 { fill: #e5e5e5 } - .terminal-1235291690-r14 { fill: #ff00ff;text-decoration: underline; } - .terminal-1235291690-r15 { fill: #ff00ff } - .terminal-1235291690-r16 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } - .terminal-1235291690-r17 { fill: #cc555a;font-style: italic;;text-decoration: underline; } - .terminal-1235291690-r18 { fill: #0080ff } - .terminal-1235291690-r19 { fill: #00050f } - .terminal-1235291690-r20 { fill: #e9e9ea } - .terminal-1235291690-r21 { fill: #808040 } - .terminal-1235291690-r22 { fill: #121212 } - .terminal-1235291690-r23 { fill: #ffffff } - .terminal-1235291690-r24 { fill: #565c62 } - .terminal-1235291690-r25 { fill: #24292f } + .terminal-1815055849-r1 { fill: #c5c8c6 } + .terminal-1815055849-r2 { fill: #608ab1;font-weight: bold } + .terminal-1815055849-r3 { fill: #cc555a;font-weight: bold } + .terminal-1815055849-r4 { fill: #d0b344;font-weight: bold } + .terminal-1815055849-r5 { fill: #0d0d0d } + .terminal-1815055849-r6 { fill: #ffff00 } + .terminal-1815055849-r7 { fill: #e3e3e3 } + .terminal-1815055849-r8 { fill: #454a50 } + .terminal-1815055849-r9 { fill: #e2e3e3 } + .terminal-1815055849-r10 { fill: #ffff00;font-weight: bold } + .terminal-1815055849-r11 { fill: #24292f;font-weight: bold } + .terminal-1815055849-r12 { fill: #000000 } + .terminal-1815055849-r13 { fill: #dfdfdf } + .terminal-1815055849-r14 { fill: #e5e5e5 } + .terminal-1815055849-r15 { fill: #ff00ff;text-decoration: underline; } + .terminal-1815055849-r16 { fill: #ff00ff } + .terminal-1815055849-r17 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } + .terminal-1815055849-r18 { fill: #cc555a;font-style: italic;;text-decoration: underline; } + .terminal-1815055849-r19 { fill: #0080ff } + .terminal-1815055849-r20 { fill: #00050f } + .terminal-1815055849-r21 { fill: #e9e9ea } + .terminal-1815055849-r22 { fill: #808040 } + .terminal-1815055849-r23 { fill: #121212 } + .terminal-1815055849-r24 { fill: #ffffff } + .terminal-1815055849-r25 { fill: #565c62 } + .terminal-1815055849-r26 { fill: #24292f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Paint + Paint - - - - \|// \ - /   \ - FileEditV/  !  \Hide Details - (_______)______________ - Exception: Error Message Itself  - '::.::Test - ____________ - - /7H? - ____________ - -  P,O - ____________ - - c==-E)= - - [] - []_______________ - [] - [_______]_______________ - - - For Help, cli + + + + \|/   / \ +  /   \ + FileEditV/  !  \Hide Details + (_______)______________ + Exception: Error Message Itself  + '::.::Test + ____________ + + /7H? + ____________ + +  P,O + ____________ + + c==-E)= + + [] + []_______________ + [] + [_______]_______________ + + + For Help, cli @@ -5643,217 +5644,217 @@ font-weight: 700; } - .terminal-3385497248-matrix { + .terminal-889104040-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3385497248-title { + .terminal-889104040-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3385497248-r1 { fill: #c5c8c6 } - .terminal-3385497248-r2 { fill: #608ab1;font-weight: bold } - .terminal-3385497248-r3 { fill: #cc555a;font-weight: bold } - .terminal-3385497248-r4 { fill: #d0b344;font-weight: bold } - .terminal-3385497248-r5 { fill: #e3e3e3 } - .terminal-3385497248-r6 { fill: #989898 } - .terminal-3385497248-r7 { fill: #e2e3e3 } - .terminal-3385497248-r8 { fill: #454a50 } - .terminal-3385497248-r9 { fill: #e5e5e5 } - .terminal-3385497248-r10 { fill: #000000 } - .terminal-3385497248-r11 { fill: #ff00ff;text-decoration: underline; } - .terminal-3385497248-r12 { fill: #ff00ff } - .terminal-3385497248-r13 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } - .terminal-3385497248-r14 { fill: #cc555a;font-style: italic;;text-decoration: underline; } - .terminal-3385497248-r15 { fill: #0080ff } - .terminal-3385497248-r16 { fill: #0d0d0d } - .terminal-3385497248-r17 { fill: #00050f } - .terminal-3385497248-r18 { fill: #24292f } - .terminal-3385497248-r19 { fill: #e9e9ea } - .terminal-3385497248-r20 { fill: #808040 } - .terminal-3385497248-r21 { fill: #ff0000;font-weight: bold } - .terminal-3385497248-r22 { fill: #ffff00;font-weight: bold } - .terminal-3385497248-r23 { fill: #ddedf9;font-weight: bold } - .terminal-3385497248-r24 { fill: #e2e3e3;font-weight: bold } - .terminal-3385497248-r25 { fill: #dfdfdf } - .terminal-3385497248-r26 { fill: #0000ff;font-weight: bold } - .terminal-3385497248-r27 { fill: #507bb3 } - .terminal-3385497248-r28 { fill: #004578;font-weight: bold } - .terminal-3385497248-r29 { fill: #001541 } - .terminal-3385497248-r30 { fill: #121212 } - .terminal-3385497248-r31 { fill: #ffffff } - .terminal-3385497248-r32 { fill: #565c62 } + .terminal-889104040-r1 { fill: #c5c8c6 } + .terminal-889104040-r2 { fill: #608ab1;font-weight: bold } + .terminal-889104040-r3 { fill: #cc555a;font-weight: bold } + .terminal-889104040-r4 { fill: #d0b344;font-weight: bold } + .terminal-889104040-r5 { fill: #e3e3e3 } + .terminal-889104040-r6 { fill: #989898 } + .terminal-889104040-r7 { fill: #e2e3e3 } + .terminal-889104040-r8 { fill: #454a50 } + .terminal-889104040-r9 { fill: #e5e5e5 } + .terminal-889104040-r10 { fill: #000000 } + .terminal-889104040-r11 { fill: #ff00ff;text-decoration: underline; } + .terminal-889104040-r12 { fill: #ff00ff } + .terminal-889104040-r13 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } + .terminal-889104040-r14 { fill: #cc555a;font-style: italic;;text-decoration: underline; } + .terminal-889104040-r15 { fill: #0080ff } + .terminal-889104040-r16 { fill: #0d0d0d } + .terminal-889104040-r17 { fill: #00050f } + .terminal-889104040-r18 { fill: #24292f } + .terminal-889104040-r19 { fill: #e9e9ea } + .terminal-889104040-r20 { fill: #808040 } + .terminal-889104040-r21 { fill: #ff0000;font-weight: bold } + .terminal-889104040-r22 { fill: #ffff00;font-weight: bold } + .terminal-889104040-r23 { fill: #ddedf9;font-weight: bold } + .terminal-889104040-r24 { fill: #e2e3e3;font-weight: bold } + .terminal-889104040-r25 { fill: #ffffff } + .terminal-889104040-r26 { fill: #dfdfdf } + .terminal-889104040-r27 { fill: #00ffff;font-weight: bold } + .terminal-889104040-r28 { fill: #507bb3 } + .terminal-889104040-r29 { fill: #004578;font-weight: bold } + .terminal-889104040-r30 { fill: #001541 } + .terminal-889104040-r31 { fill: #121212 } + .terminal-889104040-r32 { fill: #565c62 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Paint + Paint - - - - \|/Paint — Untitled - - FileEditViewImageColorsHelp - - - '::.:: - ____________ - - /7H? - ____________ - -  P,O - ____________ - - c==-E)=::mm PaintX - ____________ - - H<)A  _____  The image in the clipboard is  - ____________/     \larger than the bitmap. - |   ?   |Would you like the bitmap enlarged? - \S\__ __/ - ____________\| - YesNoCancel - [_]L______________________________ - ____________ - - O{_} - ____________ - - - [] - []________________________________________________________ - [] - [_______]________________________________________________________ - - - For Help, click Help Topics on the Help  + + + + \|/Paint — Untitled + + FileEditViewImageColorsHelp + + + '::.:: + ____________ + + /7H? + ____________ + +  P,O + ____________ + + c==-E)=::mm PaintX + ____________ + + H<)A  _____  The image in the clipboard is  + ____________/     \larger than the bitmap. + |   ?   |Would you like the bitmap enlarged? + \S\__ __/ + ____________\| + YesNoCancel + [_]L______________________________ + ____________ + + O{_} + ____________ + + + [] + []________________________________________________________ + [] + [_______]________________________________________________________ + + + For Help, click Help Topics on the Help  @@ -8979,159 +8980,160 @@ font-weight: 700; } - .terminal-3933108387-matrix { + .terminal-4141445308-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3933108387-title { + .terminal-4141445308-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3933108387-r1 { fill: #c5c8c6 } - .terminal-3933108387-r2 { fill: #608ab1;font-weight: bold } - .terminal-3933108387-r3 { fill: #cc555a;font-weight: bold } - .terminal-3933108387-r4 { fill: #d0b344;font-weight: bold } - .terminal-3933108387-r5 { fill: #e3e3e3 } - .terminal-3933108387-r6 { fill: #989898 } - .terminal-3933108387-r7 { fill: #e2e3e3 } - .terminal-3933108387-r8 { fill: #454a50 } - .terminal-3933108387-r9 { fill: #e5e5e5 } - .terminal-3933108387-r10 { fill: #ffffff } - .terminal-3933108387-r11 { fill: #000000 } - .terminal-3933108387-r12 { fill: #0d0d0d } - .terminal-3933108387-r13 { fill: #24292f } - .terminal-3933108387-r14 { fill: #ff00ff;text-decoration: underline; } - .terminal-3933108387-r15 { fill: #ff00ff } - .terminal-3933108387-r16 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } - .terminal-3933108387-r17 { fill: #ddedf9;font-weight: bold } - .terminal-3933108387-r18 { fill: #e2e3e3;font-weight: bold } - .terminal-3933108387-r19 { fill: #cc555a;font-style: italic;;text-decoration: underline; } - .terminal-3933108387-r20 { fill: #0080ff } - .terminal-3933108387-r21 { fill: #dfdfdf } - .terminal-3933108387-r22 { fill: #00050f } - .terminal-3933108387-r23 { fill: #507bb3 } - .terminal-3933108387-r24 { fill: #e9e9ea } - .terminal-3933108387-r25 { fill: #808040 } - .terminal-3933108387-r26 { fill: #000000;font-weight: bold } - .terminal-3933108387-r27 { fill: #004578;font-weight: bold } - .terminal-3933108387-r28 { fill: #001541 } - .terminal-3933108387-r29 { fill: #121212 } - .terminal-3933108387-r30 { fill: #565c62 } + .terminal-4141445308-r1 { fill: #c5c8c6 } + .terminal-4141445308-r2 { fill: #608ab1;font-weight: bold } + .terminal-4141445308-r3 { fill: #cc555a;font-weight: bold } + .terminal-4141445308-r4 { fill: #d0b344;font-weight: bold } + .terminal-4141445308-r5 { fill: #e3e3e3 } + .terminal-4141445308-r6 { fill: #989898 } + .terminal-4141445308-r7 { fill: #e2e3e3 } + .terminal-4141445308-r8 { fill: #454a50 } + .terminal-4141445308-r9 { fill: #e5e5e5 } + .terminal-4141445308-r10 { fill: #ffffff } + .terminal-4141445308-r11 { fill: #000000 } + .terminal-4141445308-r12 { fill: #0d0d0d } + .terminal-4141445308-r13 { fill: #24292f } + .terminal-4141445308-r14 { fill: #ff00ff;text-decoration: underline; } + .terminal-4141445308-r15 { fill: #ff00ff } + .terminal-4141445308-r16 { fill: #e2e3e3;font-style: italic;;text-decoration: underline; } + .terminal-4141445308-r17 { fill: #ddedf9;font-weight: bold } + .terminal-4141445308-r18 { fill: #e2e3e3;font-weight: bold } + .terminal-4141445308-r19 { fill: #cc555a;font-style: italic;;text-decoration: underline; } + .terminal-4141445308-r20 { fill: #0080ff } + .terminal-4141445308-r21 { fill: #ffff00 } + .terminal-4141445308-r22 { fill: #dfdfdf } + .terminal-4141445308-r23 { fill: #00050f } + .terminal-4141445308-r24 { fill: #507bb3 } + .terminal-4141445308-r25 { fill: #e9e9ea } + .terminal-4141445308-r26 { fill: #808040 } + .terminal-4141445308-r27 { fill: #ffff00;font-weight: bold } + .terminal-4141445308-r28 { fill: #004578;font-weight: bold } + .terminal-4141445308-r29 { fill: #001541 } + .terminal-4141445308-r30 { fill: #121212 } + .terminal-4141445308-r31 { fill: #565c62 } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - Paint + Paint - - - - \|/Paint — Untitled - - FileEditViewImageColorsHelp - - - '::.:: - ____________ - - /7H?PaintX - ____________ - -  P,O_Save changes to Untitled? - ____________/ \ - /   \ - c==-E)=/  !  \YesNoCancel - (_______)______________________________ - [] - []_______________ - [] - [_______]________________________________________________________ - - - For Help, click Help Topics on the Help + + + + \|/Paint — Untitled + + FileEditViewImageColorsHelp + + + '::.:: + ____________ + + /7H?PaintX + ____________ + +  P,O    _Save changes to Untitled? + ____________   / \ +   /   \ + c==-E)= /  !  \YesNoCancel + (_______)______________________________ + [] + []_______________ + [] + [_______]________________________________________________________ + + + For Help, click Help Topics on the Help