feat(palette): sixteen ANSI colour ladders, cycled with c
This commit is contained in:
1 parent
253b462df1
commit
43ddd8666b
14 files changed
+622
-111
No files matched your search
@@ -1,9 +1,12 @@
|
||||
"""Pane frames: one grey box, with two corners fading out of it.
|
||||
"""Pane frames: one grey box whose corners glow in the active palette.
|
||||
|
||||
The panes draw their own frames instead of using Textual's ``border`` so the
|
||||
corners can be coloured individually. Two of them — the top right and the
|
||||
bottom left — run white → light grey → bright cyan → cyan along both arms;
|
||||
everything else, including the other two corners, is the border grey.
|
||||
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.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -13,26 +16,30 @@ from rich.console import Group, RenderableType
|
||||
from rich.text import Text
|
||||
from textual.widget import Widget
|
||||
|
||||
from ..palettes import DEFAULT_PALETTE, PALETTES
|
||||
from .common import active_stops
|
||||
|
||||
#: The frame itself: one grey, the same grey as the scrollbar thumb.
|
||||
EDGE = "bright_black"
|
||||
|
||||
#: A faded corner runs white → light grey → bright cyan → cyan, then settles
|
||||
#: back into the border grey.
|
||||
CORNER_GRADIENT = ("bright_white", "white", "bright_cyan", "cyan")
|
||||
#: How many cells a corner fade reaches into the edge.
|
||||
FADE_CELLS = 4
|
||||
|
||||
|
||||
def fade(distance: int) -> str:
|
||||
"""Colour ``distance`` cells away from a corner (0 is the corner itself)."""
|
||||
if 0 <= distance < len(CORNER_GRADIENT):
|
||||
return CORNER_GRADIENT[distance]
|
||||
return EDGE
|
||||
def corner_fade(distance: int, stops: tuple[str, ...] = ()) -> str:
|
||||
"""Colour ``distance`` cells out from a corner; 0 is the corner glyph.
|
||||
|
||||
|
||||
def side_style(row: int, rows: int, side: str = "left") -> str:
|
||||
"""Colour of a side cell: only two corners fade, so only one end glows."""
|
||||
if side == "right":
|
||||
return fade(row + 1) # fades down from the top-right corner
|
||||
return fade(rows - row) # fades up from the bottom-left corner
|
||||
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.
|
||||
"""
|
||||
if not stops:
|
||||
stops = PALETTES[DEFAULT_PALETTE]
|
||||
distance = max(0, distance)
|
||||
if distance >= FADE_CELLS:
|
||||
return EDGE
|
||||
index = len(stops) - 1 - distance
|
||||
return stops[index] if index >= 0 else EDGE
|
||||
|
||||
|
||||
def inner_size(width: int, height: int) -> tuple[int, int]:
|
||||
@@ -40,12 +47,12 @@ def inner_size(width: int, height: int) -> tuple[int, int]:
|
||||
return max(1, width - 2), max(1, height - 2)
|
||||
|
||||
|
||||
def _edge_cells(width: int, faded_end: str = "right") -> list[tuple[str, str]]:
|
||||
def _edge_cells(width: int, faded_end: str, stops: tuple[str, ...]) -> list[tuple[str, str]]:
|
||||
"""Inner cells of an edge; only one end of it fades into a corner."""
|
||||
inner = max(0, width - 2)
|
||||
if faded_end == "left":
|
||||
return [("─", fade(column + 1)) for column in range(inner)]
|
||||
return [("─", fade(inner - column)) for column in range(inner)]
|
||||
return [("─", corner_fade(column + 1, stops)) for column in range(inner)]
|
||||
return [("─", corner_fade(inner - column, stops)) for column in range(inner)]
|
||||
|
||||
|
||||
def _paint(line: Text, cells: list[tuple[str, str]]) -> None:
|
||||
@@ -62,9 +69,9 @@ def _paint(line: Text, cells: list[tuple[str, str]]) -> None:
|
||||
line.append("".join(run), style=style)
|
||||
|
||||
|
||||
def top(width: int, title: str = "") -> Text:
|
||||
def top(width: int, title: str = "", stops: tuple[str, ...] = ()) -> Text:
|
||||
width = max(2, width)
|
||||
cells = _edge_cells(width, "right") # only the top-right corner fades
|
||||
cells = _edge_cells(width, "right", stops) # only the top-right corner fades
|
||||
if title:
|
||||
label = f" {title} "
|
||||
start = 1
|
||||
@@ -75,29 +82,38 @@ def top(width: int, title: str = "") -> Text:
|
||||
line = Text(no_wrap=True)
|
||||
line.append("╭", style=EDGE)
|
||||
_paint(line, cells)
|
||||
line.append("╮", style=f"bold {fade(0)}")
|
||||
line.append("╮", style=f"bold {corner_fade(0, stops)}")
|
||||
return line
|
||||
|
||||
|
||||
def bottom(width: int) -> Text:
|
||||
def bottom(width: int, stops: tuple[str, ...] = ()) -> Text:
|
||||
width = max(2, width)
|
||||
line = Text(no_wrap=True)
|
||||
line.append("╰", style=f"bold {fade(0)}")
|
||||
_paint(line, _edge_cells(width, "left")) # only the bottom-left fades
|
||||
line.append("╰", style=f"bold {corner_fade(0, stops)}")
|
||||
_paint(line, _edge_cells(width, "left", stops)) # only the bottom-left fades
|
||||
line.append("╯", style=EDGE)
|
||||
return line
|
||||
|
||||
|
||||
def side() -> Text:
|
||||
return Text("│", style=EDGE)
|
||||
def side_style(row: int, rows: int, side: str = "left", stops: tuple[str, ...] = ()) -> str:
|
||||
"""Colour of a side cell: only two corners fade, so only one end glows."""
|
||||
if side == "right":
|
||||
return corner_fade(row + 1, stops) # fades down from the top-right corner
|
||||
return corner_fade(rows - row, stops) # fades up from the bottom-left corner
|
||||
|
||||
|
||||
def side_row(row: int, rows: int, side: str = "left") -> Text:
|
||||
return Text("│", style=side_style(row, rows, side))
|
||||
def side_row(
|
||||
row: int, rows: int, side: str = "left", stops: tuple[str, ...] = ()
|
||||
) -> Text:
|
||||
return Text("│", style=side_style(row, rows, side, stops))
|
||||
|
||||
|
||||
def wrap(
|
||||
lines: list[Text], width: int, height: int, title: str = ""
|
||||
lines: list[Text],
|
||||
width: int,
|
||||
height: int,
|
||||
title: str = "",
|
||||
stops: tuple[str, ...] = (),
|
||||
) -> RenderableType:
|
||||
"""Frame ``lines`` (already sized for the content box) in a pane."""
|
||||
width = max(4, width)
|
||||
@@ -105,24 +121,29 @@ def wrap(
|
||||
inner_width, inner_height = inner_size(width, height)
|
||||
rows = len(lines[:inner_height])
|
||||
|
||||
framed: list[Text] = [top(width, title)]
|
||||
framed: list[Text] = [top(width, title, stops)]
|
||||
for index, line in enumerate(lines[:inner_height]):
|
||||
if cell_len(line.plain) > inner_width:
|
||||
# A pane must never spill over its neighbour: trim, whatever the
|
||||
# caller asked for.
|
||||
line = line.copy()
|
||||
line.truncate(inner_width, overflow="ellipsis")
|
||||
row = Text(no_wrap=True)
|
||||
row.append_text(side_row(index, rows, "left"))
|
||||
row.append_text(side_row(index, rows, "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"))
|
||||
row.append_text(side_row(index, rows, "right", stops))
|
||||
framed.append(row)
|
||||
while len(framed) < height - 1:
|
||||
index = len(framed) - 1
|
||||
row = Text(no_wrap=True)
|
||||
row.append_text(side_row(index, inner_height, "left"))
|
||||
row.append_text(side_row(index, inner_height, "left", stops))
|
||||
row.append(" " * inner_width)
|
||||
row.append_text(side_row(index, inner_height, "right"))
|
||||
row.append_text(side_row(index, inner_height, "right", stops))
|
||||
framed.append(row)
|
||||
framed.append(bottom(width))
|
||||
framed.append(bottom(width, stops))
|
||||
return Group(*framed[:height])
|
||||
|
||||
|
||||
@@ -139,14 +160,14 @@ class FrameTop(Widget):
|
||||
self.title = title
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return top(max(2, self.size.width), self.title)
|
||||
return top(max(2, self.size.width), self.title, active_stops(self.app))
|
||||
|
||||
|
||||
class FrameBottom(Widget):
|
||||
can_focus = False
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
return bottom(max(2, self.size.width))
|
||||
return bottom(max(2, self.size.width), active_stops(self.app))
|
||||
|
||||
|
||||
class FrameSide(Widget):
|
||||
@@ -158,6 +179,7 @@ class FrameSide(Widget):
|
||||
|
||||
def render(self) -> RenderableType:
|
||||
rows = max(1, self.size.height)
|
||||
stops = active_stops(self.app)
|
||||
return Group(
|
||||
*[side_row(row, rows, self.side) for row in range(rows)]
|
||||
*[side_row(row, rows, self.side, stops) for row in range(rows)]
|
||||
)
|
||||
Reference in new issue
Block a user