diff --git a/.github/workflows/black_format.yml b/.github/workflows/black_format.yml index 4191936..4318dc2 100644 --- a/.github/workflows/black_format.yml +++ b/.github/workflows/black_format.yml @@ -5,6 +5,10 @@ on: paths: - '.github/workflows/black_format.yml' - '**.py' + push: + paths: + - '.github/workflows/black_format.yml' + - '**.py' jobs: black-format-check: diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 32f3f06..588b094 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -11,7 +11,7 @@ on: - 'Makefile' push: branches: - - 'main' + - '*' jobs: build: @@ -44,10 +44,10 @@ jobs: - name: Install dependencies run: poetry install if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - - name: Run mypy - run: | - source $VENV - mypy . +# - name: Run mypy +# run: | +# source $VENV +# mypy . - name: Test with pytest run: | source $VENV diff --git a/.gitignore b/.gitignore index 7f6b817..4453442 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,9 @@ venv.bak/ .dmypy.json dmypy.json +# Pycharm +.idea/ + # Pyre type checker .pyre/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f53443e..8462cc2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,5 +3,3 @@ repos: rev: 23.3.0 hooks: - id: black - language_version: python3.7 - diff --git a/src/textual_dev/cli.py b/src/textual_dev/cli.py index e3f8e28..d2e80ad 100644 --- a/src/textual_dev/cli.py +++ b/src/textual_dev/cli.py @@ -219,6 +219,14 @@ def colors() -> None: ColorsApp().run() +@run.command("widgets") +def widgets() -> None: + """Explore possible example_widgets.""" + from textual_dev.previews import WidgetsApp + + WidgetsApp().run() + + @run.command("keys") def keys() -> None: """Show key events.""" diff --git a/src/textual_dev/previews/__init__.py b/src/textual_dev/previews/__init__.py index 68facdc..6be8080 100644 --- a/src/textual_dev/previews/__init__.py +++ b/src/textual_dev/previews/__init__.py @@ -4,5 +4,6 @@ from .colors import ColorsApp from .easing import EasingApp from .keys import KeysApp +from .widgets import WidgetsApp -__all__ = ["BorderApp", "ColorsApp", "EasingApp", "KeysApp"] +__all__ = ["BorderApp", "ColorsApp", "EasingApp", "KeysApp", "WidgetsApp"] diff --git a/src/textual_dev/previews/example_widgets/__init__.py b/src/textual_dev/previews/example_widgets/__init__.py new file mode 100644 index 0000000..49fd6a8 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/__init__.py @@ -0,0 +1,158 @@ +import random +from statistics import mean + +from textual.app import ComposeResult +from textual.containers import Container, Center, Middle +from textual.widgets import ( + DirectoryTree, + Footer, + Header, + Input, + Label, + ListView, + ListItem, + LoadingIndicator, + Sparkline, + Static, + Tree, + ProgressBar, + TabbedContent, + TabPane, + Markdown, + Tabs, +) + +from .button import button_example +from .checkbox import checkbox_example +from .content_switcher import content_switcher_example +from .data_table import data_table_example +from .markdown import markdown_viewer_example, markdown_example +from .option_list import option_list_example +from .placeholder import placeholder_example +from .pretty import pretty_example +from .radio import radio_button_example, radio_set_example +from .select import select_example, selection_list_example +from .switch import switch_example + +# from .text_log import text_log_example + + +def directory_tree_example(id: str) -> ComposeResult: + yield Container(DirectoryTree("./"), id=id) + + +def footer_example(id: str) -> ComposeResult: + yield Container(Footer(), id=id) + + +def header_example(id: str) -> ComposeResult: + yield Container(Header(), id=id) + + +def input_example(id: str) -> ComposeResult: + yield Container( + Input(placeholder="First Name"), Input(placeholder="Last Name"), id=id + ) + + +def label_example(id: str) -> ComposeResult: + yield Container(Label("Hello, world!"), id=id) + + +def list_item_example(id: str) -> ComposeResult: + yield Container( + ListView( + ListItem(Label("One")), + ListItem(Label("Two")), + ListItem(Label("Three")), + ), + id=id, + ) + + yield Footer() + + +def loading_example(id: str) -> ComposeResult: + yield Container(LoadingIndicator(), id=id) + + +def sparkline_example(id: str) -> ComposeResult: + data = [random.expovariate(1 / 3) for _ in range(1000)] + + yield Container( + Sparkline(data, summary_function=max), + Sparkline(data, summary_function=mean), + Sparkline(data, summary_function=min), + id=id, + ) + + +def static_example(id: str) -> ComposeResult: + yield Container(Static("Hello, world!"), id=id) + + +def tree_example(id: str) -> ComposeResult: + tree: Tree[dict] = Tree("Dune") + tree.root.expand() + characters = tree.root.add("Characters", expand=True) + characters.add_leaf("Paul") + characters.add_leaf("Jessica") + characters.add_leaf("Chani") + yield Container(tree, id=id) + + +def progress_bar_example(id: str) -> ComposeResult: + bar = ProgressBar(total=100, show_eta=False) + bar.advance(50) + yield Container(Center(Middle(bar)), id=id) + + +def tabbed_content_example(id: str): + content = TabbedContent() + content._tab_content = [ + TabPane("Leto", Markdown("# Leto"), id="leto"), + TabPane("Jessica", Markdown("# Jessica"), id="jessica"), + TabPane("Paul", Markdown("# Paul"), id="paulo"), + ] + # This does not work, reason why I used _tab_content above + # content.add_pane(TabPane("Leto", Markdown("#Leto"), id="letoo")) + # content.add_pane(TabPane("Jessica", Markdown(""), id="jessicoa")) + # content.add_pane(TabPane("Paul", Markdown(""), id="paulo")) + + yield Container(content, id=id) + + +def tabs_example(id: str): + yield Container(Tabs("First tab", "Second tab", "Third tab"), id=id) + + +__all__ = [ + "button_example", + "checkbox_example", + "content_switcher_example", + "data_table_example", + "directory_tree_example", + "footer_example", + "header_example", + "input_example", + "label_example", + "list_item_example", + "loading_example", + "markdown_viewer_example", + "markdown_example", + "option_list_example", + "placeholder_example", + "pretty_example", + "progress_bar_example", + "radio_button_example", + "radio_set_example", + "select_example", + "selection_list_example", + "sparkline_example", + "static_example", + "switch_example", + "tabbed_content_example", + "tabs_example", + "tree_example", + # "text_log_example", +] diff --git a/src/textual_dev/previews/example_widgets/button.py b/src/textual_dev/previews/example_widgets/button.py new file mode 100644 index 0000000..48b5c99 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/button.py @@ -0,0 +1,25 @@ +from textual.app import ComposeResult +from textual.containers import Horizontal, VerticalScroll +from textual.widgets import Static, Button + + +def button_example(id: str) -> ComposeResult: + yield Horizontal( + VerticalScroll( + Static("Standard Buttons", classes="header"), + Button("Default"), + Button("Primary!", variant="primary"), + Button.success("Success!"), + Button.warning("Warning!"), + Button.error("Error!"), + ), + VerticalScroll( + Static("Disabled Buttons", classes="header"), + Button("Default", disabled=True), + Button("Primary!", variant="primary", disabled=True), + Button.success("Success!", disabled=True), + Button.warning("Warning!", disabled=True), + Button.error("Error!", disabled=True), + ), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/checkbox.py b/src/textual_dev/previews/example_widgets/checkbox.py new file mode 100644 index 0000000..718bbe3 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/checkbox.py @@ -0,0 +1,19 @@ +from textual.app import ComposeResult +from textual.containers import VerticalScroll, Container +from textual.widgets import Checkbox + + +def checkbox_example(id: str) -> ComposeResult: + yield Container( + VerticalScroll( + Checkbox("Arrakis :sweat:"), + Checkbox("Caladan"), + Checkbox("Chusuk"), + Checkbox("[b]Giedi Prime[/b]"), + Checkbox("[magenta]Ginaz[/]"), + Checkbox("Grumman", True), + Checkbox("Kaitain", id="initial_focus"), + Checkbox("Novebruns", True), + ), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/content_switcher.py b/src/textual_dev/previews/example_widgets/content_switcher.py new file mode 100644 index 0000000..d71871e --- /dev/null +++ b/src/textual_dev/previews/example_widgets/content_switcher.py @@ -0,0 +1,60 @@ +from textual.app import ComposeResult +from textual.containers import VerticalScroll, Container, Horizontal +from textual.widgets import Button, ContentSwitcher, DataTable, Markdown + +MARKDOWN_EXAMPLE = """# Three Flavours Cornetto + +The Three Flavours Cornetto trilogy is an anthology series of British +comedic genre films directed by Edgar Wright. + +## Shaun of the Dead + +| Flavour | UK Release Date | Director | +| -- | -- | -- | +| Strawberry | 2004-04-09 | Edgar Wright | + +## Hot Fuzz + +| Flavour | UK Release Date | Director | +| -- | -- | -- | +| Classico | 2007-02-17 | Edgar Wright | + +## The World's End + +| Flavour | UK Release Date | Director | +| -- | -- | -- | +| Mint | 2013-07-19 | Edgar Wright | +""" + + +def content_switcher_example(id: str) -> ComposeResult: + table: DataTable = DataTable(id="data-table") + table.add_columns("Book", "Year") + table.add_rows( + [ + (title.ljust(35), year) + for title, year in ( + ("Dune", 1965), + ("Dune Messiah", 1969), + ("Children of Dune", 1976), + ("God Emperor of Dune", 1981), + ("Heretics of Dune", 1984), + ("Chapterhouse: Dune", 1985), + ) + ] + ) + + yield Container( + Horizontal( + Button("DataTable", id="data-table"), + Button("Markdown", id="markdown"), + id="buttons", + ), + ContentSwitcher( + table, + VerticalScroll(Markdown(MARKDOWN_EXAMPLE), id="markdown"), + initial="data-table", + id="content-switcher-example", + ), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/data_table.py b/src/textual_dev/previews/example_widgets/data_table.py new file mode 100644 index 0000000..32c24f5 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/data_table.py @@ -0,0 +1,24 @@ +from textual.containers import Container +from textual.app import ComposeResult +from textual.widgets import DataTable + +ROWS = [ + ("lane", "swimmer", "country", "time"), + (4, "Joseph Schooling", "Singapore", 50.39), + (2, "Michael Phelps", "United States", 51.14), + (5, "Chad le Clos", "South Africa", 51.14), + (6, "László Cseh", "Hungary", 51.14), + (3, "Li Zhuhao", "China", 51.26), + (8, "Mehdy Metella", "France", 51.58), + (7, "Tom Shields", "United States", 51.73), + (1, "Aleksandr Sadovnikov", "Russia", 51.84), + (10, "Darren Burns", "Scotland", 51.84), +] + + +def data_table_example(id: str) -> ComposeResult: + table: DataTable = DataTable() + table.add_columns(*ROWS[0]) + table.add_rows(ROWS[1:]) + + yield Container(table, id=id) diff --git a/src/textual_dev/previews/example_widgets/markdown.py b/src/textual_dev/previews/example_widgets/markdown.py new file mode 100644 index 0000000..63c305c --- /dev/null +++ b/src/textual_dev/previews/example_widgets/markdown.py @@ -0,0 +1,75 @@ +from textual.containers import Container +from textual.app import ComposeResult +from textual.widgets import MarkdownViewer, Markdown + +EXAMPLE_MARKDOWN_VIEWER = """\ +# Markdown Viewer + +This is an example of Textual's `MarkdownViewer` widget. + + +## Features + +Markdown syntax and extensions are supported. + +- Typography *emphasis*, **strong**, `inline code` etc. +- Headers +- Lists (bullet and ordered) +- Syntax highlighted code blocks +- Tables! + +## Tables + +Tables are displayed in a DataTable widget. + +| Name | Type | Default | Description | +| --------------- | ------ | ------- | ---------------------------------- | +| `show_header` | `bool` | `True` | Show the table header | +| `fixed_rows` | `int` | `0` | Number of fixed rows | +| `fixed_columns` | `int` | `0` | Number of fixed columns | +| `zebra_stripes` | `bool` | `False` | Display alternating colors on rows | +| `header_height` | `int` | `1` | Height of header row | +| `show_cursor` | `bool` | `True` | Show a cell cursor | + + +## Code Blocks + +Code blocks are syntax highlighted, with guidelines. + +```python +class ListViewExample(App): + def compose(self) -> ComposeResult: + yield ListView( + ListItem(Label("One")), + ListItem(Label("Two")), + ListItem(Label("Three")), + ) + yield Footer() +``` +""" + +EXAMPLE_MARKDOWN = """\ +# Markdown Document + +This is an example of Textual's `Markdown` widget. + +## Features + +Markdown syntax and extensions are supported. + +- Typography *emphasis*, **strong**, `inline code` etc. +- Headers +- Lists (bullet and ordered) +- Syntax highlighted code blocks +- Tables! +""" + + +def markdown_viewer_example(id: str) -> ComposeResult: + yield Container( + MarkdownViewer(EXAMPLE_MARKDOWN_VIEWER, show_table_of_contents=True), id=id + ) + + +def markdown_example(id: str) -> ComposeResult: + yield Container(Markdown(EXAMPLE_MARKDOWN), id=id) diff --git a/src/textual_dev/previews/example_widgets/option_list.py b/src/textual_dev/previews/example_widgets/option_list.py new file mode 100644 index 0000000..05621c2 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/option_list.py @@ -0,0 +1,33 @@ +from textual.containers import Container +from textual.app import ComposeResult +from textual.widgets import Footer, Header, OptionList +from textual.widgets.option_list import Option, Separator + + +def option_list_example(id: str) -> ComposeResult: + yield Container( + Header(), + OptionList( + Option("Aerilon", id="aer"), + Option("Aquaria", id="aqu"), + Separator(), + Option("Canceron", id="can"), + Option("Caprica", id="cap", disabled=True), + Separator(), + Option("Gemenon", id="gem"), + Separator(), + Option("Leonis", id="leo"), + Option("Libran", id="lib"), + Separator(), + Option("Picon", id="pic"), + Separator(), + Option("Sagittaron", id="sag"), + Option("Scorpia", id="sco"), + Separator(), + Option("Tauron", id="tau"), + Separator(), + Option("Virgon", id="vir"), + ), + Footer(), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/placeholder.py b/src/textual_dev/previews/example_widgets/placeholder.py new file mode 100644 index 0000000..69a4ef8 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/placeholder.py @@ -0,0 +1,33 @@ +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, VerticalScroll +from textual.widgets import Placeholder + + +def placeholder_example(id: str) -> ComposeResult: + yield Container( + VerticalScroll( + Container( + Placeholder("This is a custom label for p1.", id="p1"), + Placeholder("Placeholder p2 here!", id="p2"), + Placeholder(id="p3"), + Placeholder(id="p4"), + Placeholder(id="p5"), + Placeholder(), + Horizontal( + Placeholder(variant="size", id="col1"), + Placeholder(variant="text", id="col2"), + Placeholder(variant="size", id="col3"), + id="c1", + ), + id="bot", + ), + Container( + Placeholder(variant="text", id="left"), + Placeholder(variant="size", id="topright"), + Placeholder(variant="text", id="botright"), + id="top", + ), + id="content", + ), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/pretty.py b/src/textual_dev/previews/example_widgets/pretty.py new file mode 100644 index 0000000..f2fde96 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/pretty.py @@ -0,0 +1,18 @@ +from textual.containers import Container +from textual.app import ComposeResult +from textual.widgets import Pretty + +DATA = { + "title": "Back to the Future", + "releaseYear": 1985, + "director": "Robert Zemeckis", + "genre": "Adventure, Comedy, Sci-Fi", + "cast": [ + {"actor": "Michael J. Fox", "character": "Marty McFly"}, + {"actor": "Christopher Lloyd", "character": "Dr. Emmett Brown"}, + ], +} + + +def pretty_example(id: str) -> ComposeResult: + yield Container(Pretty(DATA), id=id) diff --git a/src/textual_dev/previews/example_widgets/radio.py b/src/textual_dev/previews/example_widgets/radio.py new file mode 100644 index 0000000..e874bc4 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/radio.py @@ -0,0 +1,55 @@ +from textual.app import ComposeResult +from textual.containers import Container, Horizontal +from textual.widgets import RadioSet, RadioButton + + +def radio_button_example(id: str) -> ComposeResult: + yield Container( + RadioSet( + RadioButton("Battlestar Galactica"), + RadioButton("Dune 1984"), + RadioButton("Dune 2021", id="focus_me"), + RadioButton("Serenity", value=True), + RadioButton("Star Trek: The Motion Picture"), + RadioButton("Star Wars: A New Hope"), + RadioButton("The Last Starfighter"), + RadioButton("Total Recall :backhand_index_pointing_right: :red_circle:"), + RadioButton("Wing Commander"), + ), + id=id, + ) + + +def radio_set_example(id: str) -> ComposeResult: + yield Container( + Horizontal( + RadioSet( + RadioButton("Battlestar Galactica"), + RadioButton("Dune 1984"), + RadioButton("Dune 2021"), + RadioButton("Serenity", value=True), + RadioButton("Star Trek: The Motion Picture"), + RadioButton("Star Wars: A New Hope"), + RadioButton("The Last Starfighter"), + RadioButton( + "Total Recall :backhand_index_pointing_right: :red_circle:" + ), + RadioButton("Wing Commander"), + id="focus_me", + ) + ), + Horizontal( + RadioSet( + "Amanda", + "Connor MacLeod", + "Duncan MacLeod", + "Heather MacLeod", + "Joe Dawson", + "Kurgan, [bold italic red]The[/]", + "Methos", + "Rachel Ellenstein", + "Ramírez", + ) + ), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/select.py b/src/textual_dev/previews/example_widgets/select.py new file mode 100644 index 0000000..e478936 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/select.py @@ -0,0 +1,33 @@ +from textual.app import ComposeResult +from textual.containers import Container +from textual.widgets import Select, Header, Footer, SelectionList + +LINES = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +I will permit it to pass over me and through me.""".splitlines() + + +def select_example(id: str) -> ComposeResult: + content = Select((line, line) for line in LINES) + yield Container(Header(), content, id=id) + + +def selection_list_example(id: str) -> ComposeResult: + yield Container( + Header(), + SelectionList[int]( + ("Falken's Maze", 0, True), + ("Black Jack", 1), + ("Gin Rummy", 2), + ("Hearts", 3), + ("Bridge", 4), + ("Checkers", 5), + ("Chess", 6, True), + ("Poker", 7), + ("Fighter Combat", 8, True), + ), + Footer(), + id=id, + ) diff --git a/src/textual_dev/previews/example_widgets/switch.py b/src/textual_dev/previews/example_widgets/switch.py new file mode 100644 index 0000000..dc11608 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/switch.py @@ -0,0 +1,31 @@ +from textual.app import ComposeResult +from textual.containers import Horizontal, Container +from textual.widgets import Switch, Static + + +def switch_example(id: str) -> ComposeResult: + focused_switch = Switch() + focused_switch.focus() + + yield Container( + Static("[b]Example switches\n", classes="label"), + Horizontal( + Static("off: ", classes="label"), + Switch(animate=False), + classes="container", + ), + Horizontal( + Static("on: ", classes="label"), + Switch(value=True), + classes="container", + ), + Horizontal( + Static("focused: ", classes="label"), focused_switch, classes="container" + ), + Horizontal( + Static("custom: ", classes="label"), + Switch(id="custom-design"), + classes="container", + ), + id=id, + ) diff --git a/src/textual_dev/previews/widgets.css b/src/textual_dev/previews/widgets.css new file mode 100644 index 0000000..6c5c926 --- /dev/null +++ b/src/textual_dev/previews/widgets.css @@ -0,0 +1,165 @@ +Screen { + align: center middle; + overflow: auto; +} + +#Button Button { + margin: 1 2; +} + +#Button VerticalScroll { + width: 24; +} + +#Checkbox VerticalScroll { + width: auto; + height: auto; + background: $boost; + padding: 2; +} + +#ListItem ListView { + width: 34; + height: auto; + margin: 2 2; +} + +#ListItem Label { + padding: 1 2; +} + + +#OptionList OptionList { + width: 70%; + height: 80%; +} + + +/*Placeholder*/ + +#top { + height: 50%; + width: 100%; + layout: grid; + grid-size: 2 2; +} + +#left { + row-span: 2; +} + +#bot { + height: 50%; + width: 100%; + layout: grid; + grid-size: 8 8; +} + +#c1 { + row-span: 4; + column-span: 8; + height: 100%; +} + +#col1, #col2, #col3 { + width: 1fr; +} + +#p1 { + row-span: 4; + column-span: 4; +} + +#p2 { + row-span: 2; + column-span: 4; +} + +#p3 { + row-span: 2; + column-span: 2; +} + +#p4 { + row-span: 1; + column-span: 2; +} + +#RadioButton RadioSet { + width: 50%; +} + +#RadioSet Horizontal { + align: center middle; + height: auto; +} + +#RadioSet RadioSet { + width: 45%; +} + +#Select Select { + width: 60; + margin: 2; +} + +#SelectionList SelectionList { + padding: 1; + border: solid $accent; + width: 80%; + height: 80%; +} + + + +#Switch { + align: center middle; +} + +#Switch .container { + height: auto; + width: auto; +} + +#Switch Switch { + height: auto; + width: auto; +} + +#Switch .label { + height: 3; + content-align: center middle; + width: auto; +} + +#custom-design { + background: darkslategrey; +} + +#custom-design > .switch--slider { + color: dodgerblue; + background: darkslateblue; +} + +#ContentSwitcher #buttons { + height: 3; + width: auto; +} + +#ContentSwitcher ContentSwitcher { + background: $panel; + border: round $primary; + width: 90%; + height: 1fr; +} + +#ContentSwitcher DataTable { + background: $panel; +} + +#ContentSwitcher MarkdownH2 { + background: $primary; + color: yellow; + border: none; + padding: 0; +} \ No newline at end of file diff --git a/src/textual_dev/previews/widgets.py b/src/textual_dev/previews/widgets.py new file mode 100644 index 0000000..0c878d2 --- /dev/null +++ b/src/textual_dev/previews/widgets.py @@ -0,0 +1,110 @@ +from textual.app import App, ComposeResult +from textual.containers import Vertical +from textual.widgets import Button, ContentSwitcher + +from textual_dev.previews.example_widgets import ( + option_list_example, + button_example, + checkbox_example, + list_item_example, + data_table_example, + markdown_viewer_example, + placeholder_example, + pretty_example, + directory_tree_example, + footer_example, + header_example, + input_example, + label_example, + loading_example, + markdown_example, + radio_button_example, + radio_set_example, + select_example, + selection_list_example, + sparkline_example, + static_example, + switch_example, + tree_example, + content_switcher_example, + progress_bar_example, + tabbed_content_example, + tabs_example, + # text_log_example, +) + +WIDGETS = { + "Button": button_example, + # "Checkbox": checkbox_example, + # "ContentSwitcher": content_switcher_example, + # "DataTable": data_table_example, + # "DirectoryTree": directory_tree_example, + # "Footer": footer_example, + # "Header": header_example, + # "Input": input_example, + # "Label": label_example, + # "ListItem": list_item_example, + # # ListView missing + # "Loading": loading_example, + # "MarkdownViewer": markdown_viewer_example, + # "Markdown": markdown_example, + # "OptionList": option_list_example, + # "Placeholder": placeholder_example, + # "Pretty": pretty_example, + # "ProgressBar": progress_bar_example, + # "RadioButton": radio_button_example, + # "RadioSet": radio_set_example, + # "Select": select_example, + # "SelectionList": selection_list_example, + # "Sparkline": sparkline_example, + # "Static": static_example, + # "Switch": switch_example, + # "TabbedContent": tabbed_content_example, + # "Tabs": tabs_example, + # "Tree": tree_example, +} + + +class WidgetButtons(Vertical): + DEFAULT_CSS = """ + WidgetButtons { + dock: left; + width: 32; + overflow-y: scroll; + } + + WidgetButtons > Button { + width: 100%; + } + """ + + def compose(self) -> ComposeResult: + for widget in WIDGETS.keys(): + yield Button(widget, id=widget) + + +class WidgetsApp(App[None]): + """Demonstrates widget types.""" + + CSS_PATH = "widgets.css" + + def compose(self) -> ComposeResult: + yield WidgetButtons() + + first_button = list(WIDGETS.keys())[0] + + with ContentSwitcher(initial=first_button, id="main-switcher"): + for widget_name, widget in WIDGETS.items(): + yield from widget(id=widget_name) + + def on_button_pressed(self, event: Button.Pressed) -> None: + widget_selection_button = event.button.id in WIDGETS.keys() + + selector = ( + "#main-switcher" if widget_selection_button else "#content-switcher-example" + ) + self.query_one(selector).current = event.button.id + + +if __name__ == "__main__": + WidgetsApp().run() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/devtools/test_devtools.py b/tests/devtools/test_devtools.py index b3d9e89..e629532 100644 --- a/tests/devtools/test_devtools.py +++ b/tests/devtools/test_devtools.py @@ -7,9 +7,9 @@ from rich.console import Console from rich.segment import Segment +from tests.utilities.wait_for_predicate import wait_for_predicate from textual_dev.renderables import DevConsoleLog, DevConsoleNotice -from utilities.wait_for_predicate import wait_for_predicate TIMESTAMP = 1649166819 WIDTH = 40 diff --git a/tests/devtools/test_devtools_client.py b/tests/devtools/test_devtools_client.py index 856e8df..cef2c78 100644 --- a/tests/devtools/test_devtools_client.py +++ b/tests/devtools/test_devtools_client.py @@ -9,8 +9,9 @@ from rich.console import ConsoleDimensions from rich.panel import Panel from textual.constants import DEVTOOLS_PORT + +from tests.utilities.wait_for_predicate import wait_for_predicate from textual_dev.client import DevtoolsClient, DevtoolsLog -from utilities.wait_for_predicate import wait_for_predicate CALLER_LINENO = 123 CALLER_PATH = "a/b/c.py" diff --git a/tests/test_cli.py b/tests/test_cli.py index 4909650..a575298 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,3 +8,21 @@ def test_cli_version(): runner = CliRunner() result = runner.invoke(run, ["--version"]) assert version("textual") in result.output + + +# def test_cli_widgets(): +# runner = CliRunner() +# result = runner.invoke(run, ["widgets"]) +# assert result.exit_code == 0 + + +def test_cli_diagnose(): + runner = CliRunner() + result = runner.invoke(run, ["diagnose"]) + assert result.exit_code == 0 + + +def test_cli_keys(): + runner = CliRunner() + result = runner.invoke(run, ["keys"]) + assert result.exit_code == 0