Compose a frame

A how-to guide: place runs, panels and lettering at positions with a Composition, which checks the whole layout fits before it draws a cell.

from sextile import Custom, OnOneFrame, Page, PageLayout, PageRequest, PageRouter, Sextile
from sextile.viewdata.canvas import Canvas
from sextile.viewdata.composition import Align, Composition, Style
from sextile.viewdata.controls import Colour

router = PageRouter()

_BOARD = [
    ("Aberdeen", "4", "10:15"), ("Bristol", "9", "10:22"), ("Carlisle", "1", "10:40"),
    ("Dover", "6", "10:51"), ("Edinburgh", "3", "11:05"), ("Falmouth", "7", "11:18"),
    ("Glasgow", "2", "11:30"),
]


@router.page("1", name="board", title="Departures")
async def board(request: PageRequest) -> Page:
    def draw(canvas: Canvas, row: int) -> None:
        layout = Composition()
        head = layout.panel(row, Align.START, width=39, colour=Colour.BLUE, rows=1)
        layout.text(row, Align.CENTRE, "KINGS CROSS", style=Style(colour=Colour.YELLOW), within=head)
        for column, heading in ((1, "TO"), (Align.CENTRE, "PLAT"), (Align.END, "TIME")):
            layout.text(row + 2, column, heading, style=Style(colour=Colour.CYAN))
        for offset, (place, plat, time) in enumerate(_BOARD):
            line = row + 4 + offset * 2
            layout.text(line, 1, place, style=Style(colour=Colour.WHITE))
            layout.text(line, Align.CENTRE, plat, style=Style(colour=Colour.WHITE))
            layout.text(line, Align.END, time, style=Style(colour=Colour.GREEN))
        foot = layout.panel(row + 18, Align.START, width=39, colour=Colour.MAGENTA, rows=1)
        layout.text(row + 18, Align.CENTRE, "Times are provisional", style=Style(colour=Colour.WHITE), within=foot)
        layout.draw(canvas)

    return PageLayout(parts=[OnOneFrame(Custom(rows=19, draw=draw))]).build(request)


app = Sextile(name="Station", pages=[*router])
 DEPARTURES                           1a
  
               KINGS CROSS              
                                        
 TO               PLAT              TIME
                                        
 Aberdeen          4               10:15
                                        
 Bristol           9               10:22
                                        
 Carlisle          1               10:40
                                        
 Dover             6               10:51
                                        
 Edinburgh         3               11:05
                                        
 Falmouth          7               11:18
                                        
 Glasgow           2               11:30
                                        
          Times are provisional         
                                        
  
 0 index                                

Composition.text(row, column, text, style=) places a run at a position, where a Canvas row writes only left to right; a column may be a number or an Align (START, CENTRE, END). Each row’s destination, platform and time are placed at START, CENTRE and END, so the three columns line up however long the names are. panel declares a coloured box once — the blue masthead and the magenta footer — and a run placed within it is aligned in the box rather than on the frame.

The layout is checked, not searched

Composition.draw reports whether the layout is possible — naming the row, the column and the arithmetic in a DoesNotFit — before a cell is written, and draws nothing if any row fails, so a bad layout never leaves half a frame on a screen. A coloured run reserves the cell before it for its colour attribute, which is why the destinations begin at column 1 and not column 0; two runs in one style cost one attribute, not two. Why placement is a single left-to-right pass rather than a search is in the graphics explanation.

Which to reach for

Use

For

Canvas.row(n), a RowWriter

text and mosaics that run left to right along one row

Composition

runs placed at positions, panels, alignment, and a checked fit

Draw a row at a time with Draw a custom frame; set lettering on a panel with Letter on a background. The compositor’s own API is sextile.viewdata.composition.