Skip to content

New Layout system #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
clipping widgets
  • Loading branch information
willmcgugan committed Jul 4, 2021
commit b4b844906ca86c79aa0180879a2587e3916a7a52
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,8 +386,8 @@ async def action_toggle_sidebar(self) -> None:
self.animator.animate(
self.bar,
"layout_offset_x",
20 if self.side else 0,
speed=30,
-40 if self.side else 0,
speed=60,
easing="in_out_cubic",
)

Expand All @@ -403,7 +403,9 @@ async def on_startup(self, event: events.Startup) -> None:

await view.dock(header, edge="top")
await view.dock(footer, edge="bottom")
await view.dock(self.bar, edge="left", size=30, z=1)
await view.dock(self.bar, edge="left", size=40, z=1)

# await view.dock(Placeholder(), Placeholder(), edge="top")

sub_view = DockView()
await sub_view.dock(Placeholder(), Placeholder(), edge="top")
Expand Down
60 changes: 21 additions & 39 deletions src/textual/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class MapRegion(NamedTuple):
order: tuple[int, int]


class ReflowResult(NamedTuple):
hidden: set[Widget]
shown: set[Widget]


class LayoutUpdate:
def __init__(self, lines: Lines, x: int, y: int) -> None:
self.lines = lines
Expand Down Expand Up @@ -70,10 +75,16 @@ def reset(self) -> None:
self.renders.clear()
self._cuts = None

def reflow(self, width: int, height: int) -> None:
def reflow(self, width: int, height: int) -> ReflowResult:
self.reset()

map = self.generate_map(width, height)

old_widgets = set(self._layout_map.keys())
new_widgets = set(map.keys())
shown_widgets = new_widgets - old_widgets
hidden_widgets = old_widgets - new_widgets

self._layout_map = map
self.width = width
self.height = height
Expand All @@ -84,6 +95,7 @@ def reflow(self, width: int, height: int) -> None:
new_renders[widget] = (region, self.renders[widget][1])

self.renders = new_renders
return ReflowResult(hidden_widgets, shown_widgets)

@abstractmethod
def generate_map(
Expand Down Expand Up @@ -150,37 +162,6 @@ def cuts(self) -> list[list[int]]:
self._cuts = [sorted(cut_set) for cut_set in cuts_sets]
return self._cuts

@classmethod
def _compare_lines(cls, lines1: Lines, lines2: Lines) -> Iterable[list[Segment]]:
"""Compares two renders and produce 'diff'"""
delta_line: list[Segment] = [Control.home().segment]
add_delta_segment = delta_line.append
move_to_column = Control.move_to_column

for y, (line1, line2) in enumerate(zip(lines1, lines2)):
if line1 == line2:
continue

add_delta_segment(Control.move_to(0, y).segment)
start_skip: int | None = None
x1 = 0
x2 = 0
for segment1, segment2 in zip(line1, line2):
if x1 == x2 and segment1 == segment2:
if start_skip is None:
start_skip = x1
else:
if start_skip is not None:
add_delta_segment(move_to_column(x2).segment)
start_skip = None
add_delta_segment(segment2)
x1 += segment1.cell_length
x2 += segment2.cell_length

if delta_line:
yield delta_line[:]
del delta_line[:]

def _get_renders(self, console: Console) -> Iterable[tuple[Region, Lines]]:
width = self.width
height = self.height
Expand Down Expand Up @@ -213,11 +194,13 @@ def render(widget: Widget, width: int, height: int) -> Lines:
new_region = region.clip(width, height)
delta_x = new_region.x - region.x
delta_y = new_region.y - region.y
# region = new_region
lines = lines[delta_y : delta_y + region.height]
region = new_region

splits = [delta_x, delta_x + region.width]
divide = Segment.divide
lines = [
list(Segment.divide(line, [delta_x, delta_x + region.width]))[1]
for line in lines
list(divide(line, splits))[1]
for line in lines[delta_y : delta_y + region.height]
]
self.renders[widget] = (region, lines)
yield region, lines
Expand Down Expand Up @@ -279,9 +262,8 @@ def render(
if final_cuts == [region.x, region.x + region.width]:
cut_segments = [line]
else:
_, *cut_segments = divide(
line, [cut - region.x for cut in final_cuts]
)
relative_cuts = [cut - region.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
Expand Down
28 changes: 6 additions & 22 deletions src/textual/layouts/dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ def generate_map(
layers: dict[int, Region] = defaultdict(lambda: layout_region)

def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
region = region + offset
region = region + offset + widget.layout_offset
if isinstance(widget, View):
sub_map = widget.layout.generate_map(
region.width, region.height, offset=region.origin
)
map.update(sub_map)
else:
map[widget] = MapRegion(region + widget.layout_offset, order)
map[widget] = MapRegion(region, order)

for index, dock in enumerate(self.docks):
dock_options = [
Expand Down Expand Up @@ -99,11 +99,7 @@ def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
if not size:
break
total += size
add_widget(
widget,
Region(x, render_y, width, size) + widget.layout_offset,
order,
)
add_widget(widget, Region(x, render_y, width, size), order)
render_y += size
remaining = max(0, remaining - size)
region = Region(x, y + total, width, height - total)
Expand All @@ -120,11 +116,7 @@ def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
if not size:
break
total += size
add_widget(
widget,
Region(x, render_y - size, width, size) + widget.layout_offset,
order,
)
add_widget(widget, Region(x, render_y - size, width, size), order)
render_y -= size
remaining = max(0, remaining - size)
region = Region(x, y, width, height - total)
Expand All @@ -141,11 +133,7 @@ def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
if not size:
break
total += size
add_widget(
widget,
Region(render_x, y, size, height) + widget.layout_offset,
order,
)
add_widget(widget, Region(render_x, y, size, height), order)
render_x += size
remaining = max(0, remaining - size)
region = Region(x + total, y, width - total, height)
Expand All @@ -162,11 +150,7 @@ def add_widget(widget: Widget, region: Region, order: tuple[int, int]):
if not size:
break
total += size
add_widget(
widget,
Region(render_x - size, y, size, height) + widget.layout_offset,
order,
)
add_widget(widget, Region(render_x - size, y, size, height), order)
render_x -= size
remaining = max(0, remaining - size)
region = Region(x, y, width - total, height)
Expand Down
14 changes: 6 additions & 8 deletions src/textual/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ async def message_update(self, message: UpdateMessage) -> None:
self.app.display(display_update)

async def message_layout(self, message: LayoutMessage) -> None:
log.debug("MESSAGE_LAYOUT %r", self.root_view)

await self.root_view.refresh_layout()

Expand Down Expand Up @@ -102,14 +101,13 @@ async def refresh_layout(self) -> None:
self.layout.reflow(width, height)
self.app.refresh()

for widget, region in self.layout:
if isinstance(widget, Widget):
await widget.post_message(
events.Resize(self, region.width, region.height)
)
# for widget, region in self.layout:
# if isinstance(widget, Widget):
# await widget.post_message(
# events.Resize(self, region.width, region.height)
# )

async def on_resize(self, event: events.Resize) -> None:
log.debug("view.on_resize")
async def on_resize(self, event: events.Resize) -> None:
self.size = Dimensions(event.width, event.height)
await self.refresh_layout()

Expand Down
3 changes: 1 addition & 2 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ async def on_event(self, event: events.Event) -> None:
await super().on_event(event)

async def on_idle(self, event: events.Idle) -> None:
if self.check_layout():
log.debug("LAYING OUT")
if self.check_layout():
self.reset_check_repaint()
self.reset_check_layout()
await self.update_layout()
Expand Down