fix focus issues

This commit is contained in:
Will McGugan
2022-10-14 18:11:08 +01:00
parent 82ab7d9db7
commit c56f870390
5 changed files with 38 additions and 33 deletions

View File

@@ -47,7 +47,7 @@ from .messages import CallbackType
from .reactive import Reactive
from .renderables.blank import Blank
from .screen import Screen
from .widget import Widget
from .widget import Widget, _wait_for_mount
PLATFORM = platform.system()
WINDOWS = PLATFORM == "Windows"
@@ -1089,9 +1089,7 @@ class App(Generic[ReturnType], DOMNode):
async def _on_compose(self) -> None:
widgets = list(self.compose())
self.mount_all(widgets)
aws = [widget._mounted.wait() for widget in widgets]
if aws:
await asyncio.wait(aws)
await _wait_for_mount(widgets)
def _on_idle(self) -> None:
"""Perform actions when there are no messages in the queue."""
@@ -1150,11 +1148,7 @@ class App(Generic[ReturnType], DOMNode):
Args:
widget (Widget): A Widget to unregister
"""
try:
widget.screen._reset_focus(widget)
except NoScreen:
pass
widget.reset_focus()
if isinstance(widget._parent, Widget):
widget._parent.children._remove(widget)
widget._detach()
@@ -1402,16 +1396,17 @@ class App(Generic[ReturnType], DOMNode):
async def _on_remove(self, event: events.Remove) -> None:
widget = event.widget
parent = widget.parent
if parent is not None:
parent.refresh(layout=True)
widget.reset_focus()
remove_widgets = widget.walk_children(
Widget, with_self=True, method="depth", reverse=True
)
for child in remove_widgets:
self._unregister(child)
for child in remove_widgets:
await child._close_messages()
self._unregister(child)
if parent is not None:
parent.refresh(layout=True)
async def action_press(self, key: str) -> None:
await self.press(key)

View File

@@ -68,9 +68,13 @@ class ColorsApp(App):
BINDINGS = [("d", "toggle_dark", "Toggle dark mode")]
def compose(self) -> ComposeResult:
yield Content(ColorButtons(), ColorsView())
yield Content(ColorButtons())
yield Footer()
def on_mount(self) -> None:
content = self.query_one("Content", Content)
content.mount(ColorsView())
def on_button_pressed(self, event: Button.Pressed) -> None:
self.query(ColorGroup).remove_class("-active")
group = self.query_one(f"#group-{event.button.id}", ColorGroup)

View File

@@ -73,7 +73,7 @@ class MessagePump(metaclass=MessagePumpMeta):
self._timers: WeakSet[Timer] = WeakSet()
self._last_idle: float = time()
self._max_idle: float | None = None
self._mounted = asyncio.Event()
self._mounted_event = asyncio.Event()
@property
def task(self) -> Task:
@@ -279,7 +279,6 @@ class MessagePump(metaclass=MessagePumpMeta):
await timer.stop()
self._timers.clear()
await self._message_queue.put(None)
if self._task is not None and asyncio.current_task() != self._task:
# Ensure everything is closed before returning
await self._task
@@ -332,12 +331,12 @@ class MessagePump(metaclass=MessagePumpMeta):
except CancelledError:
raise
except Exception as error:
self._mounted.set()
self._mounted_event.set()
self.app._handle_exception(error)
break
finally:
if isinstance(message, events.Mount):
self._mounted.set()
self._mounted_event.set()
self._message_queue.task_done()
current_time = time()
if self._message_queue.empty() or (

View File

@@ -118,16 +118,15 @@ class Reactive(Generic[ReactiveType]):
def __get__(self, obj: Reactable, obj_type: type[object]) -> ReactiveType:
if not hasattr(obj, self.internal_name):
init_name = f"_init_{self.name}"
if hasattr(obj, init_name):
default = getattr(obj, init_name)
default_value = default() if callable(default) else default
setattr(obj, self.name, default_value)
return default_value
default = getattr(obj, init_name)
default_value = default() if callable(default) else default
setattr(obj, self.internal_name, default_value)
return default_value
return getattr(obj, self.internal_name)
def __set__(self, obj: Reactable, value: ReactiveType) -> None:
name = self.name
current_value = getattr(obj, self.internal_name, None)
current_value = getattr(obj, self.name)
validate_function = getattr(obj, f"validate_{name}", None)
first_set = getattr(obj, f"__first_set_{self.internal_name}", True)
if callable(validate_function) and not first_set:

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from asyncio import Lock, wait
from asyncio import Lock, wait, create_task
from fractions import Fraction
from itertools import islice
from operator import attrgetter
@@ -59,6 +59,13 @@ _JUSTIFY_MAP: dict[str, JustifyMethod] = {
}
async def _wait_for_mount(widgets: list[Widget]) -> None:
"""Wait for widget to be mounted."""
aws = [create_task(widget._mounted_event.wait()) for widget in widgets]
if aws:
await wait(aws)
class _Styled:
"""Apply a style to a renderable.
@@ -1812,8 +1819,15 @@ class Widget(DOMNode):
scroll_visible (bool, optional): Scroll parent to make this widget
visible. Defaults to True.
"""
self.screen.set_focus(self, scroll_visible=scroll_visible)
def reset_focus(self) -> None:
try:
self.screen._reset_focus(self)
except NoScreen:
pass
def capture_mouse(self, capture: bool = True) -> None:
"""Capture (or release) the mouse.
@@ -1860,16 +1874,10 @@ class Widget(DOMNode):
async def _on_compose(self, event: events.Compose) -> None:
widgets = list(self.compose())
self.mount(*widgets)
aws = [widget._mounted.wait() for widget in widgets]
if aws:
await wait(aws)
Reactive.initialize_object(self)
await _wait_for_mount(widgets)
await self.post_message(events.Mount(self))
def _on_mount(self, event: events.Mount) -> None:
# widgets = self.compose()
# self.mount(*widgets)
# Preset scrollbars if not automatic
if self.styles.overflow_y == "scroll":
self.show_vertical_scrollbar = True
if self.styles.overflow_x == "scroll":
@@ -1941,7 +1949,7 @@ class Widget(DOMNode):
def _on_hide(self, event: events.Hide) -> None:
if self.has_focus:
self.screen._reset_focus(self)
self.reset_focus()
def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None:
self.scroll_to_region(message.region, animate=True)