• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""UI Color Styles for ConsoleApp."""
15
16import logging
17from dataclasses import dataclass
18
19from prompt_toolkit.formatted_text import StyleAndTextTuples
20from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
21from prompt_toolkit.styles import Style
22from prompt_toolkit.filters import has_focus
23
24_LOG = logging.getLogger(__package__)
25
26
27@dataclass
28class HighContrastDarkColors:
29    # pylint: disable=too-many-instance-attributes
30    default_bg = '#100f10'
31    default_fg = '#ffffff'
32
33    dim_bg = '#000000'
34    dim_fg = '#e0e6f0'
35
36    button_active_bg = '#4e4e4e'
37    button_inactive_bg = '#323232'
38
39    active_bg = '#323232'
40    active_fg = '#f4f4f4'
41
42    inactive_bg = '#1e1e1e'
43    inactive_fg = '#bfc0c4'
44
45    line_highlight_bg = '#2f2f2f'
46    selected_line_bg = '#4e4e4e'
47    dialog_bg = '#3c3c3c'
48
49    red_accent = '#ffc0bf'
50    orange_accent = '#f5ca80'
51    yellow_accent = '#eedc82'
52    green_accent = '#88ef88'
53    cyan_accent = '#60e7e0'
54    blue_accent = '#92d9ff'
55    purple_accent = '#cfcaff'
56    magenta_accent = '#ffb8ff'
57
58
59@dataclass
60class DarkColors:
61    # pylint: disable=too-many-instance-attributes
62    default_bg = '#2e2e2e'
63    default_fg = '#eeeeee'
64
65    dim_bg = '#262626'
66    dim_fg = '#dfdfdf'
67
68    button_active_bg = '#626262'
69    button_inactive_bg = '#525252'
70
71    active_bg = '#525252'
72    active_fg = '#dfdfdf'
73
74    inactive_bg = '#3f3f3f'
75    inactive_fg = '#bfbfbf'
76
77    line_highlight_bg = '#525252'
78    selected_line_bg = '#626262'
79    dialog_bg = '#3c3c3c'
80
81    red_accent = '#ff6c6b'
82    orange_accent = '#da8548'
83    yellow_accent = '#ffcc66'
84    green_accent = '#98be65'
85    cyan_accent = '#66cccc'
86    blue_accent = '#6699cc'
87    purple_accent = '#a9a1e1'
88    magenta_accent = '#c678dd'
89
90
91@dataclass
92class NordColors:
93    # pylint: disable=too-many-instance-attributes
94    default_bg = '#2e3440'
95    default_fg = '#eceff4'
96
97    dim_bg = '#272c36'
98    dim_fg = '#e5e9f0'
99
100    button_active_bg = '#4c566a'
101    button_inactive_bg = '#434c5e'
102
103    active_bg = '#434c5e'
104    active_fg = '#eceff4'
105
106    inactive_bg = '#373e4c'
107    inactive_fg = '#d8dee9'
108
109    line_highlight_bg = '#191c25'
110    selected_line_bg = '#4c566a'
111    dialog_bg = '#2c333f'
112
113    red_accent = '#bf616a'
114    orange_accent = '#d08770'
115    yellow_accent = '#ebcb8b'
116    green_accent = '#a3be8c'
117    cyan_accent = '#88c0d0'
118    blue_accent = '#81a1c1'
119    purple_accent = '#a9a1e1'
120    magenta_accent = '#b48ead'
121
122
123@dataclass
124class NordLightColors:
125    # pylint: disable=too-many-instance-attributes
126    default_bg = '#e5e9f0'
127    default_fg = '#3b4252'
128    dim_bg = '#d8dee9'
129    dim_fg = '#2e3440'
130    button_active_bg = '#aebacf'
131    button_inactive_bg = '#b8c5db'
132    active_bg = '#b8c5db'
133    active_fg = '#3b4252'
134    inactive_bg = '#c2d0e7'
135    inactive_fg = '#60728c'
136    line_highlight_bg = '#f0f4fc'
137    selected_line_bg = '#f0f4fc'
138    dialog_bg = '#d8dee9'
139
140    red_accent = '#99324b'
141    orange_accent = '#ac4426'
142    yellow_accent = '#9a7500'
143    green_accent = '#4f894c'
144    cyan_accent = '#398eac'
145    blue_accent = '#3b6ea8'
146    purple_accent = '#842879'
147    magenta_accent = '#97365b'
148
149
150@dataclass
151class MoonlightColors:
152    # pylint: disable=too-many-instance-attributes
153    default_bg = '#212337'
154    default_fg = '#c8d3f5'
155    dim_bg = '#191a2a'
156    dim_fg = '#b4c2f0'
157    button_active_bg = '#444a73'
158    button_inactive_bg = '#2f334d'
159    active_bg = '#2f334d'
160    active_fg = '#c8d3f5'
161    inactive_bg = '#222436'
162    inactive_fg = '#a9b8e8'
163    line_highlight_bg = '#383e5c'
164    selected_line_bg = '#444a73'
165    dialog_bg = '#1e2030'
166
167    red_accent = '#d95468'
168    orange_accent = '#d98e48'
169    yellow_accent = '#ebbf83'
170    green_accent = '#8bd49c'
171    cyan_accent = '#70e1e8'
172    blue_accent = '#5ec4ff'
173    purple_accent = '#b62d65'
174    magenta_accent = '#e27e8d'
175
176
177@dataclass
178class AnsiTerm:
179    # pylint: disable=too-many-instance-attributes
180    default_bg = 'default'
181    default_fg = 'default'
182
183    dim_bg = 'default'
184    dim_fg = 'default'
185
186    button_active_bg = 'default underline'
187    button_inactive_bg = 'default'
188
189    active_bg = 'default'
190    active_fg = 'default'
191
192    inactive_bg = 'default'
193    inactive_fg = 'default'
194
195    line_highlight_bg = 'ansidarkgray white'
196    selected_line_bg = 'default reverse'
197    dialog_bg = 'default'
198
199    red_accent = 'ansired'
200    orange_accent = 'orange'
201    yellow_accent = 'ansiyellow'
202    green_accent = 'ansigreen'
203    cyan_accent = 'ansicyan'
204    blue_accent = 'ansiblue'
205    purple_accent = 'ansipurple'
206    magenta_accent = 'ansimagenta'
207
208
209_THEME_NAME_MAPPING = {
210    'moonlight': MoonlightColors(),
211    'nord': NordColors(),
212    'nord-light': NordLightColors(),
213    'dark': DarkColors(),
214    'high-contrast-dark': HighContrastDarkColors(),
215    'ansi': AnsiTerm(),
216}
217
218
219def get_theme_colors(theme_name=''):
220    theme = _THEME_NAME_MAPPING.get(theme_name, DarkColors())
221    return theme
222
223
224def generate_styles(theme_name='dark'):
225    """Return prompt_toolkit styles for the given theme name."""
226    # Use DarkColors() if name not found.
227    theme = _THEME_NAME_MAPPING.get(theme_name, DarkColors())
228
229    pw_console_styles = {
230        # Default text and background.
231        'default': 'bg:{} {}'.format(theme.default_bg, theme.default_fg),
232        # Dim inactive panes.
233        'pane_inactive': 'bg:{} {}'.format(theme.dim_bg, theme.dim_fg),
234        # Use default for active panes.
235        'pane_active': 'bg:{} {}'.format(theme.default_bg, theme.default_fg),
236        # Brighten active pane toolbars.
237        'toolbar_active': 'bg:{} {}'.format(theme.active_bg, theme.active_fg),
238        'toolbar_inactive': 'bg:{} {}'.format(
239            theme.inactive_bg, theme.inactive_fg
240        ),
241        # Dimmer toolbar.
242        'toolbar_dim_active': 'bg:{} {}'.format(
243            theme.active_bg, theme.active_fg
244        ),
245        'toolbar_dim_inactive': 'bg:{} {}'.format(
246            theme.default_bg, theme.inactive_fg
247        ),
248        # Used for pane titles
249        'toolbar_accent': theme.cyan_accent,
250        'toolbar-button-decoration': '{}'.format(theme.cyan_accent),
251        'toolbar-setting-active': 'bg:{} {}'.format(
252            theme.green_accent,
253            theme.active_bg,
254        ),
255        'toolbar-button-active': 'bg:{}'.format(theme.button_active_bg),
256        'toolbar-button-inactive': 'bg:{}'.format(theme.button_inactive_bg),
257        # prompt_toolkit scrollbar styles:
258        'scrollbar.background': 'bg:{} {}'.format(
259            theme.default_bg, theme.default_fg
260        ),
261        # Scrollbar handle, bg is the bar color.
262        'scrollbar.button': 'bg:{} {}'.format(
263            theme.purple_accent, theme.default_bg
264        ),
265        'scrollbar.arrow': 'bg:{} {}'.format(
266            theme.default_bg, theme.blue_accent
267        ),
268        # Unstyled scrollbar classes:
269        # 'scrollbar.start'
270        # 'scrollbar.end'
271        # Top menu bar styles
272        'menu-bar': 'bg:{} {}'.format(theme.inactive_bg, theme.inactive_fg),
273        'menu-bar.selected-item': 'bg:{} {}'.format(
274            theme.blue_accent, theme.inactive_bg
275        ),
276        # Menu background
277        'menu': 'bg:{} {}'.format(theme.dialog_bg, theme.dim_fg),
278        # Menu item separator
279        'menu-border': theme.magenta_accent,
280        # Top bar logo + keyboard shortcuts
281        'logo': '{} bold'.format(theme.magenta_accent),
282        'keybind': '{} bold'.format(theme.purple_accent),
283        'keyhelp': theme.dim_fg,
284        # Help window styles
285        'help_window_content': 'bg:{} {}'.format(theme.dialog_bg, theme.dim_fg),
286        'frame.border': 'bg:{} {}'.format(theme.dialog_bg, theme.purple_accent),
287        'pane_indicator_active': 'bg:{}'.format(theme.magenta_accent),
288        'pane_indicator_inactive': 'bg:{}'.format(theme.inactive_bg),
289        'pane_title_active': '{} bold'.format(theme.magenta_accent),
290        'pane_title_inactive': '{}'.format(theme.purple_accent),
291        'window-tab-active': 'bg:{} {}'.format(
292            theme.active_bg, theme.cyan_accent
293        ),
294        'window-tab-inactive': 'bg:{} {}'.format(
295            theme.inactive_bg, theme.inactive_fg
296        ),
297        'pane_separator': 'bg:{} {}'.format(
298            theme.default_bg, theme.purple_accent
299        ),
300        # Search matches
301        'search': 'bg:{} {}'.format(theme.cyan_accent, theme.default_bg),
302        'search.current': 'bg:{} {}'.format(
303            theme.cyan_accent, theme.default_bg
304        ),
305        # Highlighted line styles
306        'selected-log-line': 'bg:{}'.format(theme.line_highlight_bg),
307        'marked-log-line': 'bg:{}'.format(theme.selected_line_bg),
308        'cursor-line': 'bg:{} nounderline'.format(theme.line_highlight_bg),
309        # Messages like 'Window too small'
310        'warning-text': 'bg:{} {}'.format(
311            theme.default_bg, theme.yellow_accent
312        ),
313        'log-time': 'bg:{} {}'.format(theme.default_fg, theme.default_bg),
314        # Apply foreground only for level and column values. This way the text
315        # can inherit the background color of the parent window pane or line
316        # selection.
317        'log-level-{}'.format(logging.CRITICAL): '{} bold'.format(
318            theme.red_accent
319        ),
320        'log-level-{}'.format(logging.ERROR): '{}'.format(theme.red_accent),
321        'log-level-{}'.format(logging.WARNING): '{}'.format(
322            theme.yellow_accent
323        ),
324        'log-level-{}'.format(logging.INFO): '{}'.format(theme.purple_accent),
325        'log-level-{}'.format(logging.DEBUG): '{}'.format(theme.blue_accent),
326        'log-table-column-0': '{}'.format(theme.cyan_accent),
327        'log-table-column-1': '{}'.format(theme.green_accent),
328        'log-table-column-2': '{}'.format(theme.yellow_accent),
329        'log-table-column-3': '{}'.format(theme.magenta_accent),
330        'log-table-column-4': '{}'.format(theme.purple_accent),
331        'log-table-column-5': '{}'.format(theme.blue_accent),
332        'log-table-column-6': '{}'.format(theme.orange_accent),
333        'log-table-column-7': '{}'.format(theme.red_accent),
334        'search-bar': 'bg:{}'.format(theme.inactive_bg),
335        'search-bar-title': 'bg:{} {}'.format(
336            theme.cyan_accent, theme.default_bg
337        ),
338        'search-bar-setting': '{}'.format(theme.cyan_accent),
339        'search-bar-border': 'bg:{} {}'.format(
340            theme.inactive_bg, theme.cyan_accent
341        ),
342        'search-match-count-dialog': 'bg:{}'.format(theme.inactive_bg),
343        'search-match-count-dialog-title': '{}'.format(theme.cyan_accent),
344        'search-match-count-dialog-default-fg': '{}'.format(theme.default_fg),
345        'search-match-count-dialog-border': 'bg:{} {}'.format(
346            theme.inactive_bg, theme.cyan_accent
347        ),
348        'filter-bar': 'bg:{}'.format(theme.inactive_bg),
349        'filter-bar-title': 'bg:{} {}'.format(
350            theme.red_accent, theme.default_bg
351        ),
352        'filter-bar-setting': '{}'.format(theme.cyan_accent),
353        'filter-bar-delete': '{}'.format(theme.red_accent),
354        'filter-bar-delimiter': '{}'.format(theme.purple_accent),
355        'saveas-dialog': 'bg:{}'.format(theme.inactive_bg),
356        'saveas-dialog-title': 'bg:{} {}'.format(
357            theme.inactive_bg, theme.default_fg
358        ),
359        'saveas-dialog-setting': '{}'.format(theme.cyan_accent),
360        'saveas-dialog-border': 'bg:{} {}'.format(
361            theme.inactive_bg, theme.cyan_accent
362        ),
363        'selection-dialog': 'bg:{}'.format(theme.inactive_bg),
364        'selection-dialog-title': '{}'.format(theme.yellow_accent),
365        'selection-dialog-default-fg': '{}'.format(theme.default_fg),
366        'selection-dialog-action-bg': 'bg:{}'.format(theme.yellow_accent),
367        'selection-dialog-action-fg': '{}'.format(theme.button_inactive_bg),
368        'selection-dialog-border': 'bg:{} {}'.format(
369            theme.inactive_bg, theme.yellow_accent
370        ),
371        'quit-dialog': 'bg:{}'.format(theme.inactive_bg),
372        'quit-dialog-border': 'bg:{} {}'.format(
373            theme.inactive_bg, theme.red_accent
374        ),
375        'command-runner': 'bg:{}'.format(theme.inactive_bg),
376        'command-runner-title': 'bg:{} {}'.format(
377            theme.inactive_bg, theme.default_fg
378        ),
379        'command-runner-setting': '{}'.format(theme.purple_accent),
380        'command-runner-border': 'bg:{} {}'.format(
381            theme.inactive_bg, theme.purple_accent
382        ),
383        'command-runner-selected-item': 'bg:{}'.format(theme.selected_line_bg),
384        'command-runner-fuzzy-highlight-0': '{}'.format(theme.blue_accent),
385        'command-runner-fuzzy-highlight-1': '{}'.format(theme.cyan_accent),
386        'command-runner-fuzzy-highlight-2': '{}'.format(theme.green_accent),
387        'command-runner-fuzzy-highlight-3': '{}'.format(theme.yellow_accent),
388        'command-runner-fuzzy-highlight-4': '{}'.format(theme.orange_accent),
389        'command-runner-fuzzy-highlight-5': '{}'.format(theme.red_accent),
390        # Progress Bar Styles
391        # Entire set of ProgressBars - no title is used in pw_console
392        'title': '',
393        # Actual bar title
394        'label': 'bold',
395        'percentage': '{}'.format(theme.green_accent),
396        'bar': '{}'.format(theme.magenta_accent),
397        # Filled part of the bar
398        'bar-a': '{} bold'.format(theme.cyan_accent),
399        # End of current progress
400        'bar-b': '{} bold'.format(theme.purple_accent),
401        # Empty part of the bar
402        'bar-c': '',
403        # current/total counts
404        'current': '{}'.format(theme.cyan_accent),
405        'total': '{}'.format(theme.cyan_accent),
406        'time-elapsed': '{}'.format(theme.purple_accent),
407        'time-left': '{}'.format(theme.magenta_accent),
408        # Named theme color classes for use in user plugins.
409        'theme-fg-red': '{}'.format(theme.red_accent),
410        'theme-fg-orange': '{}'.format(theme.orange_accent),
411        'theme-fg-yellow': '{}'.format(theme.yellow_accent),
412        'theme-fg-green': '{}'.format(theme.green_accent),
413        'theme-fg-cyan': '{}'.format(theme.cyan_accent),
414        'theme-fg-blue': '{}'.format(theme.blue_accent),
415        'theme-fg-purple': '{}'.format(theme.purple_accent),
416        'theme-fg-magenta': '{}'.format(theme.magenta_accent),
417        'theme-bg-red': 'bg:{}'.format(theme.red_accent),
418        'theme-bg-orange': 'bg:{}'.format(theme.orange_accent),
419        'theme-bg-yellow': 'bg:{}'.format(theme.yellow_accent),
420        'theme-bg-green': 'bg:{}'.format(theme.green_accent),
421        'theme-bg-cyan': 'bg:{}'.format(theme.cyan_accent),
422        'theme-bg-blue': 'bg:{}'.format(theme.blue_accent),
423        'theme-bg-purple': 'bg:{}'.format(theme.purple_accent),
424        'theme-bg-magenta': 'bg:{}'.format(theme.magenta_accent),
425        'theme-bg-active': 'bg:{}'.format(theme.active_bg),
426        'theme-fg-active': '{}'.format(theme.active_fg),
427        'theme-bg-inactive': 'bg:{}'.format(theme.inactive_bg),
428        'theme-fg-inactive': '{}'.format(theme.inactive_fg),
429        'theme-fg-default': '{}'.format(theme.default_fg),
430        'theme-bg-default': 'bg:{}'.format(theme.default_bg),
431        'theme-fg-dim': '{}'.format(theme.dim_fg),
432        'theme-bg-dim': 'bg:{}'.format(theme.dim_bg),
433        'theme-bg-dialog': 'bg:{}'.format(theme.dialog_bg),
434        'theme-bg-line-highlight': 'bg:{}'.format(theme.line_highlight_bg),
435        'theme-bg-button-active': 'bg:{}'.format(theme.button_active_bg),
436        'theme-bg-button-inactive': 'bg:{}'.format(theme.button_inactive_bg),
437    }
438
439    return Style.from_dict(pw_console_styles)
440
441
442def get_toolbar_style(pt_container, dim=False) -> str:
443    """Return the style class for a toolbar if pt_container is in focus."""
444    if has_focus(pt_container.__pt_container__())():
445        return 'class:toolbar_dim_active' if dim else 'class:toolbar_active'
446    return 'class:toolbar_dim_inactive' if dim else 'class:toolbar_inactive'
447
448
449def get_button_style(pt_container) -> str:
450    """Return the style class for a toolbar if pt_container is in focus."""
451    if has_focus(pt_container.__pt_container__())():
452        return 'class:toolbar-button-active'
453    return 'class:toolbar-button-inactive'
454
455
456def get_pane_style(pt_container) -> str:
457    """Return the style class for a pane title if pt_container is in focus."""
458    if has_focus(pt_container.__pt_container__())():
459        return 'class:pane_active'
460    return 'class:pane_inactive'
461
462
463def get_pane_indicator(
464    pt_container, title, mouse_handler=None, hide_indicator=False
465) -> StyleAndTextTuples:
466    """Return formatted text for a pane indicator and title."""
467
468    inactive_indicator: OneStyleAndTextTuple
469    active_indicator: OneStyleAndTextTuple
470    inactive_title: OneStyleAndTextTuple
471    active_title: OneStyleAndTextTuple
472
473    if mouse_handler:
474        inactive_indicator = (
475            'class:pane_indicator_inactive',
476            ' ',
477            mouse_handler,
478        )
479        active_indicator = ('class:pane_indicator_active', ' ', mouse_handler)
480        inactive_title = ('class:pane_title_inactive', title, mouse_handler)
481        active_title = ('class:pane_title_active', title, mouse_handler)
482    else:
483        inactive_indicator = ('class:pane_indicator_inactive', ' ')
484        active_indicator = ('class:pane_indicator_active', ' ')
485        inactive_title = ('class:pane_title_inactive', title)
486        active_title = ('class:pane_title_active', title)
487
488    fragments: StyleAndTextTuples = []
489    if has_focus(pt_container.__pt_container__())():
490        if not hide_indicator:
491            fragments.append(active_indicator)
492        fragments.append(active_title)
493    else:
494        if not hide_indicator:
495            fragments.append(inactive_indicator)
496        fragments.append(inactive_title)
497    return fragments
498