• 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}  # yapf: disable
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
237        # Brighten active pane toolbars.
238        'toolbar_active': 'bg:{} {}'.format(theme.active_bg, theme.active_fg),
239        'toolbar_inactive': 'bg:{} {}'.format(theme.inactive_bg,
240                                              theme.inactive_fg),
241
242        # Dimmer toolbar.
243        'toolbar_dim_active': 'bg:{} {}'.format(theme.active_bg,
244                                                theme.active_fg),
245        'toolbar_dim_inactive': 'bg:{} {}'.format(theme.default_bg,
246                                                  theme.inactive_fg),
247        # Used for pane titles
248        'toolbar_accent': theme.cyan_accent,
249
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
258        # prompt_toolkit scrollbar styles:
259        'scrollbar.background': 'bg:{} {}'.format(theme.default_bg,
260                                                  theme.default_fg),
261        # Scrollbar handle, bg is the bar color.
262        'scrollbar.button': 'bg:{} {}'.format(theme.purple_accent,
263                                              theme.default_bg),
264        'scrollbar.arrow': 'bg:{} {}'.format(theme.default_bg,
265                                             theme.blue_accent),
266        # Unstyled scrollbar classes:
267        # 'scrollbar.start'
268        # 'scrollbar.end'
269
270        # Top menu bar styles
271        'menu-bar': 'bg:{} {}'.format(theme.inactive_bg, theme.inactive_fg),
272        'menu-bar.selected-item': 'bg:{} {}'.format(theme.blue_accent,
273                                                    theme.inactive_bg),
274        # Menu background
275        'menu': 'bg:{} {}'.format(theme.dialog_bg, theme.dim_fg),
276        # Menu item separator
277        'menu-border': theme.magenta_accent,
278
279        # Top bar logo + keyboard shortcuts
280        'logo':    '{} bold'.format(theme.magenta_accent),
281        'keybind': '{} bold'.format(theme.purple_accent),
282        'keyhelp': theme.dim_fg,
283
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
288        'pane_indicator_active': 'bg:{}'.format(theme.magenta_accent),
289        'pane_indicator_inactive': 'bg:{}'.format(theme.inactive_bg),
290
291        'pane_title_active': '{} bold'.format(theme.magenta_accent),
292        'pane_title_inactive': '{}'.format(theme.purple_accent),
293
294        'window-tab-active': 'bg:{} {}'.format(theme.active_bg,
295                                               theme.cyan_accent),
296        'window-tab-inactive': 'bg:{} {}'.format(theme.inactive_bg,
297                                                 theme.inactive_fg),
298
299        'pane_separator': 'bg:{} {}'.format(theme.default_bg,
300                                            theme.purple_accent),
301
302        # Search matches
303        'search': 'bg:{} {}'.format(theme.cyan_accent, theme.default_bg),
304        'search.current': 'bg:{} {}'.format(theme.cyan_accent,
305                                            theme.default_bg),
306
307        # Highlighted line styles
308        'selected-log-line': 'bg:{}'.format(theme.line_highlight_bg),
309        'marked-log-line': 'bg:{}'.format(theme.selected_line_bg),
310        'cursor-line': 'bg:{} nounderline'.format(theme.line_highlight_bg),
311
312        # Messages like 'Window too small'
313        'warning-text': 'bg:{} {}'.format(theme.default_bg,
314                                          theme.yellow_accent),
315
316        'log-time': 'bg:{} {}'.format(theme.default_fg,
317                                      theme.default_bg),
318
319        # Apply foreground only for level and column values. This way the text
320        # can inherit the background color of the parent window pane or line
321        # selection.
322        'log-level-{}'.format(logging.CRITICAL): '{} bold'.format(
323            theme.red_accent),
324        'log-level-{}'.format(logging.ERROR): '{}'.format(theme.red_accent),
325        'log-level-{}'.format(logging.WARNING): '{}'.format(
326            theme.yellow_accent),
327        'log-level-{}'.format(logging.INFO): '{}'.format(theme.purple_accent),
328        'log-level-{}'.format(logging.DEBUG): '{}'.format(theme.blue_accent),
329
330        'log-table-column-0': '{}'.format(theme.cyan_accent),
331        'log-table-column-1': '{}'.format(theme.green_accent),
332        'log-table-column-2': '{}'.format(theme.yellow_accent),
333        'log-table-column-3': '{}'.format(theme.magenta_accent),
334        'log-table-column-4': '{}'.format(theme.purple_accent),
335        'log-table-column-5': '{}'.format(theme.blue_accent),
336        'log-table-column-6': '{}'.format(theme.orange_accent),
337        'log-table-column-7': '{}'.format(theme.red_accent),
338
339        'search-bar': 'bg:{}'.format(theme.inactive_bg),
340        'search-bar-title': 'bg:{} {}'.format(theme.cyan_accent,
341                                              theme.default_bg),
342        'search-bar-setting': '{}'.format(theme.cyan_accent),
343        'search-bar-border': 'bg:{} {}'.format(theme.inactive_bg,
344                                               theme.cyan_accent),
345        'search-match-count-dialog': 'bg:{}'.format(theme.inactive_bg),
346        'search-match-count-dialog-title': '{}'.format(theme.cyan_accent),
347        'search-match-count-dialog-default-fg': '{}'.format(theme.default_fg),
348        'search-match-count-dialog-border': 'bg:{} {}'.format(
349            theme.inactive_bg,
350            theme.cyan_accent),
351
352        'filter-bar': 'bg:{}'.format(theme.inactive_bg),
353        'filter-bar-title': 'bg:{} {}'.format(theme.red_accent,
354                                              theme.default_bg),
355        'filter-bar-setting': '{}'.format(theme.cyan_accent),
356        'filter-bar-delete': '{}'.format(theme.red_accent),
357        'filter-bar-delimiter': '{}'.format(theme.purple_accent),
358
359        'saveas-dialog': 'bg:{}'.format(theme.inactive_bg),
360        'saveas-dialog-title': 'bg:{} {}'.format(theme.inactive_bg,
361                                                 theme.default_fg),
362        'saveas-dialog-setting': '{}'.format(theme.cyan_accent),
363        'saveas-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
364                                                  theme.cyan_accent),
365
366        'selection-dialog': 'bg:{}'.format(theme.inactive_bg),
367        'selection-dialog-title': '{}'.format(theme.yellow_accent),
368        'selection-dialog-default-fg': '{}'.format(theme.default_fg),
369        'selection-dialog-action-bg': 'bg:{}'.format(theme.yellow_accent),
370        'selection-dialog-action-fg': '{}'.format(theme.button_inactive_bg),
371        'selection-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
372                                                     theme.yellow_accent),
373
374        'quit-dialog': 'bg:{}'.format(theme.inactive_bg),
375        'quit-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
376                                                theme.red_accent),
377
378        'command-runner': 'bg:{}'.format(theme.inactive_bg),
379        'command-runner-title': 'bg:{} {}'.format(theme.inactive_bg,
380                                                  theme.default_fg),
381        'command-runner-setting': '{}'.format(theme.purple_accent),
382        'command-runner-border': 'bg:{} {}'.format(theme.inactive_bg,
383                                                   theme.purple_accent),
384        'command-runner-selected-item': 'bg:{}'.format(theme.selected_line_bg),
385        'command-runner-fuzzy-highlight-0': '{}'.format(theme.blue_accent),
386        'command-runner-fuzzy-highlight-1': '{}'.format(theme.cyan_accent),
387        'command-runner-fuzzy-highlight-2': '{}'.format(theme.green_accent),
388        'command-runner-fuzzy-highlight-3': '{}'.format(theme.yellow_accent),
389        'command-runner-fuzzy-highlight-4': '{}'.format(theme.orange_accent),
390        'command-runner-fuzzy-highlight-5': '{}'.format(theme.red_accent),
391
392        # Progress Bar Styles
393        # Entire set of ProgressBars - no title is used in pw_console
394        'title': '',
395        # Actual bar title
396        'label': 'bold',
397        'percentage': '{}'.format(theme.green_accent),
398        'bar': '{}'.format(theme.magenta_accent),
399        # Filled part of the bar
400        'bar-a': '{} bold'.format(theme.cyan_accent),
401        # End of current progress
402        'bar-b': '{} bold'.format(theme.purple_accent),
403        # Empty part of the bar
404        'bar-c': '',
405        # current/total counts
406        'current': '{}'.format(theme.cyan_accent),
407        'total': '{}'.format(theme.cyan_accent),
408        'time-elapsed': '{}'.format(theme.purple_accent),
409        'time-left': '{}'.format(theme.magenta_accent),
410
411        # Named theme color classes for use in user plugins.
412        'theme-fg-red': '{}'.format(theme.red_accent),
413        'theme-fg-orange': '{}'.format(theme.orange_accent),
414        'theme-fg-yellow': '{}'.format(theme.yellow_accent),
415        'theme-fg-green': '{}'.format(theme.green_accent),
416        'theme-fg-cyan': '{}'.format(theme.cyan_accent),
417        'theme-fg-blue': '{}'.format(theme.blue_accent),
418        'theme-fg-purple': '{}'.format(theme.purple_accent),
419        'theme-fg-magenta': '{}'.format(theme.magenta_accent),
420        'theme-bg-red': 'bg:{}'.format(theme.red_accent),
421        'theme-bg-orange': 'bg:{}'.format(theme.orange_accent),
422        'theme-bg-yellow': 'bg:{}'.format(theme.yellow_accent),
423        'theme-bg-green': 'bg:{}'.format(theme.green_accent),
424        'theme-bg-cyan': 'bg:{}'.format(theme.cyan_accent),
425        'theme-bg-blue': 'bg:{}'.format(theme.blue_accent),
426        'theme-bg-purple': 'bg:{}'.format(theme.purple_accent),
427        'theme-bg-magenta': 'bg:{}'.format(theme.magenta_accent),
428
429        'theme-bg-active': 'bg:{}'.format(theme.active_bg),
430        'theme-fg-active': '{}'.format(theme.active_fg),
431
432        'theme-bg-inactive': 'bg:{}'.format(theme.inactive_bg),
433        'theme-fg-inactive': '{}'.format(theme.inactive_fg),
434
435        'theme-fg-default': '{}'.format(theme.default_fg),
436        'theme-bg-default': 'bg:{}'.format(theme.default_bg),
437
438        'theme-fg-dim': '{}'.format(theme.dim_fg),
439        'theme-bg-dim': 'bg:{}'.format(theme.dim_bg),
440
441        'theme-bg-dialog': 'bg:{}'.format(theme.dialog_bg),
442        'theme-bg-line-highlight': 'bg:{}'.format(theme.line_highlight_bg),
443
444        'theme-bg-button-active': 'bg:{}'.format(theme.button_active_bg),
445        'theme-bg-button-inactive': 'bg:{}'.format(theme.button_inactive_bg),
446    }  # yapf: disable
447
448    return Style.from_dict(pw_console_styles)
449
450
451def get_toolbar_style(pt_container, dim=False) -> str:
452    """Return the style class for a toolbar if pt_container is in focus."""
453    if has_focus(pt_container.__pt_container__())():
454        return 'class:toolbar_dim_active' if dim else 'class:toolbar_active'
455    return 'class:toolbar_dim_inactive' if dim else 'class:toolbar_inactive'
456
457
458def get_button_style(pt_container) -> str:
459    """Return the style class for a toolbar if pt_container is in focus."""
460    if has_focus(pt_container.__pt_container__())():
461        return 'class:toolbar-button-active'
462    return 'class:toolbar-button-inactive'
463
464
465def get_pane_style(pt_container) -> str:
466    """Return the style class for a pane title if pt_container is in focus."""
467    if has_focus(pt_container.__pt_container__())():
468        return 'class:pane_active'
469    return 'class:pane_inactive'
470
471
472def get_pane_indicator(pt_container,
473                       title,
474                       mouse_handler=None,
475                       hide_indicator=False) -> StyleAndTextTuples:
476    """Return formatted text for a pane indicator and title."""
477
478    inactive_indicator: OneStyleAndTextTuple
479    active_indicator: OneStyleAndTextTuple
480    inactive_title: OneStyleAndTextTuple
481    active_title: OneStyleAndTextTuple
482
483    if mouse_handler:
484        inactive_indicator = ('class:pane_indicator_inactive', ' ',
485                              mouse_handler)
486        active_indicator = ('class:pane_indicator_active', ' ', mouse_handler)
487        inactive_title = ('class:pane_title_inactive', title, mouse_handler)
488        active_title = ('class:pane_title_active', title, mouse_handler)
489    else:
490        inactive_indicator = ('class:pane_indicator_inactive', ' ')
491        active_indicator = ('class:pane_indicator_active', ' ')
492        inactive_title = ('class:pane_title_inactive', title)
493        active_title = ('class:pane_title_active', title)
494
495    fragments: StyleAndTextTuples = []
496    if has_focus(pt_container.__pt_container__())():
497        if not hide_indicator:
498            fragments.append(active_indicator)
499        fragments.append(active_title)
500    else:
501        if not hide_indicator:
502            fragments.append(inactive_indicator)
503        fragments.append(inactive_title)
504    return fragments
505