A ladder is a hue walk, not a brightness walk, so painting the hot end backwards put the dim colour at the corner. Sort the ramp by stock brightness and follow the pane height for the bottom-left glow.
119 lines
4.2 KiB
Python
119 lines
4.2 KiB
Python
"""The BBS frames: grey edges, and a short palette glow in two corners."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from rich.text import Text
|
|
|
|
from beefy_player.palettes import PALETTES, brightness
|
|
from beefy_player.widgets import frame
|
|
|
|
#: The original ladder, used as a fixed reference in these tests.
|
|
STOPS = PALETTES["spectrum"]
|
|
#: Its glow: the brightest cell first, dimmer cells towards the edge.
|
|
RAMP = frame.fade_ramp(STOPS)
|
|
HOT = RAMP[0] # the corner colour
|
|
|
|
|
|
def styles_of(line: Text) -> list[tuple[str, str]]:
|
|
return [
|
|
(line.plain[span.start : span.end], str(span.style)) for span in line.spans
|
|
]
|
|
|
|
|
|
def test_the_corner_gets_the_brightest_colour_of_the_fade() -> None:
|
|
ranks = [brightness(colour) for colour in RAMP]
|
|
assert ranks == sorted(ranks, reverse=True)
|
|
# spectrum's hot end is red, which is dimmer than the yellow before it:
|
|
# the corner must still be the bright one, not the ladder's last stop
|
|
assert STOPS[-1] != HOT
|
|
assert brightness(HOT) > brightness(STOPS[-1])
|
|
assert set(RAMP) <= set(STOPS)
|
|
assert frame.corner_fade(len(RAMP), STOPS) == frame.EDGE
|
|
|
|
|
|
def test_only_the_top_right_corner_fades_on_the_top_edge() -> None:
|
|
spans = styles_of(frame.top(30, stops=STOPS))
|
|
assert spans[0] == ("╭", frame.EDGE) # plain grey corner
|
|
# the fade runs into the top-right corner, brightest cell last
|
|
assert [style for _text, style in spans[-4:-1]] == [RAMP[3], RAMP[2], RAMP[1]]
|
|
assert spans[-1] == ("╮", f"bold {HOT}")
|
|
|
|
|
|
def test_only_the_bottom_left_corner_fades_on_the_bottom_edge() -> None:
|
|
spans = styles_of(frame.bottom(30, STOPS))
|
|
assert spans[0] == ("╰", f"bold {HOT}")
|
|
assert [style for _text, style in spans[1:4]] == [RAMP[1], RAMP[2], RAMP[3]]
|
|
assert spans[-1] == ("╯", frame.EDGE) # plain grey corner
|
|
|
|
|
|
def test_the_sides_glow_towards_their_own_corner_only() -> None:
|
|
# left column fades up from the bottom-left corner
|
|
assert [frame.side_style(row, 10, "left", STOPS) for row in range(10)] == [
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
RAMP[3],
|
|
RAMP[2],
|
|
RAMP[1],
|
|
]
|
|
# right column fades down from the top-right corner
|
|
assert [frame.side_style(row, 10, "right", STOPS) for row in range(10)] == [
|
|
RAMP[1],
|
|
RAMP[2],
|
|
RAMP[3],
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
frame.EDGE,
|
|
]
|
|
|
|
|
|
def test_the_title_sits_after_the_left_fade() -> None:
|
|
line = frame.top(30, "STATIONS", STOPS)
|
|
assert line.plain.startswith("╭─ STATIONS ─")
|
|
assert "STATIONS" in line.plain
|
|
# a pane too narrow for the title drops it rather than truncating
|
|
assert "STATIONS" not in frame.top(10, "STATIONS", STOPS).plain
|
|
|
|
|
|
def test_wrap_frames_the_content_on_every_row() -> None:
|
|
content = [Text("hello"), Text("world")]
|
|
group = frame.wrap(content, 20, 6, "PANE", STOPS)
|
|
lines = [line.plain for line in group.renderables]
|
|
assert len(lines) == 6
|
|
assert lines[0].startswith("╭") and lines[-1].startswith("╰")
|
|
assert all(len(line) == 20 for line in lines)
|
|
assert all(line.startswith("│") and line.endswith("│") for line in lines[1:-1])
|
|
assert lines[1].startswith("│hello")
|
|
|
|
|
|
def test_content_wider_than_the_pane_is_trimmed_not_spilled() -> None:
|
|
"""A pane must never draw over its neighbour."""
|
|
long_line = Text("x" * 40)
|
|
group = frame.wrap([long_line], 20, 4, "PANE", STOPS)
|
|
lines = [line.plain for line in group.renderables]
|
|
assert all(len(line) == 20 for line in lines)
|
|
assert lines[1].endswith("│")
|
|
# the original renderable is left untouched
|
|
assert long_line.plain == "x" * 40
|
|
|
|
|
|
def test_a_short_pane_still_glows_in_its_bottom_left_corner() -> None:
|
|
"""The fade follows the pane's height, not the number of content rows."""
|
|
group = frame.wrap([Text("one line")], 20, 6, stops=STOPS)
|
|
lines = list(group.renderables)
|
|
# rows 1-4 are the content box; the fade climbs its last three rows
|
|
assert str(lines[1].spans[0].style) == frame.EDGE
|
|
assert [str(line.spans[0].style) for line in lines[2:5]] == [
|
|
RAMP[3],
|
|
RAMP[2],
|
|
RAMP[1],
|
|
]
|