mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Cleanup easing example
This commit is contained in:
@@ -5,10 +5,14 @@ from textual.widgets import Static
|
||||
|
||||
|
||||
class JustABox(App):
|
||||
|
||||
css_path = "../darren/just_a_box.css"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Static("Hello, world!", classes="box1")
|
||||
|
||||
|
||||
app = JustABox(watch_css=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = JustABox(css_path="../darren/just_a_box.css", watch_css=True)
|
||||
app.run()
|
||||
|
||||
@@ -3,6 +3,38 @@ EasingButtons > Button {
|
||||
}
|
||||
EasingButtons {
|
||||
dock: left;
|
||||
width: 20;
|
||||
overflow: auto auto;
|
||||
width: 20;
|
||||
}
|
||||
|
||||
#bar-container {
|
||||
content-align: center middle;
|
||||
}
|
||||
|
||||
#duration-input {
|
||||
width: 30;
|
||||
}
|
||||
|
||||
#inputs {
|
||||
padding: 1;
|
||||
height: auto;
|
||||
dock: top;
|
||||
}
|
||||
|
||||
Bar {
|
||||
width: 1fr;
|
||||
}
|
||||
|
||||
#other {
|
||||
width: 1fr;
|
||||
background: #555555;
|
||||
padding: 1;
|
||||
height: 100%;
|
||||
border-left: vkey $background;
|
||||
}
|
||||
|
||||
#opacity-widget {
|
||||
padding: 1;
|
||||
background: $panel;
|
||||
border: wide #969696;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import RenderableType
|
||||
|
||||
from textual import layout
|
||||
from textual._easing import EASING
|
||||
from textual.app import ComposeResult, App
|
||||
from textual.cli.previews.borders import TEXT
|
||||
from textual.reactive import Reactive
|
||||
from textual.scrollbar import ScrollBarRender
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Button, Static
|
||||
from textual.widgets import Button, Static, Footer
|
||||
from textual.widgets.text_input import TextWidgetBase, TextInput
|
||||
|
||||
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 EASING:
|
||||
yield Button(easing)
|
||||
for easing in sorted([e for e in EASING if e != "round"], reverse=True):
|
||||
yield Button(easing, id=easing)
|
||||
|
||||
|
||||
class Bar(Widget):
|
||||
position = Reactive.init(START_POSITION)
|
||||
animation_running = Reactive(False)
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return ScrollBarRender(
|
||||
virtual_size=VIRTUAL_SIZE,
|
||||
window_size=WINDOW_SIZE,
|
||||
position=self.position,
|
||||
style="green" if self.animation_running else "red",
|
||||
)
|
||||
|
||||
|
||||
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 = TextInput(
|
||||
placeholder="Duration", initial="1.0", id="duration-input"
|
||||
)
|
||||
|
||||
self.opacity_widget = Static(
|
||||
f"[b]Welcome to Textual![/]\n\n{TEXT}", id="opacity-widget"
|
||||
)
|
||||
|
||||
yield EasingButtons()
|
||||
self.text = Static("Easing examples")
|
||||
yield self.text
|
||||
yield layout.Vertical(
|
||||
layout.Vertical(Static("Animation Duration:"), duration_input, id="inputs"),
|
||||
layout.Horizontal(
|
||||
self.animated_bar,
|
||||
layout.Container(self.opacity_widget, id="other"),
|
||||
),
|
||||
Footer(),
|
||||
)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
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_text_widget_base_changed(self, event: TextWidgetBase.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", watch_css=True)
|
||||
|
||||
Reference in New Issue
Block a user