Skip to content

Commit 5f35983

Browse files
committed
Fix rendering issues when the height of terminal < height of live blocks, closes #30
Even if blocks were idle, they were still counted as rendered, causing text overlap.
1 parent 2f9ed3a commit 5f35983

File tree

2 files changed

+305
-91
lines changed

2 files changed

+305
-91
lines changed

lib/owl/live_screen.ex

Lines changed: 203 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -456,10 +456,20 @@ defmodule Owl.LiveScreen do
456456

457457
{state, render_above_data, io_reply} = render_above(state)
458458

459+
# -1, because we need to leave one row for the last new line (cursor)
460+
rows_left = Owl.IO.rows(state.device) - 1
461+
462+
{state, render_added_blocks_data, rows_left} =
463+
render_added_blocks(state, terminal_width, rows_left)
464+
459465
{state, render_updated_blocks_data} =
460-
rerender_updated_blocks(state, render_above_data != [], terminal_width)
466+
rerender_updated_blocks(state, render_above_data != [], terminal_width, rows_left)
461467

462-
{state, render_added_blocks_data} = render_added_blocks(state, terminal_width)
468+
state = %{
469+
state
470+
| blocks_to_add: [],
471+
rendered_blocks: state.rendered_blocks ++ state.blocks_to_add
472+
}
463473

464474
data =
465475
[
@@ -482,7 +492,7 @@ defmodule Owl.LiveScreen do
482492
%{state | block_states: %{}, notify_on_next_render: []}
483493
end
484494

485-
defp get_content(state, block_id, terminal_width) do
495+
defp get_content(state, block_id, terminal_width, rows_left) do
486496
case Map.fetch(state.block_states, block_id) do
487497
{:ok, block_state} ->
488498
block_content = state.render_functions[block_id].(block_state)
@@ -495,15 +505,71 @@ defmodule Owl.LiveScreen do
495505
line -> Owl.Data.chunk_every(line, terminal_width)
496506
end)
497507

498-
{
508+
block_height = length(lines)
509+
510+
max_height = max(block_height, state.rendered_content_height[block_id][:max] || 0)
511+
lines = lines ++ List.duplicate([], max_height - block_height)
512+
block_height = max_height
513+
new_rows_left = max(rows_left - block_height, 0)
514+
515+
content_height_to_render = rows_left - new_rows_left
516+
517+
lines_to_render =
518+
lines
519+
|> Enum.reverse()
520+
|> Enum.slice(0, content_height_to_render)
521+
|> Enum.reverse()
522+
523+
block_content =
499524
lines
500525
|> Enum.map(&[IO.ANSI.clear_line(), &1])
501-
|> Owl.Data.unlines(),
502-
length(lines)
526+
|> Owl.Data.unlines()
527+
528+
content_to_render =
529+
if content_height_to_render == block_height do
530+
block_content
531+
else
532+
lines_to_render
533+
|> Enum.map(&[IO.ANSI.clear_line(), &1])
534+
|> Owl.Data.unlines()
535+
end
536+
537+
%{
538+
rendered_new_state?: true,
539+
block_content: block_content,
540+
block_height: block_height,
541+
new_rows_left: new_rows_left,
542+
content_to_render: content_to_render,
543+
content_to_render_height: content_height_to_render
503544
}
504545

505546
:error ->
506-
{state.content[block_id], state.rendered_content_height[block_id]}
547+
block_content = state.content[block_id]
548+
block_height = state.rendered_content_height[block_id].max
549+
550+
new_rows_left = max(rows_left - block_height, 0)
551+
content_height_to_render = rows_left - new_rows_left
552+
553+
content_to_render =
554+
if content_height_to_render == block_height do
555+
block_content
556+
else
557+
block_content
558+
|> Owl.Data.lines()
559+
|> Enum.reverse()
560+
|> Enum.slice(0, content_height_to_render)
561+
|> Enum.reverse()
562+
|> Owl.Data.unlines()
563+
end
564+
565+
%{
566+
rendered_new_state?: false,
567+
block_content: block_content,
568+
block_height: block_height,
569+
new_rows_left: new_rows_left,
570+
content_to_render: content_to_render,
571+
content_to_render_height: content_height_to_render
572+
}
507573
end
508574
end
509575

@@ -512,7 +578,7 @@ defmodule Owl.LiveScreen do
512578
defp render_above(%{put_above_blocks: []} = state), do: {state, [], &noop/0}
513579

514580
defp render_above(%{put_above_blocks: put_above_blocks} = state) do
515-
blocks_height = Enum.sum(Map.values(state.rendered_content_height))
581+
blocks_height = Enum.sum_by(Map.values(state.rendered_content_height), & &1.rendered)
516582
data = Enum.reverse(put_above_blocks)
517583

518584
cursor_up =
@@ -548,101 +614,153 @@ defmodule Owl.LiveScreen do
548614
List.duplicate([IO.ANSI.cursor_up(1), IO.ANSI.clear_line()], n)
549615
end
550616

551-
defp rerender_updated_blocks(state, rendered_above?, terminal_width) do
617+
defp rerender_updated_blocks(state, rendered_above?, terminal_width, rows_left) do
552618
blocks_to_replace = Map.keys(state.block_states) -- state.blocks_to_add
553619

554620
if not rendered_above? and Enum.empty?(blocks_to_replace) do
555621
{state, []}
556622
else
557-
{content_blocks, %{total_height: total_height, state: state, next_offset: return_to_end}} =
623+
{content_blocks, %{state: state}} =
558624
state.rendered_blocks
559-
|> Enum.flat_map_reduce(
560-
%{total_height: 0, next_offset: 0, force_rerender?: rendered_above?, state: state},
561-
fn block_id,
625+
|> Enum.reverse()
626+
|> Enum.map_reduce(
627+
%{state: state, rows_left: rows_left},
628+
fn block_id, %{state: state, rows_left: rows_left} ->
629+
%{
630+
rendered_new_state?: rendered_new_state?,
631+
block_content: block_content,
632+
block_height: block_height,
633+
new_rows_left: new_rows_left,
634+
content_to_render: content_to_render,
635+
content_to_render_height: content_to_render_height
636+
} = get_content(state, block_id, terminal_width, rows_left)
637+
638+
{%{
639+
height_diff:
640+
content_to_render_height - state.rendered_content_height[block_id].rendered,
641+
content: content_to_render,
642+
height: content_to_render_height,
643+
rendered_new_state?: rendered_new_state?,
644+
visible?: rows_left > 0
645+
},
562646
%{
563-
total_height: total_height,
564-
next_offset: next_offset,
565-
state: state,
566-
force_rerender?: force_rerender?
567-
} ->
568-
if force_rerender? or block_id in blocks_to_replace do
569-
{block_content, height} = get_content(state, block_id, terminal_width)
570-
571-
max_height = max(height, state.rendered_content_height[block_id])
572-
573-
block_content = [
574-
block_content,
575-
List.duplicate(["\n", IO.ANSI.clear_line()], max_height - height)
576-
]
577-
578-
{[%{offset: next_offset, content: block_content}],
579-
%{
580-
total_height: total_height + state.rendered_content_height[block_id],
581-
next_offset: 0,
582-
force_rerender?:
583-
force_rerender? || height > state.rendered_content_height[block_id],
584-
state: %{
585-
state
586-
| rendered_content_height:
587-
Map.put(state.rendered_content_height, block_id, max_height),
588-
content: Map.put(state.content, block_id, block_content)
589-
}
590-
}}
591-
else
592-
height = state.rendered_content_height[block_id]
593-
594-
{[],
595-
%{
596-
total_height: total_height + height,
597-
next_offset: next_offset + height,
598-
state: state,
599-
force_rerender?: force_rerender?
600-
}}
647+
rows_left: new_rows_left,
648+
state: %{
649+
state
650+
| rendered_content_height:
651+
Map.put(state.rendered_content_height, block_id, %{
652+
rendered: content_to_render_height,
653+
max: block_height
654+
}),
655+
content: Map.put(state.content, block_id, block_content)
656+
}
657+
}}
658+
end
659+
)
660+
661+
content_blocks = Enum.reverse(content_blocks)
662+
663+
{content_blocks, %{total_height: total_height, next_offset: return_to_end}} =
664+
Enum.flat_map_reduce(
665+
content_blocks,
666+
%{
667+
content_above_increased_height?: rendered_above?,
668+
next_offset: 0,
669+
total_height: 0
670+
},
671+
fn block, acc ->
672+
cond do
673+
not block.visible? ->
674+
{[], acc}
675+
676+
acc.content_above_increased_height? || block.height_diff > 0 ->
677+
{[Map.put(block, :offset, acc.next_offset)],
678+
%{
679+
acc
680+
| content_above_increased_height?: true,
681+
next_offset: 0,
682+
total_height: block.height + acc.total_height - block.height_diff
683+
}}
684+
685+
block.rendered_new_state? ->
686+
{[Map.put(block, :offset, acc.next_offset)],
687+
%{
688+
acc
689+
| next_offset: 0,
690+
# - block.height_diff
691+
total_height: block.height + acc.total_height
692+
}}
693+
694+
acc.next_offset == 0 and acc.total_height == 0 ->
695+
{[], acc}
696+
697+
true ->
698+
{[],
699+
%{
700+
acc
701+
| next_offset: acc.next_offset + block.height,
702+
total_height: block.height + acc.total_height
703+
}}
601704
end
602705
end
603706
)
604707

605-
if content_blocks == [] do
606-
{state, []}
607-
else
608-
data = [
609-
if(rendered_above? or total_height == 0, do: [], else: IO.ANSI.cursor_up(total_height)),
610-
content_blocks
611-
|> Enum.map(fn
612-
%{offset: 0, content: content} -> content
613-
%{offset: offset, content: content} -> [IO.ANSI.cursor_down(offset), content]
614-
end)
615-
|> Owl.Data.unlines(),
616-
if(return_to_end == 0, do: [], else: IO.ANSI.cursor_down(return_to_end))
617-
]
708+
data =
709+
if content_blocks == [] do
710+
[]
711+
else
712+
[
713+
if(rendered_above?, do: [], else: IO.ANSI.cursor_up(total_height)),
714+
content_blocks
715+
|> Enum.map(fn
716+
%{offset: 0, content: content} -> content
717+
%{offset: offset, content: content} -> [IO.ANSI.cursor_down(offset), content]
718+
end)
719+
|> Owl.Data.unlines(),
720+
if(return_to_end == 0, do: [], else: IO.ANSI.cursor_down(return_to_end))
721+
]
722+
end
618723

619-
{state, data}
620-
end
724+
{state, data}
621725
end
622726
end
623727

624-
defp render_added_blocks(%{blocks_to_add: []} = state, _terminal_width), do: {state, []}
625-
626-
defp render_added_blocks(state, terminal_width) do
627-
{content_blocks, state} =
628-
Enum.map_reduce(state.blocks_to_add, state, fn block_id, state ->
629-
{block_content, height} = get_content(state, block_id, terminal_width)
728+
defp render_added_blocks(%{blocks_to_add: []} = state, _terminal_width, rows_left),
729+
do: {state, [], rows_left}
730+
731+
defp render_added_blocks(state, terminal_width, rows_left) do
732+
{content_blocks, {state, rows_left}} =
733+
state.blocks_to_add
734+
|> Enum.reverse()
735+
|> Enum.flat_map_reduce({state, rows_left}, fn block_id, {state, rows_left} ->
736+
%{
737+
rendered_new_state?: true,
738+
block_content: block_content,
739+
block_height: block_height,
740+
new_rows_left: rows_left,
741+
content_to_render: content_to_render
742+
} = get_content(state, block_id, terminal_width, rows_left)
743+
744+
content =
745+
if content_to_render == [] do
746+
[]
747+
else
748+
[content_to_render]
749+
end
630750

631-
{block_content,
632-
%{
633-
state
634-
| rendered_content_height: Map.put(state.rendered_content_height, block_id, height),
635-
content: Map.put(state.content, block_id, block_content)
636-
}}
751+
{content,
752+
{%{
753+
state
754+
| rendered_content_height:
755+
Map.put(state.rendered_content_height, block_id, %{
756+
max: block_height,
757+
rendered: block_height
758+
}),
759+
content: Map.put(state.content, block_id, block_content)
760+
}, rows_left}}
637761
end)
638762

639-
state = %{
640-
state
641-
| blocks_to_add: [],
642-
rendered_blocks: state.rendered_blocks ++ state.blocks_to_add
643-
}
644-
645-
{state, Owl.Data.unlines(content_blocks)}
763+
{state, Owl.Data.unlines(Enum.reverse(content_blocks)), rows_left}
646764
end
647765

648766
defp empty_blocks_list?(state) do

0 commit comments

Comments
 (0)