Module textual.cli.previews.easing
Expand source code
from __future__ import annotations
from rich.console import RenderableType
from textual._easing import EASING
from textual.app import App, ComposeResult
from textual.cli.previews.borders import TEXT
from textual.containers import Container, Horizontal, Vertical
from textual.reactive import Reactive
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
from textual.widgets import Button, Footer, Static, Input
VIRTUAL_SIZE = 100
WINDOW_SIZE = 10
START_POSITION = 0.0
END_POSITION = float(VIRTUAL_SIZE - WINDOW_SIZE)
class EasingButtons(Widget):
def compose(self) -> ComposeResult:
for easing in sorted(EASING, reverse=True):
yield Button(easing, id=easing)
class Bar(Widget):
position = Reactive.init(START_POSITION)
animation_running = Reactive(False)
DEFAULT_CSS = """
Bar {
background: $surface;
color: $error;
}
Bar.-active {
background: $surface;
color: $success;
}
"""
def watch_animation_running(self, running: bool) -> None:
self.set_class(running, "-active")
def render(self) -> RenderableType:
return ScrollBarRender(
virtual_size=VIRTUAL_SIZE,
window_size=WINDOW_SIZE,
position=self.position,
style=self.rich_style,
)
class EasingApp(App):
position = Reactive.init(START_POSITION)
duration = Reactive.var(1.0)
def on_load(self):
self.bind(
"ctrl+p", "focus('duration-input')", description="Focus: Duration Input"
)
self.bind("ctrl+b", "toggle_dark", description="Toggle Dark")
def compose(self) -> ComposeResult:
self.animated_bar = Bar()
self.animated_bar.position = START_POSITION
duration_input = Input("1.0", placeholder="Duration", id="duration-input")
self.opacity_widget = Static(
f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget"
)
yield EasingButtons()
yield Vertical(
Horizontal(
Static("Animation Duration:", id="label"), duration_input, id="inputs"
),
Horizontal(
self.animated_bar,
Container(self.opacity_widget, id="other"),
),
Footer(),
)
def on_button_pressed(self, event: Button.Pressed) -> None:
self.bell()
self.animated_bar.animation_running = True
def _animation_complete():
self.animated_bar.animation_running = False
target_position = (
END_POSITION if self.position == START_POSITION else START_POSITION
)
self.animate(
"position",
value=target_position,
final_value=target_position,
duration=self.duration,
easing=event.button.id,
on_complete=_animation_complete,
)
def watch_position(self, value: int):
self.animated_bar.position = value
self.opacity_widget.styles.opacity = 1 - value / END_POSITION
def on_input_changed(self, event: Input.Changed):
if event.sender.id == "duration-input":
new_duration = _try_float(event.value)
if new_duration is not None:
self.duration = new_duration
def action_toggle_dark(self):
self.dark = not self.dark
def _try_float(string: str) -> float | None:
try:
return float(string)
except ValueError:
return None
app = EasingApp(css_path="easing.css")
if __name__ == "__main__":
app.run()
Classes
class Bar (*children: Widget, name: str | None = None, id: str | None = None, classes: str | None = None)-
A Widget is the base class for Textual widgets.
See also [static][textual.widgets._static.Static] for starting point for your own widgets.
Expand source code
class Bar(Widget): position = Reactive.init(START_POSITION) animation_running = Reactive(False) DEFAULT_CSS = """ Bar { background: $surface; color: $error; } Bar.-active { background: $surface; color: $success; } """ def watch_animation_running(self, running: bool) -> None: self.set_class(running, "-active") def render(self) -> RenderableType: return ScrollBarRender( virtual_size=VIRTUAL_SIZE, window_size=WINDOW_SIZE, position=self.position, style=self.rich_style, )Ancestors
Class variables
var COMPONENT_CLASSES : ClassVar[set[str]]var DEFAULT_CSS
Instance variables
var animation_running : ReactiveType-
Reactive descriptor.
Args
- default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
layout:bool, optional- Perform a layout on change. Defaults to False.
repaint:bool, optional- Perform a repaint on change. Defaults to True.
init:bool, optional- Call watchers on initialize (post mount). Defaults to False.
Expand source code
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType: value: _NotSet | ReactiveType = getattr(obj, self.internal_name, _NOT_SET) if isinstance(value, _NotSet): # No value present, we need to set the default init_name = f"_default_{self.name}" default = getattr(obj, init_name) default_value = default() if callable(default) else default # Set and return the value setattr(obj, self.internal_name, default_value) if self._init: self._check_watchers(obj, self.name, default_value, first_set=True) return default_value return value var position : ReactiveType-
Reactive descriptor.
Args
- default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
layout:bool, optional- Perform a layout on change. Defaults to False.
repaint:bool, optional- Perform a repaint on change. Defaults to True.
init:bool, optional- Call watchers on initialize (post mount). Defaults to False.
Expand source code
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType: value: _NotSet | ReactiveType = getattr(obj, self.internal_name, _NOT_SET) if isinstance(value, _NotSet): # No value present, we need to set the default init_name = f"_default_{self.name}" default = getattr(obj, init_name) default_value = default() if callable(default) else default # Set and return the value setattr(obj, self.internal_name, default_value) if self._init: self._check_watchers(obj, self.name, default_value, first_set=True) return default_value return value
Methods
def watch_animation_running(self, running: bool) ‑> None-
Expand source code
def watch_animation_running(self, running: bool) -> None: self.set_class(running, "-active")
Inherited members
Widget:actionadd_classallow_horizontal_scrollallow_vertical_scrollancestorsanimateappauto_heightauto_linksauto_widthbackground_colorscall_latercan_focuscan_focus_childrencapture_mousecheck_idleclassescolorscomposecontainer_sizecontainer_viewportcontent_offsetcontent_regioncontent_sizecss_identifiercss_identifier_styledcss_path_nodesdisable_messagesdispatch_keydisplaydisplayed_childrenemitemit_no_waitenable_messagesexpandfocusfocusable_childrenget_childget_component_rich_styleget_component_stylesget_content_heightget_content_widthget_default_cssget_pseudo_classesget_style_atgutterhas_classhas_focushas_pseudo_classhighlight_link_idhorizontal_scrollbarhover_styleidis_containeris_scrollableis_transparentlayerlayerslink_hover_stylelink_stylelogmax_scroll_xmax_scroll_ymountmouse_overoffseton_eventouter_sizeparentpost_messagepost_message_no_waitpost_renderpseudo_classesqueryquery_onerefreshregionrelease_mouseremoveremove_classrenderrender_linerender_linesreset_focusreset_stylesrich_stylescreenscroll_downscroll_endscroll_homescroll_leftscroll_offsetscroll_page_downscroll_page_leftscroll_page_rightscroll_page_upscroll_relativescroll_rightscroll_target_xscroll_target_yscroll_toscroll_to_regionscroll_to_widgetscroll_upscroll_visiblescroll_xscroll_yscrollbar_cornerscrollbar_gutterscrollbar_size_horizontalscrollbar_size_verticalscrollbars_enabledset_classset_intervalset_stylesset_timershow_horizontal_scrollbarshow_vertical_scrollbarshrinksiblingssizetext_styletoggle_classtreevertical_scrollbarvirtual_regionvirtual_region_with_marginvirtual_sizevisiblevisible_siblingswalk_childrenwatch_has_focuswatch_mouse_overwindow_region
class EasingApp (driver_class: Type[Driver] | None = None, css_path: CSSPathType = None, watch_css: bool = False)-
The base class for Textual Applications.
Args
- driver_class (Type[Driver] | None, optional): Driver class or
Noneto auto-detect. Defaults to None. - title (str | None, optional): Title of the application. If
None, the title is set to the name of theAppsubclass. Defaults toNone. - css_path (str | PurePath | None, optional): Path to CSS or
Nonefor no CSS file. Defaults to None. watch_css:bool, optional- Watch CSS for changes. Defaults to False.
Expand source code
class EasingApp(App): position = Reactive.init(START_POSITION) duration = Reactive.var(1.0) def on_load(self): self.bind( "ctrl+p", "focus('duration-input')", description="Focus: Duration Input" ) self.bind("ctrl+b", "toggle_dark", description="Toggle Dark") def compose(self) -> ComposeResult: self.animated_bar = Bar() self.animated_bar.position = START_POSITION duration_input = Input("1.0", placeholder="Duration", id="duration-input") self.opacity_widget = Static( f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget" ) yield EasingButtons() yield Vertical( Horizontal( Static("Animation Duration:", id="label"), duration_input, id="inputs" ), Horizontal( self.animated_bar, Container(self.opacity_widget, id="other"), ), Footer(), ) def on_button_pressed(self, event: Button.Pressed) -> None: self.bell() self.animated_bar.animation_running = True def _animation_complete(): self.animated_bar.animation_running = False target_position = ( END_POSITION if self.position == START_POSITION else START_POSITION ) self.animate( "position", value=target_position, final_value=target_position, duration=self.duration, easing=event.button.id, on_complete=_animation_complete, ) def watch_position(self, value: int): self.animated_bar.position = value self.opacity_widget.styles.opacity = 1 - value / END_POSITION def on_input_changed(self, event: Input.Changed): if event.sender.id == "duration-input": new_duration = _try_float(event.value) if new_duration is not None: self.duration = new_duration def action_toggle_dark(self): self.dark = not self.darkAncestors
- App
- typing.Generic
- DOMNode
- MessagePump
Class variables
var CSS_PATH : CSSPathTypevar SCREENS : dict[str, Screen]var SUB_TITLE : str | Nonevar TITLE : str | None
Instance variables
var duration : ReactiveType-
Reactive descriptor.
Args
- default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
layout:bool, optional- Perform a layout on change. Defaults to False.
repaint:bool, optional- Perform a repaint on change. Defaults to True.
init:bool, optional- Call watchers on initialize (post mount). Defaults to False.
Expand source code
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType: value: _NotSet | ReactiveType = getattr(obj, self.internal_name, _NOT_SET) if isinstance(value, _NotSet): # No value present, we need to set the default init_name = f"_default_{self.name}" default = getattr(obj, init_name) default_value = default() if callable(default) else default # Set and return the value setattr(obj, self.internal_name, default_value) if self._init: self._check_watchers(obj, self.name, default_value, first_set=True) return default_value return value var position : ReactiveType-
Reactive descriptor.
Args
- default (ReactiveType | Callable[[], ReactiveType]): A default value or callable that returns a default.
layout:bool, optional- Perform a layout on change. Defaults to False.
repaint:bool, optional- Perform a repaint on change. Defaults to True.
init:bool, optional- Call watchers on initialize (post mount). Defaults to False.
Expand source code
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType: value: _NotSet | ReactiveType = getattr(obj, self.internal_name, _NOT_SET) if isinstance(value, _NotSet): # No value present, we need to set the default init_name = f"_default_{self.name}" default = getattr(obj, init_name) default_value = default() if callable(default) else default # Set and return the value setattr(obj, self.internal_name, default_value) if self._init: self._check_watchers(obj, self.name, default_value, first_set=True) return default_value return value
Methods
-
Expand source code
def on_button_pressed(self, event: Button.Pressed) -> None: self.bell() self.animated_bar.animation_running = True def _animation_complete(): self.animated_bar.animation_running = False target_position = ( END_POSITION if self.position == START_POSITION else START_POSITION ) self.animate( "position", value=target_position, final_value=target_position, duration=self.duration, easing=event.button.id, on_complete=_animation_complete, ) def on_input_changed(self, event: Input.Changed)-
Expand source code
def on_input_changed(self, event: Input.Changed): if event.sender.id == "duration-input": new_duration = _try_float(event.value) if new_duration is not None: self.duration = new_duration def on_load(self)-
Expand source code
def on_load(self): self.bind( "ctrl+p", "focus('duration-input')", description="Focus: Duration Input" ) self.bind("ctrl+b", "toggle_dark", description="Toggle Dark") def watch_position(self, value: int)-
Expand source code
def watch_position(self, value: int): self.animated_bar.position = value self.opacity_widget.styles.opacity = 1 - value / END_POSITION
Inherited members
App:actionaction_bellaction_focusaction_pop_screenaction_push_screenaction_quitaction_screenshotaction_switch_screenaction_toggle_darkadd_classancestorsanimateappbackground_colorsbellbindcall_latercapture_mousecheck_bindingscheck_idleclassescolorscomposecss_identifiercss_identifier_styledcss_path_nodesdarkdebugdisable_messagesdispatch_keydisplaydisplayed_childrenemitemit_no_waitenable_messagesexitexport_screenshotfatal_errorfocusedget_childget_component_stylesget_css_variablesget_default_cssget_driver_classget_pseudo_classesget_screenget_widget_athas_classhas_pseudo_classidinstall_screenis_headlessis_mountedis_screen_installedlogmountmount_allnamespace_bindingson_eventpanicparentpop_screenpost_messagepost_message_no_waitpseudo_classespush_screenqueryquery_onerefresh_cssremove_classreset_stylesrich_stylerunsave_screenshotscreenscreen_stackset_classset_focusset_intervalset_stylesset_timersizesub_titleswitch_screentext_styletitletoggle_classtreeuninstall_screenupdate_stylesvisiblewalk_childrenwatch_dark
- driver_class (Type[Driver] | None, optional): Driver class or
class EasingButtons (*children: Widget, name: str | None = None, id: str | None = None, classes: str | None = None)-
A Widget is the base class for Textual widgets.
See also [static][textual.widgets._static.Static] for starting point for your own widgets.
Expand source code
class EasingButtons(Widget): def compose(self) -> ComposeResult: for easing in sorted(EASING, reverse=True): yield Button(easing, id=easing)Ancestors
Class variables
var COMPONENT_CLASSES : ClassVar[set[str]]
Inherited members
Widget:actionadd_classallow_horizontal_scrollallow_vertical_scrollancestorsanimateappauto_heightauto_linksauto_widthbackground_colorscall_latercan_focuscan_focus_childrencapture_mousecheck_idleclassescolorscomposecontainer_sizecontainer_viewportcontent_offsetcontent_regioncontent_sizecss_identifiercss_identifier_styledcss_path_nodesdisable_messagesdispatch_keydisplaydisplayed_childrenemitemit_no_waitenable_messagesexpandfocusfocusable_childrenget_childget_component_rich_styleget_component_stylesget_content_heightget_content_widthget_default_cssget_pseudo_classesget_style_atgutterhas_classhas_focushas_pseudo_classhighlight_link_idhorizontal_scrollbarhover_styleidis_containeris_scrollableis_transparentlayerlayerslink_hover_stylelink_stylelogmax_scroll_xmax_scroll_ymountmouse_overoffseton_eventouter_sizeparentpost_messagepost_message_no_waitpost_renderpseudo_classesqueryquery_onerefreshregionrelease_mouseremoveremove_classrenderrender_linerender_linesreset_focusreset_stylesrich_stylescreenscroll_downscroll_endscroll_homescroll_leftscroll_offsetscroll_page_downscroll_page_leftscroll_page_rightscroll_page_upscroll_relativescroll_rightscroll_target_xscroll_target_yscroll_toscroll_to_regionscroll_to_widgetscroll_upscroll_visiblescroll_xscroll_yscrollbar_cornerscrollbar_gutterscrollbar_size_horizontalscrollbar_size_verticalscrollbars_enabledset_classset_intervalset_stylesset_timershow_horizontal_scrollbarshow_vertical_scrollbarshrinksiblingssizetext_styletoggle_classtreevertical_scrollbarvirtual_regionvirtual_region_with_marginvirtual_sizevisiblevisible_siblingswalk_childrenwatch_has_focuswatch_mouse_overwindow_region