Usage Guide
The Canvas
widget
The Canvas
widget is used like any other
Textual widget; it is imported as:
from textual_canvas import Canvas
and then can be mounted or composed like any other widget.
Sizing
When creating it you provide a width and a height of the canvas in "pixels" Note that these values are the dimensions of the canvas that the "pixels" are drawn on, not the size of the widget; the widget itself is sized using all the normal Textual styling and geometry rules.
To illustrate, here are two Canvas
widgets, one where the widget is bigger
than the canvas, and one where the canvas is bigger than the widget:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class CanvasSizingApp(App[None]):
CSS = """
Screen {
layout: horizontal;
}
Canvas {
background: $panel;
border: solid cornflowerblue;
width: 1fr;
height: 1fr;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(20, 20, Color(128, 0, 128), id="smaller")
yield Canvas(60, 60, Color(128, 0, 128), id="bigger")
def on_mount(self) -> None:
self.query_one("#smaller").border_title = "Widget > Canvas"
self.query_one("#bigger").border_title = "Canvas > Widget"
if __name__ == "__main__":
CanvasSizingApp().run()
Note how the Canvas
widget on the left is bigger than the canvas it is
displaying; whereas the widget on the right is smaller than its canvas so it
has scrollbars.
Colours
There are three main colours to consider when working with Canvas
:
- The widget background colour.
- The canvas background colour.
- The current "pen" colour.
Widget vs canvas background
The difference ion the first two items listed above might not seem obvious
to start with. The Canvas
widget, like all other Textual widgets, has a
background; you can
style this with CSS just as you always would. But the canvas itself -- the
area that you'll be drawing in inside the widget -- can have its own
background colour.
By default the canvas background colour will be the widget's background
colour; but you can pass canvas_color
as a
parameter to change this.
To illustrate, here is a Canvas
widget where no background colour is
specified, so the canvas background and the widget background are the same:
from textual.app import App, ComposeResult
from textual_canvas import Canvas
class DefaultBackgroundApp(App[None]):
CSS = """
Canvas {
border: solid black;
background: cornflowerblue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30) # (1)!
if __name__ == "__main__":
DefaultBackgroundApp().run()
- The
Canvas
is created without a given colour, so the widget'sbackground
will be used as the canvas background colour.
Note how the user won't be able to see what's canvas background and what's widget outside of the background.
On the other hand, if we take the same code and give the Canvas
its own
background colour when we create it:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class OwnBackgroundApp(App[None]):
CSS = """
Canvas {
border: solid black;
background: cornflowerblue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color(80, 80, 255)) # (1)!
if __name__ == "__main__":
OwnBackgroundApp().run()
- Note how
Canvas
is given its own background colour.
The pen colour
The Canvas
widget has a "pen" colour; any time a drawing operation is
performed, if no colour is given to the method, the "pen" colour is used. By
default that colour is taken from the
color
styling of the
widget.
Drawing on the canvas
The canvas widget provides a number of methods for drawing on it.
Note
All coordinates used when drawing are relative to the top left corner of the canvas.
Drawing a single pixel
Use set_pixel
to set the colour
of a single pixel on the canvas. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class SetPixelApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: red;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).set_pixel(10, 10)
if __name__ == "__main__":
SetPixelApp().run()
That example is using the default pen colour, which in turn is defaulting
the widget's color
. Instead
we can set pixels to specific colours:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class SetPixelApp(App[None]):
CSS = """
Canvas {
background: $panel;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
for offset, colour in enumerate(("red", "green", "blue")):
self.query_one(Canvas).set_pixel(
10 + offset,
10 + offset,
Color.parse(colour),
)
if __name__ == "__main__":
SetPixelApp().run()
Drawing multiple pixels
Use set_pixels
to draw multiple
pixels of the same colour at once. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class SetPixelsApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: red;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).set_pixels(
(
(10, 10),
(15, 15),
(10, 15),
(15, 10),
)
)
if __name__ == "__main__":
SetPixelsApp().run()
Drawing a line
Use draw_line
to draw a line on
the canvas. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class DrawLineApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: blue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).draw_line(2, 2, 27, 27)
if __name__ == "__main__":
DrawLineApp().run()
Drawing a rectangle
Use draw_rectangle
to draw
a rectangle on the canvas. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class DrawRectangleApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: blue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).draw_rectangle(2, 2, 26, 26)
if __name__ == "__main__":
DrawRectangleApp().run()
Drawing a circle
Use draw_circle
to draw a
circle on the canvas. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class DrawCircleApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: blue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).draw_circle(14, 14, 10)
if __name__ == "__main__":
DrawCircleApp().run()
Clearing a single pixel
Use clear_pixel
to set a pixel's
colour to the canvas' colour. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class ClearPixelApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: blue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).draw_line(10, 10, 15, 10).clear_pixel(12, 10)
if __name__ == "__main__":
ClearPixelApp().run()
Clearing multiple pixels
Use clear_pixels
to set the colour
of multiple pixels to the canvas' colour. For example:
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas
class ClearPixelsApp(App[None]):
CSS = """
Canvas {
background: $panel;
color: blue;
}
"""
def compose(self) -> ComposeResult:
yield Canvas(30, 30, Color.parse("cornflowerblue"))
def on_mount(self) -> None:
self.query_one(Canvas).draw_line(10, 10, 16, 10).clear_pixels(
(
(11, 10),
(13, 10),
(15, 10),
)
)
if __name__ == "__main__":
ClearPixelsApp().run()
Further help
You can find more detailed documentation of the API in the next section. If you still have questions or have ideas for improvements please feel free to chat to me in GitHub discussions; if you think you've found a problem, please feel free to raise an issue.