From 67d139fd3861d875a3d455dd909043420b4d6748 Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Sat, 22 Jul 2023 12:26:40 +0200 Subject: [PATCH 1/9] Add .idea/ to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) 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/ From a6b4280b90f9c823a911972be51cca0dcad3716e Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Sat, 22 Jul 2023 12:31:58 +0200 Subject: [PATCH 2/9] Remove dependency for Python 3.7 from pre-commit --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) 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 - From bb57f01029e72a8eb838bb9109d30ca53788b30b Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:04:26 +0200 Subject: [PATCH 3/9] Test --- .github/workflows/black_format.yml | 4 + .github/workflows/pythonpackage.yml | 2 +- src/textual_dev/cli.py | 8 + src/textual_dev/previews/__init__.py | 3 +- .../previews/example_widgets/__init__.py | 158 +++++++++++++++++ .../previews/example_widgets/button.py | 25 +++ .../previews/example_widgets/checkbox.py | 19 ++ .../example_widgets/content_switcher.py | 60 +++++++ .../previews/example_widgets/data_table.py | 24 +++ .../previews/example_widgets/markdown.py | 75 ++++++++ .../previews/example_widgets/option_list.py | 33 ++++ .../previews/example_widgets/placeholder.py | 33 ++++ .../previews/example_widgets/pretty.py | 18 ++ .../previews/example_widgets/radio.py | 55 ++++++ .../previews/example_widgets/select.py | 33 ++++ .../previews/example_widgets/switch.py | 31 ++++ .../previews/example_widgets/text_log.py | 24 +++ src/textual_dev/previews/widgets.css | 165 ++++++++++++++++++ src/textual_dev/previews/widgets.py | 111 ++++++++++++ tests/__init__.py | 0 tests/test_cli.py | 6 + 21 files changed, 885 insertions(+), 2 deletions(-) create mode 100644 src/textual_dev/previews/example_widgets/__init__.py create mode 100644 src/textual_dev/previews/example_widgets/button.py create mode 100644 src/textual_dev/previews/example_widgets/checkbox.py create mode 100644 src/textual_dev/previews/example_widgets/content_switcher.py create mode 100644 src/textual_dev/previews/example_widgets/data_table.py create mode 100644 src/textual_dev/previews/example_widgets/markdown.py create mode 100644 src/textual_dev/previews/example_widgets/option_list.py create mode 100644 src/textual_dev/previews/example_widgets/placeholder.py create mode 100644 src/textual_dev/previews/example_widgets/pretty.py create mode 100644 src/textual_dev/previews/example_widgets/radio.py create mode 100644 src/textual_dev/previews/example_widgets/select.py create mode 100644 src/textual_dev/previews/example_widgets/switch.py create mode 100644 src/textual_dev/previews/example_widgets/text_log.py create mode 100644 src/textual_dev/previews/widgets.css create mode 100644 src/textual_dev/previews/widgets.py create mode 100644 tests/__init__.py 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..041c404 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -11,7 +11,7 @@ on: - 'Makefile' push: branches: - - 'main' + - '*' jobs: build: 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..4a4a8ba --- /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(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..52e7084 --- /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() + 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/example_widgets/text_log.py b/src/textual_dev/previews/example_widgets/text_log.py new file mode 100644 index 0000000..bc37d55 --- /dev/null +++ b/src/textual_dev/previews/example_widgets/text_log.py @@ -0,0 +1,24 @@ +from rich.syntax import Syntax +from textual.widgets import TextLog + +CODE = '''\ +def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value\ +''' + + +def text_log_example(id: str): + log = TextLog(highlight=True, markup=True, id=id) + log.write(Syntax(CODE, "python", indent_guides=True)) + yield log 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..252c141 --- /dev/null +++ b/src/textual_dev/previews/widgets.py @@ -0,0 +1,111 @@ +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, + # "TextLog": text_log_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/test_cli.py b/tests/test_cli.py index 4909650..89c78bc 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -8,3 +8,9 @@ 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 From f72c635a684fceabff092fb41e0c32e5574a6fd6 Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:11:16 +0200 Subject: [PATCH 4/9] Test --- .../example_widgets/content_switcher.py | 2 +- .../previews/example_widgets/data_table.py | 2 +- .../previews/example_widgets/text_log.py | 24 ------------------- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 src/textual_dev/previews/example_widgets/text_log.py diff --git a/src/textual_dev/previews/example_widgets/content_switcher.py b/src/textual_dev/previews/example_widgets/content_switcher.py index 4a4a8ba..d71871e 100644 --- a/src/textual_dev/previews/example_widgets/content_switcher.py +++ b/src/textual_dev/previews/example_widgets/content_switcher.py @@ -28,7 +28,7 @@ def content_switcher_example(id: str) -> ComposeResult: - table = DataTable(id="data-table") + table: DataTable = DataTable(id="data-table") table.add_columns("Book", "Year") table.add_rows( [ diff --git a/src/textual_dev/previews/example_widgets/data_table.py b/src/textual_dev/previews/example_widgets/data_table.py index 52e7084..32c24f5 100644 --- a/src/textual_dev/previews/example_widgets/data_table.py +++ b/src/textual_dev/previews/example_widgets/data_table.py @@ -17,7 +17,7 @@ def data_table_example(id: str) -> ComposeResult: - table = DataTable() + table: DataTable = DataTable() table.add_columns(*ROWS[0]) table.add_rows(ROWS[1:]) diff --git a/src/textual_dev/previews/example_widgets/text_log.py b/src/textual_dev/previews/example_widgets/text_log.py deleted file mode 100644 index bc37d55..0000000 --- a/src/textual_dev/previews/example_widgets/text_log.py +++ /dev/null @@ -1,24 +0,0 @@ -from rich.syntax import Syntax -from textual.widgets import TextLog - -CODE = '''\ -def loop_first_last(values: Iterable[T]) -> Iterable[tuple[bool, bool, T]]: - """Iterate and generate a tuple with a flag for first and last value.""" - iter_values = iter(values) - try: - previous_value = next(iter_values) - except StopIteration: - return - first = True - for value in iter_values: - yield first, False, previous_value - first = False - previous_value = value - yield first, True, previous_value\ -''' - - -def text_log_example(id: str): - log = TextLog(highlight=True, markup=True, id=id) - log.write(Syntax(CODE, "python", indent_guides=True)) - yield log From 79f739fcbd85ca14cdd902ad398f2d3a8af51293 Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:16:23 +0200 Subject: [PATCH 5/9] Test --- .github/workflows/pythonpackage.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 041c404..588b094 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -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 From e87cc0902116b818a269bc73656d1973b109dd56 Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:19:22 +0200 Subject: [PATCH 6/9] Test --- tests/devtools/test_devtools.py | 2 +- tests/devtools/test_devtools_client.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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" From c00a1e3a79570b7f5a291b04d63a660984554523 Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:21:45 +0200 Subject: [PATCH 7/9] Test --- src/textual_dev/previews/widgets.py | 55 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/textual_dev/previews/widgets.py b/src/textual_dev/previews/widgets.py index 252c141..0c878d2 100644 --- a/src/textual_dev/previews/widgets.py +++ b/src/textual_dev/previews/widgets.py @@ -35,34 +35,33 @@ 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, - # "TextLog": text_log_example, - "Tree": tree_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, } From 1b650d19808e31daecf6cb7ba90caa27cc9fd93f Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:32:52 +0200 Subject: [PATCH 8/9] Test --- tests/test_cli.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 89c78bc..43fe4b2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,7 +10,13 @@ def test_cli_version(): assert version("textual") in result.output -def test_cli_widgets(): +# 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, ["widgets"]) + result = runner.invoke(run, ["diagnose"]) assert result.exit_code == 0 From d9d73158d9b5ec2de4c540948f56a471e18e058d Mon Sep 17 00:00:00 2001 From: Pavel Kral Date: Fri, 11 Aug 2023 17:34:59 +0200 Subject: [PATCH 9/9] Test --- tests/test_cli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 43fe4b2..a575298 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -20,3 +20,9 @@ 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