From 77b07042c03f6efc51ebf33ab52c4e9ad8e659fe Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 15:14:47 +0000 Subject: [PATCH 01/14] typing fixes --- pyproject.toml | 1 + src/textual/_border.py | 36 +++++++++++++----------- src/textual/_cells.py | 4 +++ src/textual/_color_constants.py | 2 +- src/textual/_sleep.py | 9 ++---- src/textual/_win_sleep.py | 2 +- src/textual/color.py | 8 ++++-- src/textual/css/_help_renderables.py | 12 +++++--- src/textual/devtools/client.py | 1 + src/textual/devtools/service.py | 20 ++++++------- src/textual/renderables/_blend_colors.py | 2 ++ 11 files changed, 54 insertions(+), 43 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fd925a42e..593fc0eae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ Jinja2 = "<3.1.0" syrupy = "^3.0.0" mkdocs-rss-plugin = "^1.5.0" httpx = "^0.23.1" +msgpack-types = "" [tool.black] includes = "src" diff --git a/src/textual/_border.py b/src/textual/_border.py index 2feb33df0..dbb8c09c2 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -16,27 +16,29 @@ if TYPE_CHECKING: INNER = 1 OUTER = 2 -BORDER_CHARS: dict[EdgeType, tuple[str, str, str]] = { +BORDER_CHARS: dict[ + EdgeType, tuple[tuple[str, str, str], tuple[str, str, str], tuple[str, str, str]] +] = { # Each string of the tuple represents a sub-tuple itself: # - 1st string represents (top1, top2, top3) # - 2nd string represents (mid1, mid2, mid3) # - 3rd string represents (bottom1, bottom2, bottom3) - "": (" ", " ", " "), - "ascii": ("+-+", "| |", "+-+"), - "none": (" ", " ", " "), - "hidden": (" ", " ", " "), - "blank": (" ", " ", " "), - "round": ("╭─╮", "│ │", "╰─╯"), - "solid": ("┌─┐", "│ │", "└─┘"), - "double": ("╔═╗", "║ ║", "╚═╝"), - "dashed": ("┏╍┓", "╏ ╏", "┗╍┛"), - "heavy": ("┏━┓", "┃ ┃", "┗━┛"), - "inner": ("▗▄▖", "▐ ▌", "▝▀▘"), - "outer": ("▛▀▜", "▌ ▐", "▙▄▟"), - "hkey": ("▔▔▔", " ", "▁▁▁"), - "vkey": ("▏ ▕", "▏ ▕", "▏ ▕"), - "tall": ("▊▔▎", "▊ ▎", "▊▁▎"), - "wide": ("▁▁▁", "▎ ▋", "▔▔▔"), + "": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), + "ascii": (("+", "-", "+"), ("|", " ", "|"), ("+", "-", "+")), + "none": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), + "hidden": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), + "blank": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), + "round": (("╭", "─", "╮"), ("│", " ", "│"), ("╰", "─", "╯")), + "solid": (("┌", "─", "┐"), ("│", " ", "│"), ("└", "─", "┘")), + "double": (("╔", "═", "╗"), ("║", " ", "║"), ("╚", "═", "╝")), + "dashed": (("┏", "╍", "┓"), ("╏", " ", "╏"), ("┗", "╍", "┛")), + "heavy": (("┏", "━", "┓"), ("┃", " ", "┃"), ("┗", "━", "┛")), + "inner": (("▗", "▄", "▖"), ("▐", " ", "▌"), ("▝", "▀", "▘")), + "outer": (("▛", "▀", "▜"), ("▌", " ", "▐"), ("▙", "▄", "▟")), + "hkey": (("▔", "▔", "▔"), (" ", " ", " "), ("▁", "▁", "▁")), + "vkey": (("", "▏", " ▕"), ("", "▏", " ▕"), ("", "▏", " ▕")), + "tall": (("▊", "▔", "▎"), ("▊", " ", "▎"), ("▊", "▁", "▎")), + "wide": (("▁", "▁", "▁"), ("▎", " ", "▋"), ("▔", "▔", "▔")), } # Some of the borders are on the widget background and some are on the background of the parent diff --git a/src/textual/_cells.py b/src/textual/_cells.py index 2b838b93c..2c23b0e3e 100644 --- a/src/textual/_cells.py +++ b/src/textual/_cells.py @@ -1,5 +1,9 @@ +from typing import Callable + __all__ = ["cell_len"] + +cell_len: Callable[[str], int] try: from rich.cells import cached_cell_len as cell_len except ImportError: diff --git a/src/textual/_color_constants.py b/src/textual/_color_constants.py index cedab5673..f9a03b8ef 100644 --- a/src/textual/_color_constants.py +++ b/src/textual/_color_constants.py @@ -1,6 +1,6 @@ from __future__ import annotations -COLOR_NAME_TO_RGB: dict[str, tuple[int, int, int] | tuple[int, int, int, float]] = { +COLOR_NAME_TO_RGB: dict[str, tuple[int, int, int] | tuple[int, int, int, int]] = { # Let's start with a specific pseudo-color:: "transparent": (0, 0, 0, 0), # Then, the 16 common ANSI colors: diff --git a/src/textual/_sleep.py b/src/textual/_sleep.py index bb931e062..4b6176435 100644 --- a/src/textual/_sleep.py +++ b/src/textual/_sleep.py @@ -2,7 +2,7 @@ from __future__ import annotations from time import sleep, perf_counter -from asyncio import get_running_loop +from asyncio import get_running_loop, Future from threading import Thread, Event @@ -13,7 +13,7 @@ class Sleeper(Thread): self._exit = False self._sleep_time = 0.0 self._event = Event() - self.future = None + self.future: Future | None = None self._loop = get_running_loop() super().__init__(daemon=True) @@ -25,6 +25,7 @@ class Sleeper(Thread): sleep(self._sleep_time) self._event.clear() # self.future.set_result(None) + assert self.future is not None self._loop.call_soon_threadsafe(self.future.set_result, None) async def sleep(self, sleep_time: float) -> None: @@ -33,9 +34,6 @@ class Sleeper(Thread): self._event.set() await future - # await self._async_event.wait() - # self._async_event.clear() - async def check_sleeps() -> None: sleeper = Sleeper() @@ -46,7 +44,6 @@ async def check_sleeps() -> None: while perf_counter() - start < sleep_for: sleep(0) - # await sleeper.sleep(sleep_for) elapsed = perf_counter() - start return elapsed diff --git a/src/textual/_win_sleep.py b/src/textual/_win_sleep.py index 405c8a36d..16e91285d 100644 --- a/src/textual/_win_sleep.py +++ b/src/textual/_win_sleep.py @@ -20,7 +20,7 @@ try: import ctypes from ctypes.wintypes import LARGE_INTEGER - kernel32 = ctypes.windll.kernel32 + kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] except Exception: sleep = time_sleep else: diff --git a/src/textual/color.py b/src/textual/color.py index 17a9b34e7..663fff2cf 100644 --- a/src/textual/color.py +++ b/src/textual/color.py @@ -431,7 +431,9 @@ class Color(NamedTuple): suggested_color = None if not color_text.startswith(("#", "rgb", "hsl")): # Seems like we tried to use a color name: let's try to find one that is close enough: - suggested_color = get_suggestion(color_text, COLOR_NAME_TO_RGB.keys()) + suggested_color = get_suggestion( + color_text, list(COLOR_NAME_TO_RGB.keys()) + ) if suggested_color: error_message += f"; did you mean '{suggested_color}'?" raise ColorParseError(error_message, suggested_color) @@ -447,10 +449,10 @@ class Color(NamedTuple): ) = color_match.groups() if rgb_hex_triple is not None: - r, g, b = rgb_hex_triple + r, g, b = rgb_hex_triple # type: ignore[misc] color = cls(int(f"{r}{r}", 16), int(f"{g}{g}", 16), int(f"{b}{b}", 16)) elif rgb_hex_quad is not None: - r, g, b, a = rgb_hex_quad + r, g, b, a = rgb_hex_quad # type: ignore[misc] color = cls( int(f"{r}{r}", 16), int(f"{g}{g}", 16), diff --git a/src/textual/css/_help_renderables.py b/src/textual/css/_help_renderables.py index 00c05f764..939a5933a 100644 --- a/src/textual/css/_help_renderables.py +++ b/src/textual/css/_help_renderables.py @@ -61,7 +61,8 @@ class Bullet: self, console: Console, options: ConsoleOptions ) -> RenderResult: yield _markup_and_highlight(self.markup) - yield from self.examples + if self.examples is not None: + yield from self.examples @rich.repr.auto @@ -76,7 +77,9 @@ class HelpText: context around the issue. These are rendered below the summary. Defaults to None. """ - def __init__(self, summary: str, *, bullets: Iterable[Bullet] = None) -> None: + def __init__( + self, summary: str, *, bullets: Iterable[Bullet] | None = None + ) -> None: self.summary: str = summary self.bullets: Iterable[Bullet] | None = bullets or [] @@ -87,6 +90,7 @@ class HelpText: self, console: Console, options: ConsoleOptions ) -> RenderResult: tree = Tree(_markup_and_highlight(f"[b blue]{self.summary}"), guide_style="dim") - for bullet in self.bullets: - tree.add(bullet) + if self.bullets is not None: + for bullet in self.bullets: + tree.add(bullet) yield tree diff --git a/src/textual/devtools/client.py b/src/textual/devtools/client.py index a25bcb915..b9933feae 100644 --- a/src/textual/devtools/client.py +++ b/src/textual/devtools/client.py @@ -136,6 +136,7 @@ class DevtoolsClient: change, it will update its own Console to ensure it renders at the correct width for server-side display. """ + assert self.websocket is not None async for message in self.websocket: if message.type == aiohttp.WSMsgType.TEXT: message_json = json.loads(message.data) diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py index cea646739..1d35c4e90 100644 --- a/src/textual/devtools/service.py +++ b/src/textual/devtools/service.py @@ -2,11 +2,10 @@ from __future__ import annotations import asyncio -import base64 import json import pickle from json import JSONDecodeError -from typing import cast +from typing import Any, cast from aiohttp import WSMessage, WSMsgType from aiohttp.abc import Request @@ -232,17 +231,16 @@ class ClientHandler: ) try: await self.service.send_server_info(client_handler=self) - async for message in self.websocket: - message = cast(WSMessage, message) - - if message.type in (WSMsgType.TEXT, WSMsgType.BINARY): + async for websocket_message in self.websocket: + if websocket_message.type in (WSMsgType.TEXT, WSMsgType.BINARY): + message: dict[str, Any] try: - if isinstance(message.data, bytes): - message = msgpack.unpackb(message.data) + if isinstance(websocket_message.data, bytes): + message = msgpack.unpackb(websocket_message.data) else: - message = json.loads(message.data) + message = json.loads(websocket_message.data) except JSONDecodeError: - self.service.console.print(escape(str(message.data))) + self.service.console.print(escape(str(websocket_message.data))) continue type = message.get("type") @@ -253,7 +251,7 @@ class ClientHandler: and not self.service.shutdown_event.is_set() ): await self.incoming_queue.put(message) - elif message.type == WSMsgType.ERROR: + elif websocket_message.type == WSMsgType.ERROR: self.service.console.print( DevConsoleNotice("Websocket error occurred", level="error") ) diff --git a/src/textual/renderables/_blend_colors.py b/src/textual/renderables/_blend_colors.py index ebb41981d..b614ce612 100644 --- a/src/textual/renderables/_blend_colors.py +++ b/src/textual/renderables/_blend_colors.py @@ -15,6 +15,8 @@ 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 r1, g1, b1 = color1.triplet r2, g2, b2 = color2.triplet From 688812090122c340f59cd05b864d520c9353f666 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 15:18:18 +0000 Subject: [PATCH 02/14] update msgpack types --- poetry.lock | 14 +++++++++++++- pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2c437f4a0..a2448ef8b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -573,6 +573,14 @@ category = "main" optional = true python-versions = "*" +[[package]] +name = "msgpack-types" +version = "0.2.0" +description = "Type stubs for msgpack" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + [[package]] name = "multidict" version = "6.0.4" @@ -1040,7 +1048,7 @@ dev = ["aiohttp", "click", "msgpack"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "efa7c78c6403931436fb415a21c935eaafd7859130a9da8ef7e5d79ddb34b14d" +content-hash = "425fac5cc893af33128a6baf4fc9e296781d322e64eea5d9341d1265c6637d3c" [metadata.files] aiohttp = [ @@ -1548,6 +1556,10 @@ msgpack = [ {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, ] +msgpack-types = [ + {file = "msgpack-types-0.2.0.tar.gz", hash = "sha256:b6b7ce9f52599f9dc3497006be8cf6bed7bd2c83fa48c4df43ac6958b97b0720"}, + {file = "msgpack_types-0.2.0-py3-none-any.whl", hash = "sha256:7e5bce9e3bba9fe08ed14005ad107aa44ea8d4b779ec28b8db880826d4c67303"}, +] multidict = [ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, diff --git a/pyproject.toml b/pyproject.toml index 593fc0eae..1d8d28a24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ Jinja2 = "<3.1.0" syrupy = "^3.0.0" mkdocs-rss-plugin = "^1.5.0" httpx = "^0.23.1" -msgpack-types = "" +msgpack-types = "^0.2.0" [tool.black] includes = "src" From 6d722fd291668cd781df429a0c3695cf8884f36e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 15:55:57 +0000 Subject: [PATCH 03/14] Fix vkey --- src/textual/_border.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index dbb8c09c2..8dbe5e246 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -36,7 +36,7 @@ BORDER_CHARS: dict[ "inner": (("▗", "▄", "▖"), ("▐", " ", "▌"), ("▝", "▀", "▘")), "outer": (("▛", "▀", "▜"), ("▌", " ", "▐"), ("▙", "▄", "▟")), "hkey": (("▔", "▔", "▔"), (" ", " ", " "), ("▁", "▁", "▁")), - "vkey": (("", "▏", " ▕"), ("", "▏", " ▕"), ("", "▏", " ▕")), + "vkey": (("▏", " ", "▕"), ("▏", " ", "▕"), ("▏", " ", "▕")), "tall": (("▊", "▔", "▎"), ("▊", " ", "▎"), ("▊", "▁", "▎")), "wide": (("▁", "▁", "▁"), ("▎", " ", "▋"), ("▔", "▔", "▔")), } From 484676c09200196111d43a89e34b218f6cdd85b2 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 16:01:24 +0000 Subject: [PATCH 04/14] comment --- src/textual/_border.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index 8dbe5e246..9c521fab6 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -19,10 +19,8 @@ OUTER = 2 BORDER_CHARS: dict[ EdgeType, tuple[tuple[str, str, str], tuple[str, str, str], tuple[str, str, str]] ] = { - # Each string of the tuple represents a sub-tuple itself: - # - 1st string represents (top1, top2, top3) - # - 2nd string represents (mid1, mid2, mid3) - # - 3rd string represents (bottom1, bottom2, bottom3) + # Three tuples for the top, middle, and bottom rows. + # The sub-tuples are the characters for the left, center, and right borders. "": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), "ascii": (("+", "-", "+"), ("|", " ", "|"), ("+", "-", "+")), "none": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), From d3e66721e9e25394f8547abc38d76c48abce71bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:59:57 +0000 Subject: [PATCH 05/14] Change emit to post to self. --- CHANGELOG.md | 1 + src/textual/message_pump.py | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9f38a6ca..872e70cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: `TreeNode` can no longer be imported from `textual.widgets`; it is now available via `from textual.widgets.tree import TreeNode`. https://github.com/Textualize/textual/pull/1637 - `Tree` now shows a (subdued) cursor for a highlighted node when focus has moved elsewhere https://github.com/Textualize/textual/issues/1471 +- `MessagePump.emit` and `MessagePump.emit_no_wait` now emit events to self instead of to the parent ### Fixed diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index d79e0764c..1f0ab546e 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -581,7 +581,7 @@ class MessagePump(metaclass=MessagePumpMeta): await invoke(event.callback) def emit_no_wait(self, message: Message) -> bool: - """Send a message to the _parent_, non async version. + """Send a message to self, non-async version. Args: message: A message object. @@ -589,13 +589,10 @@ class MessagePump(metaclass=MessagePumpMeta): Returns: True if the message was posted successfully. """ - if self._parent: - return self._parent._post_message_from_child_no_wait(message) - else: - return False + return self.post_message_no_wait(message) async def emit(self, message: Message) -> bool: - """Send a message to the _parent_. + """Send a message to self. Args: message: A message object. @@ -603,10 +600,7 @@ class MessagePump(metaclass=MessagePumpMeta): Returns: True if the message was posted successfully. """ - if self._parent: - return await self._parent._post_message_from_child(message) - else: - return False + return await self.post_message(message) # TODO: Does dispatch_key belong on message pump? async def dispatch_key(self, event: events.Key) -> bool: From 0a4d1c919daa52a267e1e64f318f82cdf9f14904 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 18:10:01 +0000 Subject: [PATCH 06/14] updated widgets docs --- docs/guide/widgets.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 8c8f20c3b..59e7df67a 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -371,11 +371,11 @@ We can perform this conversion by adding `self.scroll_offset` to `event.offset`. - Once we have the board coordinate underneath the mouse we divide the x coordinate by 8 and divide the y coordinate by 4 to give us the coordinate of a square. If the cursor square coordinate calculated in `on_mouse_move` changes, Textual will call `watch_cursor_square` with the previous coordinate and new coordinate of the square. This method works out the regions of the widget to update and essentially does the reverse of the steps we took to go from mouse coordinates to square coordinates. -The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Regions to `refresh` tells Textual to update only the cells underneath those regions, and not the entire region. +The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Region object to `refresh` tells Textual to update only the cells underneath those regions, and not the entire widget. !!! note - Textual is smart about performing updates. If you refresh multiple regions (even if they overlap), Textual will combine them in to as few non-overlapping regions as possible. + Textual is smart about performing updates. If you refresh multiple regions, Textual will combine them in to as few non-overlapping regions as possible. The final step is to update the `render_line` method to use the cursor style when rendering the square underneath the mouse. From 777a205bca64e1c929e7b0a35830a9cc45a40e3e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 18:28:05 +0000 Subject: [PATCH 07/14] trailing commans --- src/textual/_border.py | 96 +++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index 9c521fab6..b6052cc8c 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -21,22 +21,86 @@ BORDER_CHARS: dict[ ] = { # Three tuples for the top, middle, and bottom rows. # The sub-tuples are the characters for the left, center, and right borders. - "": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), - "ascii": (("+", "-", "+"), ("|", " ", "|"), ("+", "-", "+")), - "none": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), - "hidden": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), - "blank": ((" ", " ", " "), (" ", " ", " "), (" ", " ", " ")), - "round": (("╭", "─", "╮"), ("│", " ", "│"), ("╰", "─", "╯")), - "solid": (("┌", "─", "┐"), ("│", " ", "│"), ("└", "─", "┘")), - "double": (("╔", "═", "╗"), ("║", " ", "║"), ("╚", "═", "╝")), - "dashed": (("┏", "╍", "┓"), ("╏", " ", "╏"), ("┗", "╍", "┛")), - "heavy": (("┏", "━", "┓"), ("┃", " ", "┃"), ("┗", "━", "┛")), - "inner": (("▗", "▄", "▖"), ("▐", " ", "▌"), ("▝", "▀", "▘")), - "outer": (("▛", "▀", "▜"), ("▌", " ", "▐"), ("▙", "▄", "▟")), - "hkey": (("▔", "▔", "▔"), (" ", " ", " "), ("▁", "▁", "▁")), - "vkey": (("▏", " ", "▕"), ("▏", " ", "▕"), ("▏", " ", "▕")), - "tall": (("▊", "▔", "▎"), ("▊", " ", "▎"), ("▊", "▁", "▎")), - "wide": (("▁", "▁", "▁"), ("▎", " ", "▋"), ("▔", "▔", "▔")), + "": ( + (" ", " ", " "), + (" ", " ", " "), + (" ", " ", " "), + ), + "ascii": ( + ("+", "-", "+"), + ("|", " ", "|"), + ("+", "-", "+"), + ), + "none": ( + (" ", " ", " "), + (" ", " ", " "), + (" ", " ", " "), + ), + "hidden": ( + (" ", " ", " "), + (" ", " ", " "), + (" ", " ", " "), + ), + "blank": ( + (" ", " ", " "), + (" ", " ", " "), + (" ", " ", " "), + ), + "round": ( + ("╭", "─", "╮"), + ("│", " ", "│"), + ("╰", "─", "╯"), + ), + "solid": ( + ("┌", "─", "┐"), + ("│", " ", "│"), + ("└", "─", "┘"), + ), + "double": ( + ("╔", "═", "╗"), + ("║", " ", "║"), + ("╚", "═", "╝"), + ), + "dashed": ( + ("┏", "╍", "┓"), + ("╏", " ", "╏"), + ("┗", "╍", "┛"), + ), + "heavy": ( + ("┏", "━", "┓"), + ("┃", " ", "┃"), + ("┗", "━", "┛"), + ), + "inner": ( + ("▗", "▄", "▖"), + ("▐", " ", "▌"), + ("▝", "▀", "▘"), + ), + "outer": ( + ("▛", "▀", "▜"), + ("▌", " ", "▐"), + ("▙", "▄", "▟"), + ), + "hkey": ( + ("▔", "▔", "▔"), + (" ", " ", " "), + ("▁", "▁", "▁"), + ), + "vkey": ( + ("▏", " ", "▕"), + ("▏", " ", "▕"), + ("▏", " ", "▕"), + ), + "tall": ( + ("▊", "▔", "▎"), + ("▊", " ", "▎"), + ("▊", "▁", "▎"), + ), + "wide": ( + ("▁", "▁", "▁"), + ("▎", " ", "▋"), + ("▔", "▔", "▔"), + ), } # Some of the borders are on the widget background and some are on the background of the parent From 71b91521fcfcd923988f209bbaddb1148992dc16 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 18:29:38 +0000 Subject: [PATCH 08/14] more trailing commas --- src/textual/_border.py | 96 +++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index b6052cc8c..bea796d5d 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -108,22 +108,86 @@ BORDER_CHARS: dict[ BORDER_LOCATIONS: dict[ EdgeType, tuple[tuple[int, int, int], tuple[int, int, int], tuple[int, int, int]] ] = { - "": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "ascii": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "none": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "hidden": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "blank": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "round": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "solid": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "double": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "dashed": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "heavy": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "inner": ((1, 1, 1), (1, 1, 1), (1, 1, 1)), - "outer": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "hkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "vkey": ((0, 0, 0), (0, 0, 0), (0, 0, 0)), - "tall": ((2, 0, 1), (2, 0, 1), (2, 0, 1)), - "wide": ((1, 1, 1), (0, 1, 3), (1, 1, 1)), + "": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "ascii": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "none": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "hidden": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "blank": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "round": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "solid": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "double": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "dashed": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "heavy": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "inner": ( + (1, 1, 1), + (1, 1, 1), + (1, 1, 1), + ), + "outer": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "hkey": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "vkey": ( + (0, 0, 0), + (0, 0, 0), + (0, 0, 0), + ), + "tall": ( + (2, 0, 1), + (2, 0, 1), + (2, 0, 1), + ), + "wide": ( + (1, 1, 1), + (0, 1, 3), + (1, 1, 1), + ), } INVISIBLE_EDGE_TYPES = cast("frozenset[EdgeType]", frozenset(("", "none", "hidden"))) From a93ec81a2a9a7e8d0ba4015eee99965389978929 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 7 Feb 2023 19:51:42 +0000 Subject: [PATCH 09/14] extra space removed --- src/textual/_border.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/_border.py b/src/textual/_border.py index bea796d5d..cded7790a 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -22,7 +22,7 @@ BORDER_CHARS: dict[ # Three tuples for the top, middle, and bottom rows. # The sub-tuples are the characters for the left, center, and right borders. "": ( - (" ", " ", " "), + (" ", " ", " "), (" ", " ", " "), (" ", " ", " "), ), From aabc1cc89816650dfaf1e90163835b6f0bbcd3f1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Feb 2023 10:20:47 +0000 Subject: [PATCH 10/14] fix typo [skip ci] --- docs/guide/widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index 59e7df67a..c784cef2f 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -371,7 +371,7 @@ We can perform this conversion by adding `self.scroll_offset` to `event.offset`. - Once we have the board coordinate underneath the mouse we divide the x coordinate by 8 and divide the y coordinate by 4 to give us the coordinate of a square. If the cursor square coordinate calculated in `on_mouse_move` changes, Textual will call `watch_cursor_square` with the previous coordinate and new coordinate of the square. This method works out the regions of the widget to update and essentially does the reverse of the steps we took to go from mouse coordinates to square coordinates. -The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Region object to `refresh` tells Textual to update only the cells underneath those regions, and not the entire widget. +The `get_square_region` function calculates a [Region][textual.geometry.Region] object for each square and uses them as a positional argument in a call to [refresh][textual.widget.Widget.refresh]. Passing Region objects to `refresh` tells Textual to update only the cells underneath those regions, and not the entire widget. !!! note From 29875a66fcf54812f0b44a602cb4ecccfdcb82f1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Feb 2023 10:31:35 +0000 Subject: [PATCH 11/14] into [skip ci] --- docs/guide/widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/widgets.md b/docs/guide/widgets.md index c784cef2f..70d7f55e8 100644 --- a/docs/guide/widgets.md +++ b/docs/guide/widgets.md @@ -375,7 +375,7 @@ The `get_square_region` function calculates a [Region][textual.geometry.Region] !!! note - Textual is smart about performing updates. If you refresh multiple regions, Textual will combine them in to as few non-overlapping regions as possible. + Textual is smart about performing updates. If you refresh multiple regions, Textual will combine them into as few non-overlapping regions as possible. The final step is to update the `render_line` method to use the cursor style when rendering the square underneath the mouse. From bd719e057fc414368e5aaf03099c873e91e512f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:39:53 +0000 Subject: [PATCH 12/14] Remove MessagePump.emit .emit_no_wait. --- CHANGELOG.md | 5 ++- src/textual/scrollbar.py | 8 +++-- src/textual/widget.py | 4 +-- src/textual/widgets/_button.py | 4 +-- src/textual/widgets/_checkbox.py | 6 ++-- src/textual/widgets/_data_table.py | 50 +++++++++++++------------- src/textual/widgets/_directory_tree.py | 6 ++-- src/textual/widgets/_input.py | 8 ++--- src/textual/widgets/_list_item.py | 2 +- src/textual/widgets/_list_view.py | 10 +++--- 10 files changed, 55 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 872e70cbb..87edabf69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: `TreeNode` can no longer be imported from `textual.widgets`; it is now available via `from textual.widgets.tree import TreeNode`. https://github.com/Textualize/textual/pull/1637 - `Tree` now shows a (subdued) cursor for a highlighted node when focus has moved elsewhere https://github.com/Textualize/textual/issues/1471 -- `MessagePump.emit` and `MessagePump.emit_no_wait` now emit events to self instead of to the parent ### Fixed @@ -40,6 +39,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666 - Fixed glitch with view position with auto width inputs https://github.com/Textualize/textual/issues/1693 +### Removed + +- Methods `MessagePump.emit` and `MessagePump.emit_no_wait` https://github.com/Textualize/textual/pull/1738 + ## [0.10.1] - 2023-01-20 ### Added diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index ad8a99e7e..12e1fd65d 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -280,10 +280,12 @@ class ScrollBar(Widget): self.mouse_over = False async def action_scroll_down(self) -> None: - await self.emit(ScrollDown(self) if self.vertical else ScrollRight(self)) + await self.post_message( + ScrollDown(self) if self.vertical else ScrollRight(self) + ) async def action_scroll_up(self) -> None: - await self.emit(ScrollUp(self) if self.vertical else ScrollLeft(self)) + await self.post_message(ScrollUp(self) if self.vertical else ScrollLeft(self)) def action_grab(self) -> None: self.capture_mouse() @@ -324,7 +326,7 @@ class ScrollBar(Widget): * (self.window_virtual_size / self.window_size) ) ) - await self.emit(ScrollTo(self, x=x, y=y)) + await self.post_message(ScrollTo(self, x=x, y=y)) event.stop() async def _on_click(self, event: events.Click) -> None: diff --git a/src/textual/widget.py b/src/textual/widget.py index 4b2a6973f..5bd204c9e 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2465,12 +2465,12 @@ class Widget(DOMNode): def _on_focus(self, event: events.Focus) -> None: self.has_focus = True self.refresh() - self.emit_no_wait(events.DescendantFocus(self)) + self.post_message_no_wait(events.DescendantFocus(self)) def _on_blur(self, event: events.Blur) -> None: self.has_focus = False self.refresh() - self.emit_no_wait(events.DescendantBlur(self)) + self.post_message_no_wait(events.DescendantBlur(self)) def _on_descendant_blur(self, event: events.DescendantBlur) -> None: if self._has_focus_within: diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 12518445b..ade5a1fd8 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -255,7 +255,7 @@ class Button(Static, can_focus=True): # Manage the "active" effect: self._start_active_affect() # ...and let other components know that we've just been clicked: - self.emit_no_wait(Button.Pressed(self)) + self.post_message_no_wait(Button.Pressed(self)) def _start_active_affect(self) -> None: """Start a small animation to show the button was clicked.""" @@ -267,7 +267,7 @@ class Button(Static, can_focus=True): async def _on_key(self, event: events.Key) -> None: if event.key == "enter" and not self.disabled: self._start_active_affect() - await self.emit(Button.Pressed(self)) + await self.post_message(Button.Pressed(self)) @classmethod def success( diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_checkbox.py index 305858faf..9b5b1b454 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_checkbox.py @@ -77,7 +77,7 @@ class Checkbox(Widget, can_focus=True): """The position of the slider.""" class Changed(Message, bubble=True): - """Emitted when the status of the checkbox changes. + """Posted when the status of the checkbox changes. Can be handled using `on_checkbox_changed` in a subclass of `Checkbox` or in a parent widget in the DOM. @@ -122,7 +122,7 @@ class Checkbox(Widget, can_focus=True): self.animate("slider_pos", target_slider_pos, duration=0.3) else: self.slider_pos = target_slider_pos - self.emit_no_wait(self.Changed(self, self.value)) + self.post_message_no_wait(self.Changed(self, self.value)) def watch_slider_pos(self, slider_pos: float) -> None: self.set_class(slider_pos == 1, "-on") @@ -151,5 +151,5 @@ class Checkbox(Widget, can_focus=True): def toggle(self) -> None: """Toggle the checkbox value. As a result of the value changing, - a Checkbox.Changed message will be emitted.""" + a Checkbox.Changed message will be posted.""" self.value = not self.value diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index bd4ecc6d3..79b8e4485 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -190,9 +190,9 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): hover_cell: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False) class CellHighlighted(Message, bubble=True): - """Emitted when the cursor moves to highlight a new cell. + """Posted when the cursor moves to highlight a new cell. It's only relevant when the `cursor_type` is `"cell"`. - It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`), + It's also posted when the cell cursor is re-enabled (by setting `show_cursor=True`), and when the cursor type is changed to `"cell"`. Can be handled using `on_data_table_cell_highlighted` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -215,7 +215,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): yield "coordinate", self.coordinate class CellSelected(Message, bubble=True): - """Emitted by the `DataTable` widget when a cell is selected. + """Posted by the `DataTable` widget when a cell is selected. It's only relevant when the `cursor_type` is `"cell"`. Can be handled using `on_data_table_cell_selected` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -238,7 +238,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): yield "coordinate", self.coordinate class RowHighlighted(Message, bubble=True): - """Emitted when a row is highlighted. This message is only emitted when the + """Posted when a row is highlighted. This message is only posted when the `cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_highlighted` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -255,7 +255,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): yield "cursor_row", self.cursor_row class RowSelected(Message, bubble=True): - """Emitted when a row is selected. This message is only emitted when the + """Posted when a row is selected. This message is only posted when the `cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_selected` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -273,7 +273,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): yield "cursor_row", self.cursor_row class ColumnHighlighted(Message, bubble=True): - """Emitted when a column is highlighted. This message is only emitted when the + """Posted when a column is highlighted. This message is only posted when the `cursor_type` is set to `"column"`. Can be handled using `on_data_table_column_highlighted` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -291,7 +291,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): yield "cursor_column", self.cursor_column class ColumnSelected(Message, bubble=True): - """Emitted when a column is selected. This message is only emitted when the + """Posted when a column is selected. This message is only posted when the `cursor_type` is set to `"column"`. Can be handled using `on_data_table_column_selected` in a subclass of `DataTable` or in a parent widget in the DOM. @@ -405,7 +405,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self._clear_caches() if show_cursor and self.cursor_type != "none": # When we re-enable the cursor, apply highlighting and - # emit the appropriate [Row|Column|Cell]Highlighted event. + # post the appropriate [Row|Column|Cell]Highlighted event. self._scroll_cursor_into_view(animate=False) if self.cursor_type == "cell": self._highlight_cell(self.cursor_cell) @@ -431,7 +431,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self, old_coordinate: Coordinate, new_coordinate: Coordinate ) -> None: if old_coordinate != new_coordinate: - # Refresh the old and the new cell, and emit the appropriate + # Refresh the old and the new cell, and post the appropriate # message to tell users of the newly highlighted row/cell/column. if self.cursor_type == "cell": self.refresh_cell(*old_coordinate) @@ -444,7 +444,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): self._highlight_column(new_coordinate.column) def _highlight_cell(self, coordinate: Coordinate) -> None: - """Apply highlighting to the cell at the coordinate, and emit event.""" + """Apply highlighting to the cell at the coordinate, and post event.""" self.refresh_cell(*coordinate) try: cell_value = self.get_cell_value(coordinate) @@ -453,19 +453,21 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): # In that case, there's nothing for us to do here. return else: - self.emit_no_wait(DataTable.CellHighlighted(self, cell_value, coordinate)) + self.post_message_no_wait( + DataTable.CellHighlighted(self, cell_value, coordinate) + ) def _highlight_row(self, row_index: int) -> None: - """Apply highlighting to the row at the given index, and emit event.""" + """Apply highlighting to the row at the given index, and post event.""" self.refresh_row(row_index) if row_index in self.data: - self.emit_no_wait(DataTable.RowHighlighted(self, row_index)) + self.post_message_no_wait(DataTable.RowHighlighted(self, row_index)) def _highlight_column(self, column_index: int) -> None: - """Apply highlighting to the column at the given index, and emit event.""" + """Apply highlighting to the column at the given index, and post event.""" self.refresh_column(column_index) if column_index < len(self.columns): - self.emit_no_wait(DataTable.ColumnHighlighted(self, column_index)) + self.post_message_no_wait(DataTable.ColumnHighlighted(self, column_index)) def validate_cursor_cell(self, value: Coordinate) -> Coordinate: return self._clamp_cursor_cell(value) @@ -641,7 +643,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): # If a position has opened for the cursor to appear, where it previously # could not (e.g. when there's no data in the table), then a highlighted - # event is emitted, since there's now a highlighted cell when there wasn't + # event is posted, since there's now a highlighted cell when there wasn't # before. cell_now_available = self.row_count == 1 and len(self.columns) > 0 visible_cursor = self.show_cursor and self.cursor_type != "none" @@ -1039,8 +1041,8 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): def on_click(self, event: events.Click) -> None: self._set_hover_cursor(True) if self.show_cursor and self.cursor_type != "none": - # Only emit selection events if there is a visible row/col/cell cursor. - self._emit_selected_message() + # Only post selection events if there is a visible row/col/cell cursor. + self._post_message_selected_message() meta = self.get_style_at(event.x, event.y).meta if meta: self.cursor_cell = Coordinate(meta["row"], meta["column"]) @@ -1088,14 +1090,14 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): def action_select_cursor(self) -> None: self._set_hover_cursor(False) if self.show_cursor and self.cursor_type != "none": - self._emit_selected_message() + self._post_selected_message() - def _emit_selected_message(self): - """Emit the appropriate message for a selection based on the `cursor_type`.""" + def _post_selected_message(self): + """Post the appropriate message for a selection based on the `cursor_type`.""" cursor_cell = self.cursor_cell cursor_type = self.cursor_type if cursor_type == "cell": - self.emit_no_wait( + self.post_message_no_wait( DataTable.CellSelected( self, self.get_cell_value(cursor_cell), @@ -1104,7 +1106,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): ) elif cursor_type == "row": row, _ = cursor_cell - self.emit_no_wait(DataTable.RowSelected(self, row)) + self.post_message_no_wait(DataTable.RowSelected(self, row)) elif cursor_type == "column": _, column = cursor_cell - self.emit_no_wait(DataTable.ColumnSelected(self, column)) + self.post_message_no_wait(DataTable.ColumnSelected(self, column)) diff --git a/src/textual/widgets/_directory_tree.py b/src/textual/widgets/_directory_tree.py index 40282e03f..5b4845c05 100644 --- a/src/textual/widgets/_directory_tree.py +++ b/src/textual/widgets/_directory_tree.py @@ -67,7 +67,7 @@ class DirectoryTree(Tree[DirEntry]): """ class FileSelected(Message, bubble=True): - """Emitted when a file is selected. + """Posted when a file is selected. Can be handled using `on_directory_tree_file_selected` in a subclass of `DirectoryTree` or in a parent widget in the DOM. @@ -173,7 +173,7 @@ class DirectoryTree(Tree[DirEntry]): if not dir_entry.loaded: self.load_directory(event.node) else: - self.emit_no_wait(self.FileSelected(self, dir_entry.path)) + self.post_message_no_wait(self.FileSelected(self, dir_entry.path)) def on_tree_node_selected(self, event: Tree.NodeSelected) -> None: event.stop() @@ -181,4 +181,4 @@ class DirectoryTree(Tree[DirEntry]): if dir_entry is None: return if not dir_entry.is_dir: - self.emit_no_wait(self.FileSelected(self, dir_entry.path)) + self.post_message_no_wait(self.FileSelected(self, dir_entry.path)) diff --git a/src/textual/widgets/_input.py b/src/textual/widgets/_input.py index 278ab3a88..d449efee0 100644 --- a/src/textual/widgets/_input.py +++ b/src/textual/widgets/_input.py @@ -139,7 +139,7 @@ class Input(Widget, can_focus=True): max_size: reactive[int | None] = reactive(None) class Changed(Message, bubble=True): - """Emitted when the value changes. + """Posted when the value changes. Can be handled using `on_input_changed` in a subclass of `Input` or in a parent widget in the DOM. @@ -155,7 +155,7 @@ class Input(Widget, can_focus=True): self.input: Input = sender class Submitted(Message, bubble=True): - """Emitted when the enter key is pressed within an `Input`. + """Posted when the enter key is pressed within an `Input`. Can be handled using `on_input_submitted` in a subclass of `Input` or in a parent widget in the DOM. @@ -244,7 +244,7 @@ class Input(Widget, can_focus=True): async def watch_value(self, value: str) -> None: if self.styles.auto_dimensions: self.refresh(layout=True) - await self.emit(self.Changed(self, value)) + await self.post_message(self.Changed(self, value)) @property def cursor_width(self) -> int: @@ -479,4 +479,4 @@ class Input(Widget, can_focus=True): self.cursor_position = 0 async def action_submit(self) -> None: - await self.emit(self.Submitted(self, self.value)) + await self.post_message(self.Submitted(self, self.value)) diff --git a/src/textual/widgets/_list_item.py b/src/textual/widgets/_list_item.py index f1af8c416..e9222a7aa 100644 --- a/src/textual/widgets/_list_item.py +++ b/src/textual/widgets/_list_item.py @@ -34,7 +34,7 @@ class ListItem(Widget, can_focus=False): pass def on_click(self, event: events.Click) -> None: - self.emit_no_wait(self._ChildClicked(self)) + self.post_message_no_wait(self._ChildClicked(self)) def watch_highlighted(self, value: bool) -> None: self.set_class(value, "--highlight") diff --git a/src/textual/widgets/_list_view.py b/src/textual/widgets/_list_view.py index a4a530e2e..6e391d862 100644 --- a/src/textual/widgets/_list_view.py +++ b/src/textual/widgets/_list_view.py @@ -35,7 +35,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): index = reactive(0, always_update=True) class Highlighted(Message, bubble=True): - """Emitted when the highlighted item changes. + """Posted when the highlighted item changes. Highlighted item is controlled using up/down keys. Can be handled using `on_list_view_highlighted` in a subclass of `ListView` @@ -50,7 +50,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): self.item: ListItem | None = item class Selected(Message, bubble=True): - """Emitted when a list item is selected, e.g. when you press the enter key on it. + """Posted when a list item is selected, e.g. when you press the enter key on it. Can be handled using `on_list_view_selected` in a subclass of `ListView` or in a parent widget in the DOM. @@ -125,7 +125,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): new_child = None self._scroll_highlighted_region() - self.emit_no_wait(self.Highlighted(self, new_child)) + self.post_message_no_wait(self.Highlighted(self, new_child)) def append(self, item: ListItem) -> AwaitMount: """Append a new ListItem to the end of the ListView. @@ -155,7 +155,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): def action_select_cursor(self) -> None: selected_child = self.highlighted_child - self.emit_no_wait(self.Selected(self, selected_child)) + self.post_message_no_wait(self.Selected(self, selected_child)) def action_cursor_down(self) -> None: self.index += 1 @@ -166,7 +166,7 @@ class ListView(Vertical, can_focus=True, can_focus_children=False): def on_list_item__child_clicked(self, event: ListItem._ChildClicked) -> None: self.focus() self.index = self.children.index(event.sender) - self.emit_no_wait(self.Selected(self, event.sender)) + self.post_message_no_wait(self.Selected(self, event.sender)) def _scroll_highlighted_region(self) -> None: """Used to keep the highlighted index within vision""" From e5e7c08afe0f7ad49d5b7197daf83de937435db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 8 Feb 2023 10:53:51 +0000 Subject: [PATCH 13/14] Update docs accordingly. --- .../on-dog-food-the-original-metaverse-and-not-being-bored.md | 2 +- docs/examples/events/custom01.py | 4 ++-- docs/guide/events.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md index 076cb96b5..494105cfb 100644 --- a/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md +++ b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md @@ -288,7 +288,7 @@ So, thanks to this bit of code in my `Activity` widget... parent.move_child( self, before=parent.children.index( self ) - 1 ) - self.emit_no_wait( self.Moved( self ) ) + self.post_messa_no_wait( self.Moved( self ) ) self.scroll_visible( top=True ) ``` diff --git a/docs/examples/events/custom01.py b/docs/examples/events/custom01.py index 043b32ffc..c96f2e0a9 100644 --- a/docs/examples/events/custom01.py +++ b/docs/examples/events/custom01.py @@ -25,8 +25,8 @@ class ColorButton(Static): self.styles.border = ("tall", self.color) async def on_click(self) -> None: - # The emit method sends an event to a widget's parent - await self.emit(self.Selected(self, self.color)) + # The post_message method sends an event to be handled in the DOM + await self.post_message(self.Selected(self, self.color)) def render(self) -> str: return str(self.color) diff --git a/docs/guide/events.md b/docs/guide/events.md index 2fa5195c2..cefe87de2 100644 --- a/docs/guide/events.md +++ b/docs/guide/events.md @@ -110,7 +110,7 @@ The message class is defined within the widget class itself. This is not strictl ## Sending events -In the previous example we used [emit()][textual.message_pump.MessagePump.emit] to send an event to its parent. We could also have used [emit_no_wait()][textual.message_pump.MessagePump.emit_no_wait] for non async code. Sending messages in this way allows you to write custom widgets without needing to know in what context they will be used. +In the previous example we used [post_message()][textual.message_pump.MessagePump.post_message] to send an event to its parent. We could also have used [post_message_no_wait()][textual.message_pump.MessagePump.post_message_no_wait] for non async code. Sending messages in this way allows you to write custom widgets without needing to know in what context they will be used. There are other ways of sending (posting) messages, which you may need to use less frequently. From c39b23f78df98a29a8826735d0268c713a9e066f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Wed, 8 Feb 2023 11:26:27 +0000 Subject: [PATCH 14/14] Final fixes. --- ...-original-metaverse-and-not-being-bored.md | 2 +- src/textual/message_pump.py | 22 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md index 494105cfb..fab89158c 100644 --- a/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md +++ b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md @@ -288,7 +288,7 @@ So, thanks to this bit of code in my `Activity` widget... parent.move_child( self, before=parent.children.index( self ) - 1 ) - self.post_messa_no_wait( self.Moved( self ) ) + self.post_message_no_wait( self.Moved( self ) ) self.scroll_visible( top=True ) ``` diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 1f0ab546e..4058139ce 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -580,28 +580,6 @@ class MessagePump(metaclass=MessagePumpMeta): async def on_callback(self, event: events.Callback) -> None: await invoke(event.callback) - def emit_no_wait(self, message: Message) -> bool: - """Send a message to self, non-async version. - - Args: - message: A message object. - - Returns: - True if the message was posted successfully. - """ - return self.post_message_no_wait(message) - - async def emit(self, message: Message) -> bool: - """Send a message to self. - - Args: - message: A message object. - - Returns: - True if the message was posted successfully. - """ - return await self.post_message(message) - # TODO: Does dispatch_key belong on message pump? async def dispatch_key(self, event: events.Key) -> bool: """Dispatch a key event to method.