demo updates

This commit is contained in:
Will McGugan
2024-11-14 13:00:40 +00:00
parent 9f32476af2
commit dcfbbc38b6
14 changed files with 133 additions and 10 deletions

View File

@@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `can_focus` and `can_focus_children` parameters to scrollable container types. https://github.com/Textualize/textual/pull/5226
- Added `textual.lazy.Reveal` https://github.com/Textualize/textual/pull/5226
- Added `Screen.action_blur` https://github.com/Textualize/textual/pull/5226
- `Click` events can now be used with the on decorator to match the initially clicked widget
### Changed

View File

@@ -93,6 +93,7 @@ class XTermParser(Parser[Message]):
event_class = events.MouseDown if state == "M" else events.MouseUp
event = event_class(
None,
x,
y,
delta_x,

View File

@@ -3702,7 +3702,9 @@ class App(Generic[ReturnType], DOMNode):
self.get_widget_at(event.x, event.y)[0]
is self._mouse_down_widget
):
click_event = events.Click.from_event(event)
click_event = events.Click.from_event(
self._mouse_down_widget, event
)
self.screen._forward_event(click_event)
except NoWidget:
pass

View File

@@ -15,6 +15,14 @@ class DemoApp(App):
align: center top;
&>*{ max-width: 100; }
}
Screen .-maximized {
margin: 1 2;
max-width: 100%;
&.column { margin: 1 2; padding: 1 2; }
&.column > * {
max-width: 100%;
}
}
"""
MODES = {
@@ -48,4 +56,30 @@ class DemoApp(App):
"Screenshot",
tooltip="Save an SVG 'screenshot' of the current screen",
),
Binding(
"ctrl+m",
"app.maximize",
"Maximize",
tooltip="Maximized the focused widget (if possible)",
),
]
def action_maximize(self) -> None:
if self.screen.focused is None:
self.notify(
"Nothing to be maximized (try pressing [b]tab[/b])",
title="Maximize",
severity="warning",
)
else:
if self.screen.maximize(self.screen.focused):
self.notify(
"You are now in the maximized view. Press [b]escape[/b] to return.",
title="Maximize",
)
else:
self.notify(
"This widget may not be maximized.",
title="Maximize",
severity="warning",
)

View File

@@ -145,7 +145,7 @@ class StarCount(Vertical):
color: $text-warning;
#stars { align: center top; }
#forks { align: right top; }
Label { text-style: bold; }
Label { text-style: bold; color: $foreground; }
LoadingIndicator { background: transparent !important; }
Digits { width: auto; margin-right: 1; }
Label { margin-right: 1; }

View File

@@ -8,13 +8,14 @@ from rich.syntax import Syntax
from rich.table import Table
from rich.traceback import Traceback
from textual import containers, lazy
from textual import containers, events, lazy, on
from textual.app import ComposeResult
from textual.binding import Binding
from textual.demo.data import COUNTRIES
from textual.demo.page import PageScreen
from textual.reactive import reactive, var
from textual.suggester import SuggestFromList
from textual.theme import BUILTIN_THEMES
from textual.widgets import (
Button,
Checkbox,
@@ -33,6 +34,7 @@ from textual.widgets import (
RadioSet,
RichLog,
Sparkline,
Switch,
TabbedContent,
)
@@ -49,6 +51,7 @@ The following list is *not* exhaustive…
class Buttons(containers.VerticalGroup):
"""Buttons demo."""
ALLOW_MAXIMIZE = True
DEFAULT_CLASSES = "column"
DEFAULT_CSS = """
Buttons {
@@ -179,6 +182,7 @@ Cells may be individually styled, and may include Rich renderables.
class Inputs(containers.VerticalGroup):
"""Demonstrates Inputs."""
ALLOW_MAXIMIZE = True
DEFAULT_CLASSES = "column"
INPUTS_MD = """\
## Inputs and MaskedInputs
@@ -234,6 +238,7 @@ Build for intuitive and user-friendly forms.
class ListViews(containers.VerticalGroup):
"""Demonstrates List Views and Option Lists."""
ALLOW_MAXIMIZE = True
DEFAULT_CLASSES = "column"
LISTS_MD = """\
## List Views and Option Lists
@@ -435,6 +440,59 @@ For detailed graphs, see [textual-plotext](https://github.com/Textualize/textual
self.data = [abs(sin(x / 3.14)) for x in range(offset, offset + 360 * 6, 20)]
class Switches(containers.VerticalGroup):
"""Demonstrate the Switch widget."""
ALLOW_MAXIMIZE = True
DEFAULT_CLASSES = "column"
SWITCHES_MD = """\
## Switches
Functionally almost identical to a Checkbox, but more displays more prominently in the UI.
"""
DEFAULT_CSS = """\
Switches {
Label {
padding: 1;
&:hover {text-style:underline; }
}
}
"""
def compose(self) -> ComposeResult:
yield Markdown(self.SWITCHES_MD)
with containers.ItemGrid(min_column_width=32):
for theme in BUILTIN_THEMES:
with containers.HorizontalGroup():
yield Switch(id=theme)
yield Label(theme, name=theme)
@on(events.Click, "Label")
def on_click(self, event: events.Click) -> None:
"""Make the label toggle the switch."""
# TODO: Add a dedicated form label
event.stop()
if event.widget is not None:
self.query_one(f"#{event.widget.name}", Switch).toggle()
def on_switch_changed(self, event: Switch.Changed) -> None:
# Don't issue more Changed events
with self.prevent(Switch.Changed):
# Reset all other switches
for switch in self.query("Switch").results(Switch):
if switch.id != event.switch.id:
switch.value = False
assert event.switch.id is not None
theme_id = event.switch.id
def switch_theme() -> None:
"""Callback to switch the theme."""
self.app.theme = theme_id
# Call after a short delay, so we see the Switch animation
self.set_timer(0.3, switch_theme)
class WidgetsScreen(PageScreen):
"""The Widgets screen"""
@@ -467,4 +525,5 @@ class WidgetsScreen(PageScreen):
yield ListViews()
yield Logs()
yield Sparklines()
yield Switches()
yield Footer()

View File

@@ -267,8 +267,6 @@ class LinuxDriver(Driver):
self.write("\x1b[?25l") # Hide cursor
self.write("\x1b[?1004h") # Enable FocusIn/FocusOut.
self.write("\x1b[>1u") # https://sw.kovidgoyal.net/kitty/keyboard-protocol/
# Disambiguate escape codes https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
self.write("\x1b[=1;u")
self.flush()
self._key_thread = Thread(target=self._run_input_thread)

View File

@@ -327,6 +327,7 @@ class MouseEvent(InputEvent, bubble=True):
- [ ] Verbose
Args:
widget: The widget under the mouse.
x: The relative x coordinate.
y: The relative y coordinate.
delta_x: Change in x since the last message.
@@ -341,6 +342,7 @@ class MouseEvent(InputEvent, bubble=True):
"""
__slots__ = [
"widget",
"x",
"y",
"delta_x",
@@ -356,6 +358,7 @@ class MouseEvent(InputEvent, bubble=True):
def __init__(
self,
widget: Widget | None,
x: int,
y: int,
delta_x: int,
@@ -369,6 +372,8 @@ class MouseEvent(InputEvent, bubble=True):
style: Style | None = None,
) -> None:
super().__init__()
self.widget: Widget | None = widget
"""The widget under the mouse at the time of a click."""
self.x = x
"""The relative x coordinate."""
self.y = y
@@ -392,8 +397,11 @@ class MouseEvent(InputEvent, bubble=True):
self._style = style or Style()
@classmethod
def from_event(cls: Type[MouseEventT], event: MouseEvent) -> MouseEventT:
def from_event(
cls: Type[MouseEventT], widget: Widget, event: MouseEvent
) -> MouseEventT:
new_event = cls(
widget,
event.x,
event.y,
event.delta_x,
@@ -409,6 +417,7 @@ class MouseEvent(InputEvent, bubble=True):
return new_event
def __rich_repr__(self) -> rich.repr.Result:
yield self.widget
yield "x", self.x
yield "y", self.y
yield "delta_x", self.delta_x, 0
@@ -422,6 +431,10 @@ class MouseEvent(InputEvent, bubble=True):
yield "meta", self.meta, False
yield "ctrl", self.ctrl, False
@property
def control(self) -> Widget | None:
return self.widget
@property
def offset(self) -> Offset:
"""The mouse coordinate as an offset.
@@ -478,6 +491,7 @@ class MouseEvent(InputEvent, bubble=True):
def _apply_offset(self, x: int, y: int) -> MouseEvent:
return self.__class__(
self.widget,
x=self.x + x,
y=self.y + y,
delta_x=self.delta_x,

View File

@@ -725,6 +725,8 @@ class MessagePump(metaclass=_MessagePumpMeta):
continue
for attribute, selector in selectors.items():
node = getattr(message, attribute)
if node is None:
break
if not isinstance(node, Widget):
raise OnNoWidget(
f"on decorator can't match against {attribute!r} as it is not a widget."

View File

@@ -15,8 +15,10 @@ def blend_colors(color1: Color, color2: Color, ratio: float) -> Color:
Returns:
A Color representing the blending of the two supplied colors.
"""
assert color1.triplet is not None
assert color2.triplet is not None
# assert color1.triplet is not None
# assert color2.triplet is not None
if color1.triplet is None or color2.triplet is None:
return color2
r1, g1, b1 = color1.triplet
r2, g2, b2 = color2.triplet

View File

@@ -746,12 +746,15 @@ class Screen(Generic[ScreenResultType], Widget):
"""
return self._move_focus(-1, selector)
def maximize(self, widget: Widget, container: bool = True) -> None:
def maximize(self, widget: Widget, container: bool = True) -> bool:
"""Maximize a widget, so it fills the screen.
Args:
widget: Widget to maximize.
container: If one of the widgets ancestors is a maximizeable widget, maximize that instead.
Returns:
`True` if the widget was maximized, otherwise `False`.
"""
if widget.allow_maximize:
if container:
@@ -761,9 +764,10 @@ class Screen(Generic[ScreenResultType], Widget):
break
if maximize_widget.allow_maximize:
self.maximized = maximize_widget
return
return True
self.maximized = widget
return False
def minimize(self) -> None:
"""Restore any maximized widget to normal state."""
@@ -1371,6 +1375,7 @@ class Screen(Generic[ScreenResultType], Widget):
the origin of the specified region.
"""
return events.MouseMove(
event.widget,
event.x - region.x,
event.y - region.y,
event.delta_x,

View File

@@ -25,6 +25,8 @@ class ListView(VerticalScroll, can_focus=True, can_focus_children=False):
index: The index in the list that's currently highlighted.
"""
ALLOW_MAXIMIZE = True
DEFAULT_CSS = """
ListView {
background: $surface;

View File

@@ -25,6 +25,8 @@ class RadioSet(VerticalScroll, can_focus=True, can_focus_children=False):
turned off.
"""
ALLOW_MAXIMIZE = True
DEFAULT_CSS = """
RadioSet {
border: tall $border-blurred;

View File

@@ -50,6 +50,7 @@ class Switch(Widget, can_focus=True):
background: $surface;
height: auto;
width: auto;
padding: 0 2;
&.-on > .switch--slider {
color: $success;