fix(frame): start the corner fade at the brightest palette stop

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.
This commit is contained in:
beefcarver committed 2026-11-04 15:49:26 -05:00
1 parent 81116282cb
commit ec2ca72efb
4 files changed
+117 -39

No files matched your search

+27 -13
View File
@@ -2,8 +2,9 @@
The panes draw their own frames instead of using Textual's ``border`` so the
corners can be coloured. Two of them — the top right and the bottom left —
run a short fade out of the palette's hot end and back into the border grey;
everything else, including the other two corners, is that grey.
run a short fade out of the palette's hot end and back into the border grey,
brightest colour at the corner; everything else, including the other two
corners, is that grey.
The ladder is whatever palette the app is on (``c`` cycles them), which is why
every function here takes ``stops`` rather than reading a module constant.
@@ -11,12 +12,14 @@ every function here takes ``stops`` rather than reading a module constant.
from __future__ import annotations
from functools import cache
from rich.cells import cell_len
from rich.console import Group, RenderableType
from rich.text import Text
from textual.widget import Widget
from ..palettes import DEFAULT_PALETTE, PALETTES
from ..palettes import DEFAULT_PALETTE, PALETTES, brightness
from .common import active_stops
#: The frame itself: one grey, the same grey as the scrollbar thumb.
@@ -26,20 +29,32 @@ EDGE = "bright_black"
FADE_CELLS = 4
@cache
def fade_ramp(stops: tuple[str, ...]) -> tuple[str, ...]:
"""The corner ramp: brightest cell first, border grey past its end.
The colours come from the palette's hot end, but a ladder is a hue walk,
not a brightness walk: ``spectrum`` ends on ``red``, which is dimmer than
the ``yellow`` just before it, so painting the ladder backwards put the
dim colour at the corner and the glow out on the edge. Sorting the hot
end by brightness keeps the corner the brightest cell and dims outwards,
whatever theme sits behind the names.
"""
hot = [name for name in stops if name != EDGE][-FADE_CELLS:]
return tuple(sorted(hot, key=brightness, reverse=True))
def corner_fade(distance: int, stops: tuple[str, ...] = ()) -> str:
"""Colour ``distance`` cells out from a corner; 0 is the corner glyph.
The fade is short — four cells — and walks backwards down the palette from
its hot end, so the corner glows in the palette's brightest colour and then
hands over to the plain border grey.
The fade is short — four cells — and runs from the brightest colour the
palette's hot end offers back into the plain border grey.
"""
if not stops:
stops = PALETTES[DEFAULT_PALETTE]
ramp = fade_ramp(tuple(stops))
distance = max(0, distance)
if distance >= FADE_CELLS:
return EDGE
index = len(stops) - 1 - distance
return stops[index] if index >= 0 else EDGE
return ramp[distance] if distance < len(ramp) else EDGE
def inner_size(width: int, height: int) -> tuple[int, int]:
@@ -119,7 +134,6 @@ def wrap(
width = max(4, width)
height = max(2, height)
inner_width, inner_height = inner_size(width, height)
rows = len(lines[:inner_height])
framed: list[Text] = [top(width, title, stops)]
for index, line in enumerate(lines[:inner_height]):
@@ -129,12 +143,12 @@ def wrap(
line = line.copy()
line.truncate(inner_width, overflow="ellipsis")
row = Text(no_wrap=True)
row.append_text(side_row(index, rows, "left", stops))
row.append_text(side_row(index, inner_height, "left", stops))
row.append_text(line)
pad = inner_width - cell_len(line.plain)
if pad > 0:
row.append(" " * pad)
row.append_text(side_row(index, rows, "right", stops))
row.append_text(side_row(index, inner_height, "right", stops))
framed.append(row)
while len(framed) < height - 1:
index = len(framed) - 1