Skip to content

Add documentation #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
02cbcf1
:books: Fix a docstring
davep Apr 13, 2025
19c945d
:factory: Add docs to the cleaning targets
davep Apr 13, 2025
e9d78ca
:books: Make a start on adding proper documentation
davep Apr 13, 2025
9ec5a55
:books: Add installation instructions
davep Apr 13, 2025
263eed9
:books: Add the library content documentation
davep Apr 13, 2025
8cb46d6
:books: Exclude render_line from the docs
davep Apr 13, 2025
66802b2
:books: Add information on creating the Canvas
davep Apr 13, 2025
6ec21ac
Merge branch 'main' into docs
davep Apr 13, 2025
9346ab7
:books: Tidy up some links
davep Apr 14, 2025
c9bbc30
:books: Watch example code too
davep Apr 14, 2025
ee432d6
:books: Add titles to the example canvas widgets
davep Apr 14, 2025
ec56880
:books: Allow code annotations in docs
davep Apr 14, 2025
9ef2ec9
:books: Add information about colours
davep Apr 14, 2025
6c501d4
:books: Explain set_pixel
davep Apr 14, 2025
3feeee9
:books: Document using set_pixels
davep Apr 14, 2025
259e258
:books: Explain how to draw a line
davep Apr 14, 2025
08c091a
:books: Mention that all coordinates are relative to top left
davep Apr 14, 2025
5b0baef
:books: Explain how to draw a rectangle
davep Apr 14, 2025
7dceedd
:twisted_rightwards_arrows: Merge branch 'main' into docs
davep Apr 14, 2025
9bd00b8
:books: Tweak the rectangle example
davep Apr 14, 2025
66d941d
:books: Explain how to draw a circle
davep Apr 14, 2025
405076c
:books: Include the licence and the changelog
davep Apr 14, 2025
87602ac
:books: Add the CNAME for the docs
davep Apr 14, 2025
75feadf
:books: Better linkage
davep Apr 14, 2025
6253dd2
:books: Documentation tidying
davep Apr 14, 2025
e139980
:factory: Include all documentation examples in the code quality checks
davep Apr 14, 2025
721fcef
:books: Swap out the prism for a mandelbrot set
davep Apr 14, 2025
afbb25a
:books: Simplify the README, deferring to the main docs
davep Apr 14, 2025
da03e9b
:card_index: Point the package metadata at the website
davep Apr 14, 2025
c1132e9
:books: Point people to where they can find extra help
davep Apr 14, 2025
3729a12
:twisted_rightwards_arrows: Merge branch 'main' into docs
davep Apr 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
build/
dist/
__pycache__
site/
.screenshot_cache/
46 changes: 27 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
lib := textual_canvas
src := src/
run := rye run
test := rye test
python := $(run) python
lint := rye lint -- --select I
fmt := rye fmt
mypy := $(run) mypy
mkdocs := $(run) mkdocs
spell := $(run) codespell
lib := textual_canvas
src := src/
examples := docs/examples
run := rye run
test := rye test
python := $(run) python
lint := rye lint -- --select I
fmt := rye fmt
mypy := $(run) mypy
mkdocs := $(run) mkdocs
spell := $(run) codespell

##############################################################################
# Local "interactive testing" of the code.
Expand Down Expand Up @@ -42,23 +43,23 @@ resetup: realclean # Recreate the virtual environment from scratch
# Checking/testing/linting/etc.
.PHONY: lint
lint: # Check the code for linting issues
$(lint) $(src)
$(lint) $(src) $(examples)

.PHONY: codestyle
codestyle: # Is the code formatted correctly?
$(fmt) --check $(src)
$(fmt) --check $(src) $(examples)

.PHONY: typecheck
typecheck: # Perform static type checks with mypy
$(mypy) --scripts-are-modules $(src)
$(mypy) --scripts-are-modules $(src) $(examples)

.PHONY: stricttypecheck
stricttypecheck: # Perform a strict static type checks with mypy
$(mypy) --scripts-are-modules --strict $(src)
$(mypy) --scripts-are-modules --strict $(src) $(examples)

.PHONY: spellcheck
spellcheck: # Spell check the code
$(spell) *.md $(src) $(docs) $(tests)
$(spell) *.md $(src) $(docs)

.PHONY: checkall
checkall: spellcheck codestyle lint stricttypecheck # Check all the things
Expand Down Expand Up @@ -103,19 +104,26 @@ repl: # Start a Python REPL in the venv.

.PHONY: delint
delint: # Fix linting issues.
$(lint) --fix $(src)
$(lint) --fix $(src) $(examples)

.PHONY: pep8ify
pep8ify: # Reformat the code to be as PEP8 as possible.
$(fmt) $(src)
$(fmt) $(src) $(examples)

.PHONY: tidy
tidy: delint pep8ify # Tidy up the code, fixing lint and format issues.

.PHONY: clean
clean: # Clean the build directories
.PHONY: clean-packaging
clean-packaging: # Clean the package building files
rm -rf dist

.PHONY: clean-docs
clean-docs: # Clean up the documentation building files
rm -rf site .screenshot_cache

.PHONY: clean
clean: clean-packaging clean-docs # Clean the build directories

.PHONY: realclean
realclean: clean # Clean the venv and build directories
rm -rf .venv
Expand Down
108 changes: 7 additions & 101 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@

## Introduction

This library aims to provide a very simple terminal-based drawing canvas
widget for use with [Textual](https://textual.textualize.io/). Initially
developed for a (also-being-worked-on-right-now) less general project, I'm
making it available on the off-chance anyone else might want to play with it
now.

You might not want to rely on this just yet though; I'm still messing and
experimenting.
`textual-canvas` provides a simple terminal-based drawing canvas widget for
use with [Textual](https://textual.textualize.io/). Initially developed as a
widget for building
[`textual-mandelbrot`](https://github.com/davep/textual-mandelbrot), it made
sense to spin it out into its own general-purpose library.

## Installing

Expand All @@ -30,98 +27,7 @@ This is a scrollable and focusable widget that can be used to colour
"pixels" themselves are half a character cell in height, hopefully coming
out roughly square in most environments.

The `Canvas` can be imported like this:

```python
from textual_canvas import Canvas
```

And is created by providing a width and height (in its own idea of "pixels")
plus an optional initial background colour (which should be a Textual
[`Color`](https://textual.textualize.io/api/color/#textual.color.Color)).

In a Textual `compose` method you might use it something like this:

```python
yield Canvas(120, 120, Color(30, 40, 50))
```

Currently there are the following methods available for drawing:

- `clear(self, color: Color | None = None) -> Self`
- `set_pixel(self, x: int, y: int, color: Color) -> Self`
- `set_pixels(self, locations: Iterable[ tuple[ int, int ] ], color: Color) -> Self`
- `draw_line(self, x0: int, y0: int, x1: int, y1: int, color: Color) -> Self`
- `draw_rectangle(self, x: int, y: int, width: int, height: int, color: Color) -> Self`
- `draw_circle(self, center_x: int, center_y: int, radius: int, color: Color) -> Self`

I'll document all of this better, when I spend more time on it than the 1/2
hour somewhere between dinner and bedtime.

A quick and dirty example of this being used would be:

```python
from textual.app import App, ComposeResult
from textual.color import Color
from textual_canvas import Canvas


##############################################################################
class CanvasTestApp(App[None]):
"""The Canvas testing application."""

CSS = """
Canvas {
border: round green;
width: 1fr;
height: 1fr;
}
"""

def compose(self) -> ComposeResult:
yield Canvas(120, 120, Color(30, 40, 50))

def on_mount(self) -> None:
"""Set up the display once the DOM is available."""
canvas = self.query_one(Canvas)

canvas.draw_line(60, 40, 90, 80, Color(128, 128, 128))
canvas.draw_line(60, 40, 30, 80, Color(128, 128, 128))
canvas.draw_line(30, 80, 90, 80, Color(128, 128, 128))

canvas.draw_line(0, 70, 48, 55, Color(255, 255, 255))

for n in range(52, 59):
canvas.draw_line(48, 55, 58, n, Color(128, 128, 128))

canvas.draw_line(70, 52, 119, 57, Color(255, 0, 0))
canvas.draw_line(71, 53, 119, 58, Color(255, 165, 0))
canvas.draw_line(72, 54, 119, 59, Color(255, 255, 0))
canvas.draw_line(72, 55, 119, 60, Color(0, 255, 0))
canvas.draw_line(73, 56, 119, 61, Color(0, 0, 255))
canvas.draw_line(74, 57, 119, 62, Color(75, 0, 130))
canvas.draw_line(75, 58, 119, 63, Color(143, 0, 255))


if __name__ == "__main__":
CanvasTestApp().run()
```

To see this code in action you, in an environment where the library is
installed, run:

```sh
$ python -m textual_canvas
```

You should see something like this:

![Demo code](https://raw.githubusercontent.com/davep/textual-canvas/main/img/textual-canvas.png)

## TODO

Lots. Lots and lots. As mentioned above, there's little tinker project that
I'm building on top of this, so I'll be using that to see how this gets
fleshed out.
See [the documentation](https://textual-canvas.davep.dev/) for an
introduction, a usage guide and detailed API documentation.

[//]: # (README.md ends here)
19 changes: 19 additions & 0 deletions docs/examples/default_background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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()
23 changes: 23 additions & 0 deletions docs/examples/draw_circle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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()
23 changes: 23 additions & 0 deletions docs/examples/draw_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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()
23 changes: 23 additions & 0 deletions docs/examples/draw_rectangle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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()
67 changes: 67 additions & 0 deletions docs/examples/mandelbrot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import Iterator

from textual.app import App, ComposeResult
from textual.color import Color

from textual_canvas.canvas import Canvas

BLUE_BROWN = [
Color(66, 30, 15),
Color(25, 7, 26),
Color(9, 1, 47),
Color(4, 4, 73),
Color(0, 7, 100),
Color(12, 44, 138),
Color(24, 82, 177),
Color(57, 125, 209),
Color(134, 181, 229),
Color(211, 236, 248),
Color(241, 233, 191),
Color(248, 201, 95),
Color(255, 170, 0),
Color(204, 128, 0),
Color(153, 87, 0),
Color(106, 52, 3),
]
"""https://stackoverflow.com/a/16505538/2123348"""


def mandelbrot(x: float, y: float) -> int:
c1 = complex(x, y)
c2 = 0j
for n in range(40):
if abs(c2) > 2:
return n
c2 = c1 + (c2**2.0)
return 0


def frange(r_from: float, r_to: float, size: int) -> Iterator[float]:
steps = 0
step = (r_to - r_from) / size
n = r_from
while n < r_to and steps < size:
yield n
n += step
steps += 1


class MandelbrotApp(App[None]):
def compose(self) -> ComposeResult:
yield Canvas(120, 90)

def on_mount(self) -> None:
canvas = self.query_one(Canvas)
for x_pixel, x_point in enumerate(frange(-2.5, 1.5, canvas.width)):
for y_pixel, y_point in enumerate(frange(-1.5, 1.5, canvas.height)):
canvas.set_pixel(
x_pixel,
y_pixel,
BLUE_BROWN[value % 16]
if (value := mandelbrot(x_point, y_point))
else Color(0, 0, 0),
)


if __name__ == "__main__":
MandelbrotApp().run()
20 changes: 20 additions & 0 deletions docs/examples/own_background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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()
Loading
Loading