Skip to content

Commit

Permalink
Fix for blanking bug
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Aug 5, 2021
1 parent ae6aa78 commit 6618f0d
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 118 deletions.
37 changes: 29 additions & 8 deletions examples/code_viewer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import os
import sys
from rich.console import RenderableType

from rich.syntax import Syntax
from rich.traceback import Traceback

from textual import events
from textual.app import App
Expand All @@ -12,9 +14,13 @@ class MyApp(App):
"""An example of a very simple Textual App"""

async def on_load(self, event: events.Load) -> None:
"""Sent before going in to application mode."""

# Bind our basic keys
await self.bind("b", "view.toggle('sidebar')", "Toggle sidebar")
await self.bind("q", "quit", "Quit")

# Get path to show
try:
self.path = sys.argv[1]
except IndexError:
Expand All @@ -23,28 +29,43 @@ async def on_load(self, event: events.Load) -> None:
)

async def on_mount(self, event: events.Mount) -> None:
"""Call after terminal goes in to application mode"""

# Create our widgets
# In this a scroll view for the code and a directory tree
self.body = ScrollView()
self.directory = DirectoryTree(self.path, "Code")

# Dock our widgets
await self.view.dock(Header(), edge="top")
await self.view.dock(Footer(), edge="bottom")

# Note the directory is also in a scroll view
await self.view.dock(
ScrollView(self.directory), edge="left", size=32, name="sidebar"
)
await self.view.dock(self.body, edge="top")

async def message_file_click(self, message: FileClick) -> None:
syntax = Syntax.from_path(
message.path,
line_numbers=True,
word_wrap=True,
indent_guides=True,
theme="monokai",
)
"""A message sent by the directory tree when a file is clicked."""

syntax: RenderableType
try:
# Construct a Syntax object for the path in the message
syntax = Syntax.from_path(
message.path,
line_numbers=True,
word_wrap=True,
indent_guides=True,
theme="monokai",
)
except Exception:
# Possibly a binary file
# For demonstration purposes we will show the traceback
syntax = Traceback(theme="monokai", width=None, show_locals=True)
self.app.sub_title = os.path.basename(message.path)
await self.body.update(syntax)
self.body.home()


# Run our app class
MyApp.run(title="Code Viewer", log="textual.log")
7 changes: 0 additions & 7 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,6 @@ async def shutdown(self):
await self.close_messages()

def refresh(self, repaint: bool = True, layout: bool = False) -> None:
log("APP REFRESH")
sync_available = os.environ.get("TERM_PROGRAM", "") != "Apple_Terminal"
if not self._closed:
console = self.console
Expand All @@ -352,13 +351,7 @@ def display(self, renderable: RenderableType) -> None:
if not self._closed:
console = self.console
try:
# if sync_available:
# console.file.write("\x1bP=1s\x1b\\")
# with console:
console.print(renderable)
# if sync_available:
# console.file.write("\x1bP=2s\x1b\\")
# console.file.flush()
except Exception:
self.panic()

Expand Down
4 changes: 2 additions & 2 deletions src/textual/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ def overlaps(self, other: Region) -> bool:
x, y, x2, y2 = self.corners
ox, oy, ox2, oy2 = other.corners

return ((x2 > ox >= x) or (x2 > ox2 > x) or (ox < x and ox2 > x2)) and (
(y2 > oy >= y) or (y2 > oy2 > y) or (oy < y and oy2 > y2)
return ((x2 > ox >= x) or (x2 > ox2 >= x) or (ox < x and ox2 >= x2)) and (
(y2 > oy >= y) or (y2 > oy2 >= y) or (oy < y and oy2 >= y2)
)

def contains(self, x: int, y: int) -> bool:
Expand Down
103 changes: 38 additions & 65 deletions src/textual/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ def check_update(self) -> bool:

def require_update(self) -> None:
self._require_update = True
self.reset()
self._layout_map = None

def reset_update(self) -> None:
self._require_update = False
Expand All @@ -97,7 +99,6 @@ def reset(self) -> None:
# self.regions.clear()
# self._layout_map = None

@timer("reflow")
def reflow(
self, console: Console, width: int, height: int, scroll: Offset
) -> ReflowResult:
Expand All @@ -106,29 +107,14 @@ def reflow(
self.width = width
self.height = height

with timer("generate_map"):
map = self.generate_map(
console,
Size(width, height),
Region(0, 0, width, height),
scroll,
)
map = self.generate_map(
console,
Size(width, height),
Region(0, 0, width, height),
scroll,
)
self._require_update = False

# log(map.widgets)
# map = {
# widget: OrderedRegion(region + offset, order)
# for widget, (region, order, offset) in map.items()
# }

# Filter out widgets that are off screen or zero area

# map = {
# widget: map_region
# for widget, map_region in map.items()
# if map_region.region and viewport.overlaps(map_region.region)
# }

old_widgets = set() if self.map is None else set(self.map.keys())
new_widgets = set(map.keys())
# Newly visible widgets
Expand Down Expand Up @@ -189,12 +175,6 @@ def __iter__(self) -> Iterator[tuple[Widget, Region, Region]]:
for widget, (region, order, clip) in layers:
yield widget, region.intersection(clip), region

# def __reversed__(self) -> Iterable[tuple[Widget, Region]]:
# if self.map is not None:
# layers = sorted(self.map.items(), key=lambda item: item[1].order)
# for widget, (region, _order, clip) in layers:
# yield widget, region.intersection(clip), region

def get_offset(self, widget: Widget) -> Offset:
try:
return self.map[widget].region.origin
Expand All @@ -203,8 +183,8 @@ def get_offset(self, widget: Widget) -> Offset:

def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
"""Get the widget under the given point or None."""
for widget, region, _ in self:
if widget.is_visual and region.contains(x, y):
for widget, cropped_region, region in self:
if widget.is_visual and cropped_region.contains(x, y):
return widget, region
raise NoWidget(f"No widget under screen coordinate ({x}, {y})")

Expand Down Expand Up @@ -284,8 +264,6 @@ def _get_renders(self, console: Console) -> Iterable[tuple[Region, Region, Lines
continue

lines = widget._get_lines()
# width, height = region.size
# lines = Segment.set_shape(lines, width, height)

if clip in region:
yield region, clip, lines
Expand All @@ -310,7 +288,6 @@ def _assemble_chops(
line for _, line in sorted(bucket.items()) if line is not None
)

@timer("render")
def render(
self,
console: Console,
Expand Down Expand Up @@ -347,38 +324,34 @@ def render(
[_Segment(" " * width, background_style)] for _ in range(height)
]
# Go through all the renders in reverse order and fill buckets with no render
with timer("renders"):
renders = list(self._get_renders(console))

with timer("chops"):
clip_y, clip_y2 = crop_region.y_extents
for region, clip, lines in chain(
renders, [(screen, screen, background_render)]
):
# clip = clip.intersection(crop_region)
render_region = region.intersection(clip)
for y, line in enumerate(lines, render_region.y):
if clip_y > y > clip_y2:
continue
# first_cut = clamp(render_region.x, clip_x, clip_x2)
# last_cut = clamp(render_region.x + render_region.width, clip_x, clip_x2)
first_cut = render_region.x
last_cut = render_region.x_max
final_cuts = [
cut for cut in cuts[y] if (last_cut >= cut >= first_cut)
]
# final_cuts = cuts[y]

# log(final_cuts, render_region.x_extents)
if len(final_cuts) == 2:
cut_segments = [line]
else:
render_x = render_region.x
relative_cuts = [cut - render_x for cut in final_cuts]
_, *cut_segments = divide(line, relative_cuts)
for cut, segments in zip(final_cuts, cut_segments):
if chops[y][cut] is None:
chops[y][cut] = segments
renders = list(self._get_renders(console))

clip_y, clip_y2 = crop_region.y_extents
for region, clip, lines in chain(
renders, [(screen, screen, background_render)]
):
# clip = clip.intersection(crop_region)
render_region = region.intersection(clip)
for y, line in enumerate(lines, render_region.y):
if clip_y > y > clip_y2:
continue
# first_cut = clamp(render_region.x, clip_x, clip_x2)
# last_cut = clamp(render_region.x + render_region.width, clip_x, clip_x2)
first_cut = render_region.x
last_cut = render_region.x_max
final_cuts = [cut for cut in cuts[y] if (last_cut >= cut >= first_cut)]
# final_cuts = cuts[y]

# log(final_cuts, render_region.x_extents)
if len(final_cuts) == 2:
cut_segments = [line]
else:
render_x = render_region.x
relative_cuts = [cut - render_x for cut in final_cuts]
_, *cut_segments = divide(line, relative_cuts)
for cut, segments in zip(final_cuts, cut_segments):
if chops[y][cut] is None:
chops[y][cut] = segments

# Assemble the cut renders in to lists of segments
crop_x, crop_y, crop_x2, crop_y2 = crop_region.corners
Expand Down
2 changes: 1 addition & 1 deletion src/textual/layouts/vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def add_widget(widget: Widget, region: Region, clip: Region) -> None:
or widget.render_cache.size.width != render_width
):
widget.render_lines_free(render_width)
log("RENDERING")
assert widget.render_cache is not None
render_height = widget.render_cache.size.height
region = Region(x, y, render_width, render_height)
add_widget(widget, region - scroll, viewport)
Expand Down
2 changes: 2 additions & 0 deletions src/textual/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ async def on_resize(self, event: events.Resize) -> None:
self._update_size(event.size)
if self.is_root_view:
await self.refresh_layout()
self.app.refresh()
event.stop()

def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
return self.layout.get_widget_at(x, y)
Expand Down
17 changes: 11 additions & 6 deletions src/textual/views/_window_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
from ..layouts.vertical import VerticalLayout
from ..view import View
from ..message import Message
from ..messages import UpdateMessage
from ..widget import Widget
from ..widgets import Static


class VirtualSizeChange(Message):
class WindowChange(Message):
pass


Expand All @@ -37,18 +38,22 @@ async def update(self, widget: Widget | RenderableType) -> None:
layout.add(self.widget)
await self.refresh_layout()
self.refresh(layout=True)
await self.emit(VirtualSizeChange(self))
await self.emit(WindowChange(self))

async def watch_virtual_size(self, size: Size) -> None:
self.log("VIRTUAL SIZE CHANGE")
await self.emit(VirtualSizeChange(self))
await self.emit(WindowChange(self))

async def watch_scroll_x(self, value: int) -> None:
self.refresh(layout=True)

async def watch_scroll_y(self, value: int) -> None:
self.refresh(layout=True)

# async def on_resize(self, event: events.Resize) -> None:
# # self.layout.renders.pop(self.widget)
# self.require_repaint()
async def message_update(self, message: UpdateMessage) -> None:
self.layout.require_update()
await self.root_view.refresh_layout()
# self.app.refresh()

async def on_resize(self, event: events.Resize) -> None:
await self.emit(WindowChange(self))
19 changes: 2 additions & 17 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,26 +154,12 @@ def _get_lines(self) -> Lines:
"""
if self.render_cache is None:
self.render_cache = self.render_lines()
self.log("RENDERING", self)
lines = self.render_cache.lines
return lines

def clear_render_cache(self) -> None:
self.render_cache = None

# def require_repaint(self) -> None:
# """Mark widget as requiring a repaint.

# Actual repaint is done by parent on idle.
# """
# self.render_cache = None
# self._repaint_required = True
# self.post_message_no_wait(events.Null(self))

# def require_layout(self) -> None:
# self._layout_required = True
# self.post_message_no_wait(events.Null(self))

def check_repaint(self) -> bool:
return self._repaint_required

Expand Down Expand Up @@ -207,9 +193,10 @@ def refresh(self, repaint: bool = True, layout: bool = False) -> None:
layout (bool, optional): Also layout widgets in the view. Defaults to False.
"""
if layout:
self.clear_render_cache()
self._layout_required = True
elif repaint:
# self.clear_render_cache()
self.clear_render_cache()
self._repaint_required = True
self.post_message_no_wait(events.Null(self))

Expand Down Expand Up @@ -240,8 +227,6 @@ async def on_idle(self, event: events.Idle) -> None:
if self.check_layout():
self.reset_check_repaint()
self.reset_check_layout()
# await self.emit(UpdateMessage(self, self))
# await self.emit(UpdateMessage(self, self, layout=False))
await self.emit(LayoutMessage(self))
elif self.check_repaint():
self.render_cache = None
Expand Down
2 changes: 1 addition & 1 deletion src/textual/widgets/_directory_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def load_directory(self, node: TreeNode[DirEntry]):
await node.add(entry.name, DirEntry(entry.path, entry.is_dir()))
node.loaded = True
await node.expand()
self.require_repaint()
# self.refresh(layout=True)

async def message_tree_click(self, message: TreeClick[DirEntry]) -> None:
dir_entry = message.node.data
Expand Down
Loading

0 comments on commit 6618f0d

Please sign in to comment.