123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- """
- Collection of reusable components for building full screen applications.
- All of these widgets implement the ``__pt_container__`` method, which makes
- them usable in any situation where we are expecting a `prompt_toolkit`
- container object.
- .. warning::
- At this point, the API for these widgets is considered unstable, and can
- potentially change between minor releases (we try not too, but no
- guarantees are made yet). The public API in
- `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable.
- """
- from __future__ import annotations
- from functools import partial
- from typing import Callable, Generic, Sequence, TypeVar
- from prompt_toolkit.application.current import get_app
- from prompt_toolkit.auto_suggest import AutoSuggest, DynamicAutoSuggest
- from prompt_toolkit.buffer import Buffer, BufferAcceptHandler
- from prompt_toolkit.completion import Completer, DynamicCompleter
- from prompt_toolkit.document import Document
- from prompt_toolkit.filters import (
- Condition,
- FilterOrBool,
- has_focus,
- is_done,
- is_true,
- to_filter,
- )
- from prompt_toolkit.formatted_text import (
- AnyFormattedText,
- StyleAndTextTuples,
- Template,
- to_formatted_text,
- )
- from prompt_toolkit.formatted_text.utils import fragment_list_to_text
- from prompt_toolkit.history import History
- from prompt_toolkit.key_binding.key_bindings import KeyBindings
- from prompt_toolkit.key_binding.key_processor import KeyPressEvent
- from prompt_toolkit.keys import Keys
- from prompt_toolkit.layout.containers import (
- AnyContainer,
- ConditionalContainer,
- Container,
- DynamicContainer,
- Float,
- FloatContainer,
- HSplit,
- VSplit,
- Window,
- WindowAlign,
- )
- from prompt_toolkit.layout.controls import (
- BufferControl,
- FormattedTextControl,
- GetLinePrefixCallable,
- )
- from prompt_toolkit.layout.dimension import AnyDimension
- from prompt_toolkit.layout.dimension import Dimension as D
- from prompt_toolkit.layout.margins import (
- ConditionalMargin,
- NumberedMargin,
- ScrollbarMargin,
- )
- from prompt_toolkit.layout.processors import (
- AppendAutoSuggestion,
- BeforeInput,
- ConditionalProcessor,
- PasswordProcessor,
- Processor,
- )
- from prompt_toolkit.lexers import DynamicLexer, Lexer
- from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
- from prompt_toolkit.utils import get_cwidth
- from prompt_toolkit.validation import DynamicValidator, Validator
- from .toolbars import SearchToolbar
- __all__ = [
- "TextArea",
- "Label",
- "Button",
- "Frame",
- "Shadow",
- "Box",
- "VerticalLine",
- "HorizontalLine",
- "RadioList",
- "CheckboxList",
- "Checkbox", # backward compatibility
- "ProgressBar",
- ]
- E = KeyPressEvent
- class Border:
- "Box drawing characters. (Thin)"
- HORIZONTAL = "\u2500"
- VERTICAL = "\u2502"
- TOP_LEFT = "\u250c"
- TOP_RIGHT = "\u2510"
- BOTTOM_LEFT = "\u2514"
- BOTTOM_RIGHT = "\u2518"
- class TextArea:
- """
- A simple input field.
- This is a higher level abstraction on top of several other classes with
- sane defaults.
- This widget does have the most common options, but it does not intend to
- cover every single use case. For more configurations options, you can
- always build a text area manually, using a
- :class:`~prompt_toolkit.buffer.Buffer`,
- :class:`~prompt_toolkit.layout.BufferControl` and
- :class:`~prompt_toolkit.layout.Window`.
- Buffer attributes:
- :param text: The initial text.
- :param multiline: If True, allow multiline input.
- :param completer: :class:`~prompt_toolkit.completion.Completer` instance
- for auto completion.
- :param complete_while_typing: Boolean.
- :param accept_handler: Called when `Enter` is pressed (This should be a
- callable that takes a buffer as input).
- :param history: :class:`~prompt_toolkit.history.History` instance.
- :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest`
- instance for input suggestions.
- BufferControl attributes:
- :param password: When `True`, display using asterisks.
- :param focusable: When `True`, allow this widget to receive the focus.
- :param focus_on_click: When `True`, focus after mouse click.
- :param input_processors: `None` or a list of
- :class:`~prompt_toolkit.layout.Processor` objects.
- :param validator: `None` or a :class:`~prompt_toolkit.validation.Validator`
- object.
- Window attributes:
- :param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax
- highlighting.
- :param wrap_lines: When `True`, don't scroll horizontally, but wrap lines.
- :param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.)
- :param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.)
- :param scrollbar: When `True`, display a scroll bar.
- :param style: A style string.
- :param dont_extend_width: When `True`, don't take up more width then the
- preferred width reported by the control.
- :param dont_extend_height: When `True`, don't take up more width then the
- preferred height reported by the control.
- :param get_line_prefix: None or a callable that returns formatted text to
- be inserted before a line. It takes a line number (int) and a
- wrap_count and returns formatted text. This can be used for
- implementation of line continuations, things like Vim "breakindent" and
- so on.
- Other attributes:
- :param search_field: An optional `SearchToolbar` object.
- """
- def __init__(
- self,
- text: str = "",
- multiline: FilterOrBool = True,
- password: FilterOrBool = False,
- lexer: Lexer | None = None,
- auto_suggest: AutoSuggest | None = None,
- completer: Completer | None = None,
- complete_while_typing: FilterOrBool = True,
- validator: Validator | None = None,
- accept_handler: BufferAcceptHandler | None = None,
- history: History | None = None,
- focusable: FilterOrBool = True,
- focus_on_click: FilterOrBool = False,
- wrap_lines: FilterOrBool = True,
- read_only: FilterOrBool = False,
- width: AnyDimension = None,
- height: AnyDimension = None,
- dont_extend_height: FilterOrBool = False,
- dont_extend_width: FilterOrBool = False,
- line_numbers: bool = False,
- get_line_prefix: GetLinePrefixCallable | None = None,
- scrollbar: bool = False,
- style: str = "",
- search_field: SearchToolbar | None = None,
- preview_search: FilterOrBool = True,
- prompt: AnyFormattedText = "",
- input_processors: list[Processor] | None = None,
- name: str = "",
- ) -> None:
- if search_field is None:
- search_control = None
- elif isinstance(search_field, SearchToolbar):
- search_control = search_field.control
- if input_processors is None:
- input_processors = []
- # Writeable attributes.
- self.completer = completer
- self.complete_while_typing = complete_while_typing
- self.lexer = lexer
- self.auto_suggest = auto_suggest
- self.read_only = read_only
- self.wrap_lines = wrap_lines
- self.validator = validator
- self.buffer = Buffer(
- document=Document(text, 0),
- multiline=multiline,
- read_only=Condition(lambda: is_true(self.read_only)),
- completer=DynamicCompleter(lambda: self.completer),
- complete_while_typing=Condition(
- lambda: is_true(self.complete_while_typing)
- ),
- validator=DynamicValidator(lambda: self.validator),
- auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
- accept_handler=accept_handler,
- history=history,
- name=name,
- )
- self.control = BufferControl(
- buffer=self.buffer,
- lexer=DynamicLexer(lambda: self.lexer),
- input_processors=[
- ConditionalProcessor(
- AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done
- ),
- ConditionalProcessor(
- processor=PasswordProcessor(), filter=to_filter(password)
- ),
- BeforeInput(prompt, style="class:text-area.prompt"),
- ]
- + input_processors,
- search_buffer_control=search_control,
- preview_search=preview_search,
- focusable=focusable,
- focus_on_click=focus_on_click,
- )
- if multiline:
- if scrollbar:
- right_margins = [ScrollbarMargin(display_arrows=True)]
- else:
- right_margins = []
- if line_numbers:
- left_margins = [NumberedMargin()]
- else:
- left_margins = []
- else:
- height = D.exact(1)
- left_margins = []
- right_margins = []
- style = "class:text-area " + style
- # If no height was given, guarantee height of at least 1.
- if height is None:
- height = D(min=1)
- self.window = Window(
- height=height,
- width=width,
- dont_extend_height=dont_extend_height,
- dont_extend_width=dont_extend_width,
- content=self.control,
- style=style,
- wrap_lines=Condition(lambda: is_true(self.wrap_lines)),
- left_margins=left_margins,
- right_margins=right_margins,
- get_line_prefix=get_line_prefix,
- )
- @property
- def text(self) -> str:
- """
- The `Buffer` text.
- """
- return self.buffer.text
- @text.setter
- def text(self, value: str) -> None:
- self.document = Document(value, 0)
- @property
- def document(self) -> Document:
- """
- The `Buffer` document (text + cursor position).
- """
- return self.buffer.document
- @document.setter
- def document(self, value: Document) -> None:
- self.buffer.set_document(value, bypass_readonly=True)
- @property
- def accept_handler(self) -> BufferAcceptHandler | None:
- """
- The accept handler. Called when the user accepts the input.
- """
- return self.buffer.accept_handler
- @accept_handler.setter
- def accept_handler(self, value: BufferAcceptHandler) -> None:
- self.buffer.accept_handler = value
- def __pt_container__(self) -> Container:
- return self.window
- class Label:
- """
- Widget that displays the given text. It is not editable or focusable.
- :param text: Text to display. Can be multiline. All value types accepted by
- :class:`prompt_toolkit.layout.FormattedTextControl` are allowed,
- including a callable.
- :param style: A style string.
- :param width: When given, use this width, rather than calculating it from
- the text size.
- :param dont_extend_width: When `True`, don't take up more width than
- preferred, i.e. the length of the longest line of
- the text, or value of `width` parameter, if
- given. `True` by default
- :param dont_extend_height: When `True`, don't take up more width than the
- preferred height, i.e. the number of lines of
- the text. `False` by default.
- """
- def __init__(
- self,
- text: AnyFormattedText,
- style: str = "",
- width: AnyDimension = None,
- dont_extend_height: bool = True,
- dont_extend_width: bool = False,
- align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT,
- # There is no cursor navigation in a label, so it makes sense to always
- # wrap lines by default.
- wrap_lines: FilterOrBool = True,
- ) -> None:
- self.text = text
- def get_width() -> AnyDimension:
- if width is None:
- text_fragments = to_formatted_text(self.text)
- text = fragment_list_to_text(text_fragments)
- if text:
- longest_line = max(get_cwidth(line) for line in text.splitlines())
- else:
- return D(preferred=0)
- return D(preferred=longest_line)
- else:
- return width
- self.formatted_text_control = FormattedTextControl(text=lambda: self.text)
- self.window = Window(
- content=self.formatted_text_control,
- width=get_width,
- height=D(min=1),
- style="class:label " + style,
- dont_extend_height=dont_extend_height,
- dont_extend_width=dont_extend_width,
- align=align,
- wrap_lines=wrap_lines,
- )
- def __pt_container__(self) -> Container:
- return self.window
- class Button:
- """
- Clickable button.
- :param text: The caption for the button.
- :param handler: `None` or callable. Called when the button is clicked. No
- parameters are passed to this callable. Use for instance Python's
- `functools.partial` to pass parameters to this callable if needed.
- :param width: Width of the button.
- """
- def __init__(
- self,
- text: str,
- handler: Callable[[], None] | None = None,
- width: int = 12,
- left_symbol: str = "<",
- right_symbol: str = ">",
- ) -> None:
- self.text = text
- self.left_symbol = left_symbol
- self.right_symbol = right_symbol
- self.handler = handler
- self.width = width
- self.control = FormattedTextControl(
- self._get_text_fragments,
- key_bindings=self._get_key_bindings(),
- focusable=True,
- )
- def get_style() -> str:
- if get_app().layout.has_focus(self):
- return "class:button.focused"
- else:
- return "class:button"
- # Note: `dont_extend_width` is False, because we want to allow buttons
- # to take more space if the parent container provides more space.
- # Otherwise, we will also truncate the text.
- # Probably we need a better way here to adjust to width of the
- # button to the text.
- self.window = Window(
- self.control,
- align=WindowAlign.CENTER,
- height=1,
- width=width,
- style=get_style,
- dont_extend_width=False,
- dont_extend_height=True,
- )
- def _get_text_fragments(self) -> StyleAndTextTuples:
- width = self.width - (
- get_cwidth(self.left_symbol) + get_cwidth(self.right_symbol)
- )
- text = (f"{{:^{width}}}").format(self.text)
- def handler(mouse_event: MouseEvent) -> None:
- if (
- self.handler is not None
- and mouse_event.event_type == MouseEventType.MOUSE_UP
- ):
- self.handler()
- return [
- ("class:button.arrow", self.left_symbol, handler),
- ("[SetCursorPosition]", ""),
- ("class:button.text", text, handler),
- ("class:button.arrow", self.right_symbol, handler),
- ]
- def _get_key_bindings(self) -> KeyBindings:
- "Key bindings for the Button."
- kb = KeyBindings()
- @kb.add(" ")
- @kb.add("enter")
- def _(event: E) -> None:
- if self.handler is not None:
- self.handler()
- return kb
- def __pt_container__(self) -> Container:
- return self.window
- class Frame:
- """
- Draw a border around any container, optionally with a title text.
- Changing the title and body of the frame is possible at runtime by
- assigning to the `body` and `title` attributes of this class.
- :param body: Another container object.
- :param title: Text to be displayed in the top of the frame (can be formatted text).
- :param style: Style string to be applied to this widget.
- """
- def __init__(
- self,
- body: AnyContainer,
- title: AnyFormattedText = "",
- style: str = "",
- width: AnyDimension = None,
- height: AnyDimension = None,
- key_bindings: KeyBindings | None = None,
- modal: bool = False,
- ) -> None:
- self.title = title
- self.body = body
- fill = partial(Window, style="class:frame.border")
- style = "class:frame " + style
- top_row_with_title = VSplit(
- [
- fill(width=1, height=1, char=Border.TOP_LEFT),
- fill(char=Border.HORIZONTAL),
- fill(width=1, height=1, char="|"),
- # Notice: we use `Template` here, because `self.title` can be an
- # `HTML` object for instance.
- Label(
- lambda: Template(" {} ").format(self.title),
- style="class:frame.label",
- dont_extend_width=True,
- ),
- fill(width=1, height=1, char="|"),
- fill(char=Border.HORIZONTAL),
- fill(width=1, height=1, char=Border.TOP_RIGHT),
- ],
- height=1,
- )
- top_row_without_title = VSplit(
- [
- fill(width=1, height=1, char=Border.TOP_LEFT),
- fill(char=Border.HORIZONTAL),
- fill(width=1, height=1, char=Border.TOP_RIGHT),
- ],
- height=1,
- )
- @Condition
- def has_title() -> bool:
- return bool(self.title)
- self.container = HSplit(
- [
- ConditionalContainer(content=top_row_with_title, filter=has_title),
- ConditionalContainer(content=top_row_without_title, filter=~has_title),
- VSplit(
- [
- fill(width=1, char=Border.VERTICAL),
- DynamicContainer(lambda: self.body),
- fill(width=1, char=Border.VERTICAL),
- # Padding is required to make sure that if the content is
- # too small, the right frame border is still aligned.
- ],
- padding=0,
- ),
- VSplit(
- [
- fill(width=1, height=1, char=Border.BOTTOM_LEFT),
- fill(char=Border.HORIZONTAL),
- fill(width=1, height=1, char=Border.BOTTOM_RIGHT),
- ],
- # specifying height here will increase the rendering speed.
- height=1,
- ),
- ],
- width=width,
- height=height,
- style=style,
- key_bindings=key_bindings,
- modal=modal,
- )
- def __pt_container__(self) -> Container:
- return self.container
- class Shadow:
- """
- Draw a shadow underneath/behind this container.
- (This applies `class:shadow` the the cells under the shadow. The Style
- should define the colors for the shadow.)
- :param body: Another container object.
- """
- def __init__(self, body: AnyContainer) -> None:
- self.container = FloatContainer(
- content=body,
- floats=[
- Float(
- bottom=-1,
- height=1,
- left=1,
- right=-1,
- transparent=True,
- content=Window(style="class:shadow"),
- ),
- Float(
- bottom=-1,
- top=1,
- width=1,
- right=-1,
- transparent=True,
- content=Window(style="class:shadow"),
- ),
- ],
- )
- def __pt_container__(self) -> Container:
- return self.container
- class Box:
- """
- Add padding around a container.
- This also makes sure that the parent can provide more space than required by
- the child. This is very useful when wrapping a small element with a fixed
- size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit``
- try to make sure to adapt respectively the width and height, possibly
- shrinking other elements. Wrapping something in a ``Box`` makes it flexible.
- :param body: Another container object.
- :param padding: The margin to be used around the body. This can be
- overridden by `padding_left`, padding_right`, `padding_top` and
- `padding_bottom`.
- :param style: A style string.
- :param char: Character to be used for filling the space around the body.
- (This is supposed to be a character with a terminal width of 1.)
- """
- def __init__(
- self,
- body: AnyContainer,
- padding: AnyDimension = None,
- padding_left: AnyDimension = None,
- padding_right: AnyDimension = None,
- padding_top: AnyDimension = None,
- padding_bottom: AnyDimension = None,
- width: AnyDimension = None,
- height: AnyDimension = None,
- style: str = "",
- char: None | str | Callable[[], str] = None,
- modal: bool = False,
- key_bindings: KeyBindings | None = None,
- ) -> None:
- self.padding = padding
- self.padding_left = padding_left
- self.padding_right = padding_right
- self.padding_top = padding_top
- self.padding_bottom = padding_bottom
- self.body = body
- def left() -> AnyDimension:
- if self.padding_left is None:
- return self.padding
- return self.padding_left
- def right() -> AnyDimension:
- if self.padding_right is None:
- return self.padding
- return self.padding_right
- def top() -> AnyDimension:
- if self.padding_top is None:
- return self.padding
- return self.padding_top
- def bottom() -> AnyDimension:
- if self.padding_bottom is None:
- return self.padding
- return self.padding_bottom
- self.container = HSplit(
- [
- Window(height=top, char=char),
- VSplit(
- [
- Window(width=left, char=char),
- body,
- Window(width=right, char=char),
- ]
- ),
- Window(height=bottom, char=char),
- ],
- width=width,
- height=height,
- style=style,
- modal=modal,
- key_bindings=None,
- )
- def __pt_container__(self) -> Container:
- return self.container
- _T = TypeVar("_T")
- class _DialogList(Generic[_T]):
- """
- Common code for `RadioList` and `CheckboxList`.
- """
- open_character: str = ""
- close_character: str = ""
- container_style: str = ""
- default_style: str = ""
- selected_style: str = ""
- checked_style: str = ""
- multiple_selection: bool = False
- show_scrollbar: bool = True
- def __init__(
- self,
- values: Sequence[tuple[_T, AnyFormattedText]],
- default_values: Sequence[_T] | None = None,
- ) -> None:
- assert len(values) > 0
- default_values = default_values or []
- self.values = values
- # current_values will be used in multiple_selection,
- # current_value will be used otherwise.
- keys: list[_T] = [value for (value, _) in values]
- self.current_values: list[_T] = [
- value for value in default_values if value in keys
- ]
- self.current_value: _T = (
- default_values[0]
- if len(default_values) and default_values[0] in keys
- else values[0][0]
- )
- # Cursor index: take first selected item or first item otherwise.
- if len(self.current_values) > 0:
- self._selected_index = keys.index(self.current_values[0])
- else:
- self._selected_index = 0
- # Key bindings.
- kb = KeyBindings()
- @kb.add("up")
- def _up(event: E) -> None:
- self._selected_index = max(0, self._selected_index - 1)
- @kb.add("down")
- def _down(event: E) -> None:
- self._selected_index = min(len(self.values) - 1, self._selected_index + 1)
- @kb.add("pageup")
- def _pageup(event: E) -> None:
- w = event.app.layout.current_window
- if w.render_info:
- self._selected_index = max(
- 0, self._selected_index - len(w.render_info.displayed_lines)
- )
- @kb.add("pagedown")
- def _pagedown(event: E) -> None:
- w = event.app.layout.current_window
- if w.render_info:
- self._selected_index = min(
- len(self.values) - 1,
- self._selected_index + len(w.render_info.displayed_lines),
- )
- @kb.add("enter")
- @kb.add(" ")
- def _click(event: E) -> None:
- self._handle_enter()
- @kb.add(Keys.Any)
- def _find(event: E) -> None:
- # We first check values after the selected value, then all values.
- values = list(self.values)
- for value in values[self._selected_index + 1 :] + values:
- text = fragment_list_to_text(to_formatted_text(value[1])).lower()
- if text.startswith(event.data.lower()):
- self._selected_index = self.values.index(value)
- return
- # Control and window.
- self.control = FormattedTextControl(
- self._get_text_fragments, key_bindings=kb, focusable=True
- )
- self.window = Window(
- content=self.control,
- style=self.container_style,
- right_margins=[
- ConditionalMargin(
- margin=ScrollbarMargin(display_arrows=True),
- filter=Condition(lambda: self.show_scrollbar),
- ),
- ],
- dont_extend_height=True,
- )
- def _handle_enter(self) -> None:
- if self.multiple_selection:
- val = self.values[self._selected_index][0]
- if val in self.current_values:
- self.current_values.remove(val)
- else:
- self.current_values.append(val)
- else:
- self.current_value = self.values[self._selected_index][0]
- def _get_text_fragments(self) -> StyleAndTextTuples:
- def mouse_handler(mouse_event: MouseEvent) -> None:
- """
- Set `_selected_index` and `current_value` according to the y
- position of the mouse click event.
- """
- if mouse_event.event_type == MouseEventType.MOUSE_UP:
- self._selected_index = mouse_event.position.y
- self._handle_enter()
- result: StyleAndTextTuples = []
- for i, value in enumerate(self.values):
- if self.multiple_selection:
- checked = value[0] in self.current_values
- else:
- checked = value[0] == self.current_value
- selected = i == self._selected_index
- style = ""
- if checked:
- style += " " + self.checked_style
- if selected:
- style += " " + self.selected_style
- result.append((style, self.open_character))
- if selected:
- result.append(("[SetCursorPosition]", ""))
- if checked:
- result.append((style, "*"))
- else:
- result.append((style, " "))
- result.append((style, self.close_character))
- result.append((self.default_style, " "))
- result.extend(to_formatted_text(value[1], style=self.default_style))
- result.append(("", "\n"))
- # Add mouse handler to all fragments.
- for i in range(len(result)):
- result[i] = (result[i][0], result[i][1], mouse_handler)
- result.pop() # Remove last newline.
- return result
- def __pt_container__(self) -> Container:
- return self.window
- class RadioList(_DialogList[_T]):
- """
- List of radio buttons. Only one can be checked at the same time.
- :param values: List of (value, label) tuples.
- """
- open_character = "("
- close_character = ")"
- container_style = "class:radio-list"
- default_style = "class:radio"
- selected_style = "class:radio-selected"
- checked_style = "class:radio-checked"
- multiple_selection = False
- def __init__(
- self,
- values: Sequence[tuple[_T, AnyFormattedText]],
- default: _T | None = None,
- ) -> None:
- if default is None:
- default_values = None
- else:
- default_values = [default]
- super().__init__(values, default_values=default_values)
- class CheckboxList(_DialogList[_T]):
- """
- List of checkbox buttons. Several can be checked at the same time.
- :param values: List of (value, label) tuples.
- """
- open_character = "["
- close_character = "]"
- container_style = "class:checkbox-list"
- default_style = "class:checkbox"
- selected_style = "class:checkbox-selected"
- checked_style = "class:checkbox-checked"
- multiple_selection = True
- class Checkbox(CheckboxList[str]):
- """Backward compatibility util: creates a 1-sized CheckboxList
- :param text: the text
- """
- show_scrollbar = False
- def __init__(self, text: AnyFormattedText = "", checked: bool = False) -> None:
- values = [("value", text)]
- super().__init__(values=values)
- self.checked = checked
- @property
- def checked(self) -> bool:
- return "value" in self.current_values
- @checked.setter
- def checked(self, value: bool) -> None:
- if value:
- self.current_values = ["value"]
- else:
- self.current_values = []
- class VerticalLine:
- """
- A simple vertical line with a width of 1.
- """
- def __init__(self) -> None:
- self.window = Window(
- char=Border.VERTICAL, style="class:line,vertical-line", width=1
- )
- def __pt_container__(self) -> Container:
- return self.window
- class HorizontalLine:
- """
- A simple horizontal line with a height of 1.
- """
- def __init__(self) -> None:
- self.window = Window(
- char=Border.HORIZONTAL, style="class:line,horizontal-line", height=1
- )
- def __pt_container__(self) -> Container:
- return self.window
- class ProgressBar:
- def __init__(self) -> None:
- self._percentage = 60
- self.label = Label("60%")
- self.container = FloatContainer(
- content=Window(height=1),
- floats=[
- # We first draw the label, then the actual progress bar. Right
- # now, this is the only way to have the colors of the progress
- # bar appear on top of the label. The problem is that our label
- # can't be part of any `Window` below.
- Float(content=self.label, top=0, bottom=0),
- Float(
- left=0,
- top=0,
- right=0,
- bottom=0,
- content=VSplit(
- [
- Window(
- style="class:progress-bar.used",
- width=lambda: D(weight=int(self._percentage)),
- ),
- Window(
- style="class:progress-bar",
- width=lambda: D(weight=int(100 - self._percentage)),
- ),
- ]
- ),
- ),
- ],
- )
- @property
- def percentage(self) -> int:
- return self._percentage
- @percentage.setter
- def percentage(self, value: int) -> None:
- self._percentage = value
- self.label.text = f"{value}%"
- def __pt_container__(self) -> Container:
- return self.container
|