fix for simple case

This commit is contained in:
Will McGugan
2022-05-26 11:15:44 +01:00
parent 153567d3b1
commit 5c10f944ed
9 changed files with 56 additions and 50 deletions

View File

@@ -3,10 +3,6 @@ from textual.app import App
class ExampleApp(App): class ExampleApp(App):
CSS = """
"""
COLORS = [ COLORS = [
"white", "white",
"maroon", "maroon",
@@ -22,15 +18,12 @@ class ExampleApp(App):
def on_mount(self): def on_mount(self):
self.styles.background = "darkblue" self.styles.background = "darkblue"
self.bind("t", "tree")
def on_key(self, event): def on_key(self, event):
if event.key.isdigit(): if event.key.isdigit():
self.styles.background = self.COLORS[int(event.key)] self.styles.background = self.COLORS[int(event.key)]
self.bell() self.bell()
def action_tree(self):
self.log(self.tree)
app = ExampleApp() app = ExampleApp()
app.run() app.run()

View File

@@ -5,7 +5,7 @@ from textual.widget import Widget
class WidthApp(App): class WidthApp(App):
CSS = """ CSS = """
Widget { Widget {
background: blue; background: blue 50%;
width: 50%; width: 50%;
} }
""" """

View File

@@ -12,7 +12,7 @@ TEXT = Text.from_markup(lorem)
class TextWidget(Widget): class TextWidget(Widget):
def render(self, style): def render(self):
return TEXT return TEXT

View File

@@ -221,7 +221,7 @@ class Compositor:
# Keep a copy of the old map because we're going to compare it with the update # Keep a copy of the old map because we're going to compare it with the update
old_map = self.map.copy() old_map = self.map.copy()
old_widgets = old_map.keys() old_widgets = old_map.keys()
map, widgets = self._arrange_root(parent) map, widgets = self._arrange_root(parent, size)
# parent.log(map) # parent.log(map)
@@ -246,17 +246,16 @@ class Compositor:
resized_widgets = { resized_widgets = {
widget widget
for widget, (region, *_) in map.items() for widget, (region, *_) in map.items()
if widget in old_widgets and widget.size != region.size if widget in old_widgets and old_map[widget].region.size != region.size
} }
# Gets pairs of tuples of (Widget, MapGeometry) which have changed # Gets pairs of tuples of (Widget, MapGeometry) which have changed
# i.e. if something is moved / deleted / added # i.e. if something is moved / deleted / added
screen = size.region screen = size.region
if screen not in self._dirty_regions: if screen not in self._dirty_regions:
crop_screen = screen.intersection crop_screen = screen.intersection
changes: set[tuple[Widget, MapGeometry]] = ( changes = map.items() ^ old_map.items()
self.map.items() ^ old_map.items()
)
self._dirty_regions.update( self._dirty_regions.update(
[ [
crop_screen(map_geometry.visible_region) crop_screen(map_geometry.visible_region)
@@ -270,7 +269,9 @@ class Compositor:
resized=resized_widgets, resized=resized_widgets,
) )
def _arrange_root(self, root: Widget) -> tuple[CompositorMap, set[Widget]]: def _arrange_root(
self, root: Widget, size: Size
) -> tuple[CompositorMap, set[Widget]]:
"""Arrange a widgets children based on its layout attribute. """Arrange a widgets children based on its layout attribute.
Args: Args:
@@ -282,7 +283,7 @@ class Compositor:
""" """
ORIGIN = Offset(0, 0) ORIGIN = Offset(0, 0)
size = root.size
map: CompositorMap = {} map: CompositorMap = {}
widgets: set[Widget] = set() widgets: set[Widget] = set()
get_order = attrgetter("order") get_order = attrgetter("order")

View File

@@ -526,7 +526,7 @@ class App(Generic[ReturnType], DOMNode):
self.screen.refresh(layout=True) self.screen.refresh(layout=True)
def render(self) -> RenderableType: def render(self) -> RenderableType:
return Blank("red") return Blank()
def query(self, selector: str | None = None) -> DOMQuery: def query(self, selector: str | None = None) -> DOMQuery:
"""Get a DOM query in the current screen. """Get a DOM query in the current screen.
@@ -737,22 +737,21 @@ class App(Generic[ReturnType], DOMNode):
driver = self._driver = self.driver_class(self.console, self) driver = self._driver = self.driver_class(self.console, self)
driver.start_application_mode() driver.start_application_mode()
try: with redirect_stdout(StdoutRedirector(self.devtools, self._log_file)): # type: ignore
mount_event = events.Mount(sender=self) try:
await self.dispatch_message(mount_event) mount_event = events.Mount(sender=self)
await self.dispatch_message(mount_event)
self.title = self._title self.title = self._title
self.stylesheet.update(self) self.stylesheet.update(self)
self.refresh() self.refresh()
await self.animator.start() await self.animator.start()
with redirect_stdout(StdoutRedirector(self.devtools, self._log_file)): # type: ignore
await self._ready() await self._ready()
await super().process_messages() await super().process_messages()
await self.animator.stop() await self.animator.stop()
await self.close_all() await self.close_all()
finally: finally:
driver.stop_application_mode() driver.stop_application_mode()
except Exception as error: except Exception as error:
self.on_exception(error) self.on_exception(error)
finally: finally:
@@ -872,7 +871,11 @@ class App(Generic[ReturnType], DOMNode):
await self.close_messages() await self.close_messages()
def refresh(self, *, repaint: bool = True, layout: bool = False) -> None: def refresh(self, *, repaint: bool = True, layout: bool = False) -> None:
self._display(self.screen._compositor) self.screen.refresh(repaint=repaint, layout=layout)
# self._display(self.screen._compositor.render())
def _paint(self):
self._display(self.screen._compositor.render())
def refresh_css(self, animate: bool = True) -> None: def refresh_css(self, animate: bool = True) -> None:
"""Refresh CSS. """Refresh CSS.
@@ -1052,11 +1055,11 @@ class App(Generic[ReturnType], DOMNode):
async def handle_update(self, message: messages.Update) -> None: async def handle_update(self, message: messages.Update) -> None:
message.stop() message.stop()
self.app.refresh() self._paint()
async def handle_layout(self, message: messages.Layout) -> None: async def handle_layout(self, message: messages.Layout) -> None:
message.stop() message.stop()
self.app.refresh() self._paint()
async def on_key(self, event: events.Key) -> None: async def on_key(self, event: events.Key) -> None:
if event.key == "tab": if event.key == "tab":
@@ -1071,6 +1074,9 @@ class App(Generic[ReturnType], DOMNode):
await self.close_messages() await self.close_messages()
async def on_resize(self, event: events.Resize) -> None: async def on_resize(self, event: events.Resize) -> None:
event.stop()
self.screen._screen_resized(event.size)
await self.screen.post_message(event) await self.screen.post_message(event)
async def action_press(self, key: str) -> None: async def action_press(self, key: str) -> None:

View File

@@ -476,7 +476,6 @@ class Styles(StylesBase):
return self._rules.get(rule, default) return self._rules.get(rule, default)
def refresh(self, *, layout: bool = False) -> None: def refresh(self, *, layout: bool = False) -> None:
print(self, self.node, "REFRESH", layout)
if self.node is not None: if self.node is not None:
self.node.refresh(layout=layout) self.node.refresh(layout=layout)

View File

@@ -11,12 +11,12 @@ class Blank:
"""Draw solid background color.""" """Draw solid background color."""
def __init__(self, color: Color | str = "transparent") -> None: def __init__(self, color: Color | str = "transparent") -> None:
background = ( background = color if isinstance(color, Color) else Color.parse(color)
color.rich_color self._style = (
if isinstance(color, Color) Style()
else Color.parse(color).rich_color if background.is_transparent
else Style.from_color(None, background.rich_color)
) )
self._style = Style.from_color(None, background)
def __rich_console__( def __rich_console__(
self, console: Console, options: ConsoleOptions self, console: Console, options: ConsoleOptions

View File

@@ -9,9 +9,11 @@ from rich.style import Style
from . import events, messages, errors from . import events, messages, errors
from .geometry import Offset, Region from .color import Color
from .geometry import Offset, Region, Size
from ._compositor import Compositor, MapGeometry from ._compositor import Compositor, MapGeometry
from .reactive import Reactive from .reactive import Reactive
from .renderables.blank import Blank
from .widget import Widget from .widget import Widget
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
@@ -31,10 +33,6 @@ class Screen(Widget):
Screen { Screen {
layout: vertical; layout: vertical;
overflow-y: auto; overflow-y: auto;
/*
background: $surface;
color: $text-surface;
*/
} }
""" """
@@ -45,11 +43,15 @@ class Screen(Widget):
self._compositor = Compositor() self._compositor = Compositor()
self._dirty_widgets: set[Widget] = set() self._dirty_widgets: set[Widget] = set()
@property
def is_transparent(self) -> bool:
return False
def watch_dark(self, dark: bool) -> None: def watch_dark(self, dark: bool) -> None:
pass pass
def render(self) -> RenderableType: def render(self) -> RenderableType:
return self.app.render() return Blank()
def get_offset(self, widget: Widget) -> Offset: def get_offset(self, widget: Widget) -> Offset:
"""Get the absolute offset of a given Widget. """Get the absolute offset of a given Widget.
@@ -114,15 +116,16 @@ class Screen(Widget):
self._dirty_widgets.clear() self._dirty_widgets.clear()
self._update_timer.pause() self._update_timer.pause()
def _refresh_layout(self) -> None: def _refresh_layout(self, size: Size | None = None, full: bool = False) -> None:
"""Refresh the layout (can change size and positions of widgets).""" """Refresh the layout (can change size and positions of widgets)."""
if not self.size: size = self.size if size is None else size
if not size:
return return
# This paint the entire screen, so replaces the batched dirty widgets
self._compositor.update_widgets(self._dirty_widgets) self._compositor.update_widgets(self._dirty_widgets)
self._update_timer.pause() self._update_timer.pause()
try: try:
hidden, shown, resized = self._compositor.reflow(self, self.size) hidden, shown, resized = self._compositor.reflow(self, size)
Hide = events.Hide Hide = events.Hide
Show = events.Show Show = events.Show
@@ -131,6 +134,7 @@ class Screen(Widget):
for widget in shown: for widget in shown:
widget.post_message_no_wait(Show(self)) widget.post_message_no_wait(Show(self))
# We want to send a resize event to widgets that were just added or change since last layout
send_resize = shown | resized send_resize = shown | resized
for ( for (
@@ -151,7 +155,7 @@ class Screen(Widget):
self.app.on_exception(error) self.app.on_exception(error)
return return
display_update = self._compositor.render() display_update = self._compositor.render(full=full)
if display_update is not None: if display_update is not None:
self.app._display(display_update) self.app._display(display_update)
@@ -172,9 +176,13 @@ class Screen(Widget):
UPDATE_PERIOD, self._on_update, name="screen_update", pause=True UPDATE_PERIOD, self._on_update, name="screen_update", pause=True
) )
def _screen_resized(self, size: Size):
self._refresh_layout(size, full=True)
async def on_resize(self, event: events.Resize) -> None: async def on_resize(self, event: events.Resize) -> None:
self.size_updated(event.size, event.virtual_size, event.container_size)
event.stop() event.stop()
# self._size = event.size
# self._refresh_layout(event.size, full=True)
async def _on_mouse_move(self, event: events.MouseMove) -> None: async def _on_mouse_move(self, event: events.MouseMove) -> None:
try: try:

View File

@@ -769,7 +769,6 @@ class Widget(DOMNode):
self._size = size self._size = size
self._virtual_size = virtual_size self._virtual_size = virtual_size
self._container_size = container_size self._container_size = container_size
if self.is_container: if self.is_container:
self._refresh_scrollbars() self._refresh_scrollbars()
width, height = self.container_size width, height = self.container_size