xref: /aosp_15_r20/external/pigweed/pw_console/py/pw_console/widgets/checkbox.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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"""Functions to create checkboxes for menus and toolbars."""
15
16import sys
17from typing import Callable, Iterable, NamedTuple
18
19from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
20from prompt_toolkit.formatted_text import StyleAndTextTuples
21
22_KEY_SEPARATOR = ' '
23_CHECKED_BOX = '[✓]'
24
25if sys.platform in ['win32']:
26    _CHECKED_BOX = '[x]'
27
28
29class ToolbarButton(NamedTuple):
30    key: str | None = None
31    description: str | None = 'Button'
32    mouse_handler: Callable | None = None
33    is_checkbox: bool = False
34    checked: Callable | None = None
35
36
37def to_checkbox(
38    checked: bool,
39    mouse_handler: Callable | None = None,
40    end: str = ' ',
41    unchecked_style: str = 'class:checkbox',
42    checked_style: str = 'class:checkbox-checked',
43) -> OneStyleAndTextTuple:
44    text = _CHECKED_BOX if checked else '[ ]'
45    text += end
46    style = checked_style if checked else unchecked_style
47    if mouse_handler:
48        return (style, text, mouse_handler)
49    return (style, text)
50
51
52def to_checkbox_text(checked: bool, end=' '):
53    return to_checkbox(checked, end=end)[1]
54
55
56def to_setting(
57    checked: bool,
58    text: str,
59    active_style='class:toolbar-setting-active',
60    inactive_style='',
61    mouse_handler=None,
62):
63    """Apply a style to text if checked is True."""
64    style = active_style if checked else inactive_style
65    if mouse_handler:
66        return (style, text, mouse_handler)
67    return (style, text)
68
69
70def to_checkbox_with_keybind_indicator(
71    checked: bool,
72    key: str,
73    description: str,
74    mouse_handler=None,
75    base_style: str = '',
76    **checkbox_kwargs,
77):
78    """Create a clickable keybind indicator with checkbox for toolbars."""
79    if mouse_handler:
80        return to_keybind_indicator(
81            key,
82            description,
83            mouse_handler,
84            leading_fragments=[
85                to_checkbox(checked, mouse_handler, **checkbox_kwargs)
86            ],
87            base_style=base_style,
88        )
89    return to_keybind_indicator(
90        key,
91        description,
92        leading_fragments=[to_checkbox(checked, **checkbox_kwargs)],
93        base_style=base_style,
94    )
95
96
97def to_keybind_indicator(
98    key: str,
99    description: str,
100    mouse_handler: Callable | None = None,
101    leading_fragments: Iterable | None = None,
102    middle_fragments: Iterable | None = None,
103    base_style: str = '',
104    key_style: str = 'class:keybind',
105    description_style: str = 'class:keyhelp',
106):
107    """Create a clickable keybind indicator for toolbars."""
108    if base_style:
109        base_style += ' '
110
111    fragments: StyleAndTextTuples = []
112    fragments.append((base_style + 'class:toolbar-button-decoration', ' '))
113
114    def append_fragment_with_base_style(frag_list, fragment) -> None:
115        if mouse_handler:
116            frag_list.append(
117                (base_style + fragment[0], fragment[1], mouse_handler)
118            )
119        else:
120            frag_list.append((base_style + fragment[0], fragment[1]))
121
122    # Add any starting fragments first
123    if leading_fragments:
124        for fragment in leading_fragments:
125            append_fragment_with_base_style(fragments, fragment)
126
127    # Function name
128    if mouse_handler:
129        fragments.append(
130            (base_style + description_style, description, mouse_handler)
131        )
132    else:
133        fragments.append((base_style + description_style, description))
134
135    if middle_fragments:
136        for fragment in middle_fragments:
137            append_fragment_with_base_style(fragments, fragment)
138
139    # Separator and keybind
140    if key:
141        if mouse_handler:
142            fragments.append(
143                (base_style + description_style, _KEY_SEPARATOR, mouse_handler)
144            )
145            fragments.append((base_style + key_style, key, mouse_handler))
146        else:
147            fragments.append((base_style + description_style, _KEY_SEPARATOR))
148            fragments.append((base_style + key_style, key))
149
150    fragments.append((base_style + 'class:toolbar-button-decoration', ' '))
151    return fragments
152