codehs-utils 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codehs_utils-1.0.0/.gitignore +12 -0
- codehs_utils-1.0.0/LICENSE +21 -0
- codehs_utils-1.0.0/PKG-INFO +510 -0
- codehs_utils-1.0.0/README.md +490 -0
- codehs_utils-1.0.0/pyproject.toml +39 -0
- codehs_utils-1.0.0/src/codehs_utils/__init__.py +78 -0
- codehs_utils-1.0.0/src/codehs_utils/_buffer.py +51 -0
- codehs_utils-1.0.0/src/codehs_utils/_pixelbuf.py +10 -0
- codehs_utils-1.0.0/src/codehs_utils/_platform.py +156 -0
- codehs_utils-1.0.0/src/codehs_utils/colors.py +510 -0
- codehs_utils-1.0.0/src/codehs_utils/drawing.py +337 -0
- codehs_utils-1.0.0/src/codehs_utils/geometry.py +70 -0
- codehs_utils-1.0.0/src/codehs_utils/terminal.py +573 -0
- codehs_utils-1.0.0/src/codehs_utils/text.py +199 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: codehs-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A small terminal toolkit: colors, gradients, banners, keyboard/mouse input, and drawing for CodeHS-style Python environments.
|
|
5
|
+
Project-URL: Homepage, https://github.com/yourusername/codehs-utils
|
|
6
|
+
Project-URL: Issues, https://github.com/yourusername/codehs-utils/issues
|
|
7
|
+
Author-email: Your Name <you@example.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ansi,codehs,colors,console,terminal,tui
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Terminals
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# codehs-utils
|
|
22
|
+
|
|
23
|
+
[](https://badge.fury.io/py/codehs-utils)
|
|
24
|
+
[](https://pypi.org/project/codehs-utils/)
|
|
25
|
+
|
|
26
|
+
A Python library for terminal colors, gradients, banners, drawing, and keyboard/mouse input, originally built for CodeHS's Python environment.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- RGB and named colors for foreground and background, plus mixing/lighten/darken
|
|
31
|
+
- Gradient text with built-in presets (rainbow, fire, ocean, sunset, and more)
|
|
32
|
+
- Composable text banners and boxes, alignable and laid out side by side
|
|
33
|
+
- Rectangles with fill and border drawing
|
|
34
|
+
- A half-block pixel canvas for square-pixel graphics
|
|
35
|
+
- Keyboard and mouse input, including click-and-drag mouse events
|
|
36
|
+
- Cursor and screen control (alternate screen, hide/show cursor, clear, resize-aware sizing)
|
|
37
|
+
- Clickable buttons with hover/press states
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install codehs-utils
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import codehs_utils as c
|
|
49
|
+
|
|
50
|
+
# Colorful banner
|
|
51
|
+
print(c.banner("Hello, World!", color="white", background="blue"))
|
|
52
|
+
|
|
53
|
+
# Interactive app with mouse support
|
|
54
|
+
with c.app(mouse=True):
|
|
55
|
+
c.banner("Click anywhere, press ESC to quit").draw(1, 1)
|
|
56
|
+
for event in c.events(timeout=5):
|
|
57
|
+
if event is None or (event.kind == "key" and event.key == "ESC"):
|
|
58
|
+
break
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
|
|
63
|
+
### Color Functions
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from codehs_utils import ColorLike, ColorText, GradientText
|
|
67
|
+
|
|
68
|
+
# Using named colors, hex, or RGB tuples
|
|
69
|
+
red = ColorLike("red")
|
|
70
|
+
also_red = ColorLike("#ff0000")
|
|
71
|
+
also_red_too = ColorLike((255, 0, 0))
|
|
72
|
+
|
|
73
|
+
print(ColorText("Red text", foreground=red))
|
|
74
|
+
|
|
75
|
+
# Mixing and adjusting colors
|
|
76
|
+
lighter = red.lighten(0.3)
|
|
77
|
+
darker = red.darken(0.3)
|
|
78
|
+
contrast = red.contrast() # black or white, whichever reads better
|
|
79
|
+
|
|
80
|
+
# Background colors
|
|
81
|
+
print(ColorText("Green background", background="green"))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Gradient Text
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from codehs_utils import GradientText
|
|
88
|
+
|
|
89
|
+
# Built-in preset, as a shortcut method...
|
|
90
|
+
print(GradientText.rainbow("This text has rainbow colors!"))
|
|
91
|
+
|
|
92
|
+
# ...or via from_preset(), which every shortcut method calls internally
|
|
93
|
+
print(GradientText.from_preset("Same thing", preset="rainbow"))
|
|
94
|
+
|
|
95
|
+
# Custom stops
|
|
96
|
+
print(GradientText("Custom gradient", colors=["red", "orange", "yellow"]))
|
|
97
|
+
|
|
98
|
+
# Background gradient
|
|
99
|
+
print(GradientText("Rainbow background!", background_colors="rainbow"))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Mixing two colors together (used internally by `.lighten()`/`.darken()`, also usable directly):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from codehs_utils import ColorLike
|
|
106
|
+
|
|
107
|
+
blend = ColorLike("red").mix("blue", t=0.5) # 50/50 blend
|
|
108
|
+
print(blend.to_hex())
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Style Methods
|
|
112
|
+
|
|
113
|
+
`ColorText` and `GradientText` share a set of chainable style methods (from their common `_StyleMixin`), so you can build up a piece of styled text step by step instead of passing everything to the constructor at once:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from codehs_utils import ColorText
|
|
117
|
+
|
|
118
|
+
msg = ColorText("warning!").bold().underline()
|
|
119
|
+
msg.set_background("red")
|
|
120
|
+
print(msg)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Available chainable methods:
|
|
124
|
+
- `.bold()`, `.italic()`, `.underline()`, `.strikethrough()`, `.dim()`, `.blink()`, `.reverse()` — turn on a single style
|
|
125
|
+
- `.add_style(name)` / `.remove_style(name)` — turn a named style on/off
|
|
126
|
+
- `.set_styles(*names)` — replace the whole style list at once
|
|
127
|
+
- `.set_text(text)` — change the wrapped text
|
|
128
|
+
- `.set_background(color)` — set or clear the background color
|
|
129
|
+
- `.set_reset(bool)` — whether an ANSI reset code is appended after the text (default `True`)
|
|
130
|
+
|
|
131
|
+
Every method returns `self`, so calls can be chained.
|
|
132
|
+
|
|
133
|
+
`StyledText` concatenates several `ColorText`/`GradientText`/plain-string pieces into one object, and can be wrapped or aligned as a whole:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from codehs_utils import ColorText, GradientText, StyledText
|
|
137
|
+
|
|
138
|
+
line = StyledText([ColorText("Error: ", "red"), GradientText.fire("something broke")])
|
|
139
|
+
print(line)
|
|
140
|
+
print(line.align(40, "center"))
|
|
141
|
+
print(line.wrap(20))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Cursor & Screen Control
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from codehs_utils import (
|
|
148
|
+
clear_screen, clear_line, set_cursor_pos, set_cursor_row, set_cursor_col,
|
|
149
|
+
move_cursor, save_cursor_position, restore_cursor_position,
|
|
150
|
+
hide_cursor, show_cursor, get_cursor_position, get_terminal_size,
|
|
151
|
+
enter_alt_screen, leave_alt_screen, restore_terminal,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Clear the screen, or just the current line
|
|
155
|
+
clear_screen()
|
|
156
|
+
clear_line(mode="to_end") # "full", "to_end", or "to_start"
|
|
157
|
+
|
|
158
|
+
# Move the cursor
|
|
159
|
+
set_cursor_pos(10, 5) # absolute: row 10, column 5
|
|
160
|
+
set_cursor_row(10)
|
|
161
|
+
set_cursor_col(5)
|
|
162
|
+
move_cursor(dx=2, dy=-1) # relative
|
|
163
|
+
|
|
164
|
+
# Save/restore cursor position
|
|
165
|
+
save_cursor_position()
|
|
166
|
+
restore_cursor_position()
|
|
167
|
+
|
|
168
|
+
# Hide/show the cursor
|
|
169
|
+
hide_cursor()
|
|
170
|
+
show_cursor()
|
|
171
|
+
|
|
172
|
+
# Read back state
|
|
173
|
+
row, col = get_cursor_position()
|
|
174
|
+
width, height = get_terminal_size()
|
|
175
|
+
|
|
176
|
+
# The alternate screen buffer (used internally by app())
|
|
177
|
+
enter_alt_screen()
|
|
178
|
+
leave_alt_screen()
|
|
179
|
+
|
|
180
|
+
# Manually undo everything (hide_cursor, alt screen, mouse tracking, raw
|
|
181
|
+
# mode) in one call — normally you'd just use app() instead
|
|
182
|
+
restore_terminal()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Lower-level output helpers, used internally but available directly:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from codehs_utils import write, frame
|
|
189
|
+
|
|
190
|
+
write("some text") # like print(), but no trailing newline and no `sep`
|
|
191
|
+
|
|
192
|
+
# Batch several writes into one flush, avoiding flicker/tearing
|
|
193
|
+
with frame():
|
|
194
|
+
write("line one\n")
|
|
195
|
+
write("line two\n")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Text Formatting
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from codehs_utils import align_text, wrap_text
|
|
202
|
+
|
|
203
|
+
# Text alignment
|
|
204
|
+
text = "Hello World"
|
|
205
|
+
print(align_text(text, 20, "center")) # Center align in 20 characters
|
|
206
|
+
print(align_text(text, 20, "right")) # Right align
|
|
207
|
+
print(align_text(text, 20, "left")) # Left align
|
|
208
|
+
|
|
209
|
+
# Word wrapping (ANSI-aware, so styled text still wraps correctly)
|
|
210
|
+
print(wrap_text("A long line of text that needs wrapping", width=15))
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Banners & Boxes
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from codehs_utils import banner, Banner, BannerRow
|
|
217
|
+
|
|
218
|
+
# A styled box of text
|
|
219
|
+
b = banner("Important Message", color="black", background="lightyellow")
|
|
220
|
+
print(b)
|
|
221
|
+
|
|
222
|
+
# Draw it at a specific position in an app()
|
|
223
|
+
b.draw(row=1, col=1)
|
|
224
|
+
|
|
225
|
+
# Lay banners out side by side
|
|
226
|
+
row = BannerRow([banner("One"), banner("Two"), banner("Three")])
|
|
227
|
+
print(row)
|
|
228
|
+
|
|
229
|
+
# Adjust spacing and vertical alignment between banners
|
|
230
|
+
row.set_gap(3).set_valign("middle")
|
|
231
|
+
|
|
232
|
+
# Align a whole banner (or row) within a wider field
|
|
233
|
+
print(b.align(width=40, align="right"))
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Rectangles & Pixels
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from codehs_utils import Rect, fill_rect, set_pixel, get_pixel_size
|
|
240
|
+
|
|
241
|
+
# Fill a rectangle
|
|
242
|
+
rect = fill_rect(row=1, col=1, width=10, height=3, color="blue")
|
|
243
|
+
|
|
244
|
+
# Draw a border around a Rect
|
|
245
|
+
rect.draw_border(color="cyan", style="double")
|
|
246
|
+
|
|
247
|
+
# Half-block pixel canvas
|
|
248
|
+
width, height = get_pixel_size()
|
|
249
|
+
set_pixel(x=5, y=5, color="red")
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Keyboard & Mouse Input
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from codehs_utils import get_key, get_keys, get_mouse_event, events, app, KeyEvent, MouseEvent
|
|
256
|
+
|
|
257
|
+
with app(mouse=True):
|
|
258
|
+
for event in events(timeout=None):
|
|
259
|
+
if isinstance(event, KeyEvent):
|
|
260
|
+
print("Key pressed:", event.key)
|
|
261
|
+
elif isinstance(event, MouseEvent):
|
|
262
|
+
print("Mouse:", event.type, event.button, event.x, event.y)
|
|
263
|
+
|
|
264
|
+
# Blocking single-key read (outside an app(), still uses raw/cbreak mode)
|
|
265
|
+
key = get_key(timeout=5)
|
|
266
|
+
|
|
267
|
+
# Drain every key pressed so far without blocking
|
|
268
|
+
keys = get_keys()
|
|
269
|
+
|
|
270
|
+
# Just the next mouse event
|
|
271
|
+
mouse_event = get_mouse_event(timeout=1)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Mouse tracking can also be toggled manually, if you're not using `app()`:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from codehs_utils import enable_mouse_tracking, disable_mouse_tracking
|
|
278
|
+
|
|
279
|
+
enable_mouse_tracking()
|
|
280
|
+
disable_mouse_tracking()
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Advanced Usage
|
|
284
|
+
|
|
285
|
+
### Custom Colors and Gradients
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from codehs_utils import ColorLike, GradientText
|
|
289
|
+
|
|
290
|
+
# Create ColorLike objects directly
|
|
291
|
+
purple = ColorLike((128, 64, 192))
|
|
292
|
+
print(purple.to_hex())
|
|
293
|
+
|
|
294
|
+
# List and preview all built-in gradient presets
|
|
295
|
+
print(GradientText.list_presets())
|
|
296
|
+
GradientText.preview_presets()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Clickable Buttons
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from codehs_utils import Button, app, events
|
|
303
|
+
|
|
304
|
+
with app(mouse=True):
|
|
305
|
+
button = Button("Click me", row=2, col=2, on_click=lambda: print("Clicked!"))
|
|
306
|
+
button.draw()
|
|
307
|
+
for event in events(timeout=10):
|
|
308
|
+
if event is None:
|
|
309
|
+
break
|
|
310
|
+
# handle() updates hover/pressed state, redraws if it changed, fires
|
|
311
|
+
# on_click when appropriate, and returns True exactly on that click
|
|
312
|
+
button.handle(event)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### The `app()` Context Manager
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
from codehs_utils import app
|
|
319
|
+
|
|
320
|
+
with app(mouse=True, cursor=False, clear=True, alt_screen=True):
|
|
321
|
+
... # Terminal state is restored automatically on exit, even on Ctrl+C
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Reference
|
|
325
|
+
|
|
326
|
+
### Text Styles
|
|
327
|
+
Passed as `styles=[...]` to `ColorText`/`GradientText`, or via `.bold()`, `.italic()`, etc:
|
|
328
|
+
- `bold`
|
|
329
|
+
- `dim`
|
|
330
|
+
- `italic`
|
|
331
|
+
- `underline`
|
|
332
|
+
- `blink`
|
|
333
|
+
- `reverse`
|
|
334
|
+
- `strikethrough`
|
|
335
|
+
|
|
336
|
+
### Border Styles
|
|
337
|
+
Passed as `style=` to `Rect.draw_border()`:
|
|
338
|
+
- `single` — `┌─┐ └─┘`
|
|
339
|
+
- `double` — `╔═╗ ╚═╝`
|
|
340
|
+
- `rounded` — `╭─╮ ╰─╯`
|
|
341
|
+
- `heavy` — `┏━┓ ┗━┛`
|
|
342
|
+
- `ascii` — `+-+ +-+`
|
|
343
|
+
|
|
344
|
+
### Gradient Presets
|
|
345
|
+
Built into `GradientText`, usable by name (e.g. `GradientText.rainbow(...)`):
|
|
346
|
+
- `rainbow`, `fire`, `ocean`, `sunset`, `pastel`, `grayscale`, `neon`, `forest`, `mint`, `gold`
|
|
347
|
+
|
|
348
|
+
## API Reference
|
|
349
|
+
|
|
350
|
+
Every public name is re-exported from the top level (`import codehs_utils as c`), so the module path below is just for grouping — you don't need to import from it directly.
|
|
351
|
+
|
|
352
|
+
### Colors — `colors`
|
|
353
|
+
|
|
354
|
+
| Name | Description |
|
|
355
|
+
|---|---|
|
|
356
|
+
| `ColorLike(color)` | Parses a name, `"#hex"`, or `(r, g, b)` tuple into a normalized color |
|
|
357
|
+
| `ColorLike.rgb` | The parsed `(r, g, b)` tuple, or `None` |
|
|
358
|
+
| `ColorLike.to_hex()` | Returns `"#rrggbb"` |
|
|
359
|
+
| `ColorLike.luminance()` | Perceived brightness, 0.0–1.0 |
|
|
360
|
+
| `ColorLike.contrast()` | Black or white, whichever reads better on this color |
|
|
361
|
+
| `ColorLike.mix(other, t=0.5)` | Blends toward another color by ratio `t` |
|
|
362
|
+
| `ColorLike.lighten(amount=0.2)` / `.darken(amount=0.2)` | Mixes toward white / black |
|
|
363
|
+
| `ColorLike.print_samples()` | Prints every named color as a labeled banner |
|
|
364
|
+
| `ColorText(text, foreground=, background=, styles=, reset=)` | A single piece of solid-colored/styled text |
|
|
365
|
+
| `GradientText(text, colors=, color=, background=, background_colors=, styles=, reset=)` | Text with a foreground and/or background gradient |
|
|
366
|
+
| `GradientText.from_preset(text, preset="rainbow", **kwargs)` | Build from a named preset |
|
|
367
|
+
| `GradientText.rainbow(...)`, `.fire(...)`, `.ocean(...)`, `.sunset(...)`, `.pastel(...)`, `.grayscale(...)`, `.neon(...)`, `.forest(...)`, `.mint(...)`, `.gold(...)` | Shortcut constructors, one per built-in preset |
|
|
368
|
+
| `GradientText.list_presets()` | List of built-in preset names |
|
|
369
|
+
| `GradientText.preview_presets(sample_text=, print_output=True)` | Prints (and returns) a sample of every preset |
|
|
370
|
+
| `StyledText(parts=None)` | Concatenates strings/`ColorText`/`GradientText` into one object |
|
|
371
|
+
| `ColorSpec`, `GradientColors` | Type aliases used in the signatures above (not runtime objects) |
|
|
372
|
+
|
|
373
|
+
**Shared style methods** (on `ColorText` and `GradientText`, via `_StyleMixin`):
|
|
374
|
+
|
|
375
|
+
| Name | Description |
|
|
376
|
+
|---|---|
|
|
377
|
+
| `.bold()` `.dim()` `.italic()` `.underline()` `.blink()` `.reverse()` `.strikethrough()` | Turn on one style |
|
|
378
|
+
| `.add_style(name)` / `.remove_style(name)` | Turn a named style on/off |
|
|
379
|
+
| `.set_styles(*names)` | Replace the whole style list |
|
|
380
|
+
| `.set_text(text)` | Change the wrapped text |
|
|
381
|
+
| `.set_background(color)` | Set or clear the background color |
|
|
382
|
+
| `.set_reset(bool)` | Whether a trailing ANSI reset code is appended |
|
|
383
|
+
| `.set_color(color)` (`ColorText` only) | Set or clear the foreground color |
|
|
384
|
+
| `.set_colors(colors)` / `.set_background_gradient(colors)` (`GradientText` only) | Set the foreground / background gradient stops |
|
|
385
|
+
|
|
386
|
+
**`StyledText` methods:**
|
|
387
|
+
|
|
388
|
+
| Name | Description |
|
|
389
|
+
|---|---|
|
|
390
|
+
| `.wrap(width, collapse_space=True)` | ANSI-aware word wrap of the combined text |
|
|
391
|
+
| `.align(width, align="left", fillchar=" ")` | Align the combined text within `width` |
|
|
392
|
+
|
|
393
|
+
### Text — `text`
|
|
394
|
+
|
|
395
|
+
| Name | Description |
|
|
396
|
+
|---|---|
|
|
397
|
+
| `wrap_text(text, width, collapse_space=True, break_long_words=True, preserve_newlines=True)` | ANSI-aware word wrapping |
|
|
398
|
+
| `align_text(text, width, align="left", fillchar=" ")` | `"left"`, `"right"`, `"center"`, or `"justify"` alignment |
|
|
399
|
+
|
|
400
|
+
### Geometry — `geometry`
|
|
401
|
+
|
|
402
|
+
| Name | Description |
|
|
403
|
+
|---|---|
|
|
404
|
+
| `Rect(row, col, width, height)` | A rectangle; also returned by most drawing functions |
|
|
405
|
+
| `Rect.top` `.left` `.bottom` `.right` | Edge coordinates |
|
|
406
|
+
| `Rect.contains(x, y)` | Whether a point falls inside the rect |
|
|
407
|
+
| `Rect.fill(color=None, char=" ")` | Fill the rect (wraps `fill_rect`) |
|
|
408
|
+
| `Rect.draw_border(color=None, style="single", background=None)` | Draw a border around the rect |
|
|
409
|
+
|
|
410
|
+
### Drawing — `drawing`
|
|
411
|
+
|
|
412
|
+
| Name | Description |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `Banner(lines)` | A block of text padded to a uniform width |
|
|
415
|
+
| `Banner.height` | Number of lines |
|
|
416
|
+
| `Banner.draw(row, col)` | Draw at a position, returns the `Rect` drawn |
|
|
417
|
+
| `Banner.align(width, align="left", fillchar=" ")` | Align the banner within a wider field |
|
|
418
|
+
| `BannerRow(banners, gap=1, valign="top")` | Lay several banners out side by side |
|
|
419
|
+
| `BannerRow.width` `.height` | Combined dimensions |
|
|
420
|
+
| `BannerRow.set_gap(gap)` / `.set_valign(valign)` | Adjust spacing / vertical alignment |
|
|
421
|
+
| `BannerRow.draw(row, col)` / `.align(width, align=, fillchar=)` | Same as on `Banner` |
|
|
422
|
+
| `banner(text, width=None, color=, colors=, background=, background_colors=, styles=, padding=1, align="center")` | Build a styled `Banner` in one call |
|
|
423
|
+
| `fill_rect(row, col, width, height, color=None, char=" ", foreground=None)` | Fill a rectangle directly, returns a `Rect` |
|
|
424
|
+
| `get_pixel_size()` | Terminal size in half-block pixels (`(width, height * 2)`) |
|
|
425
|
+
| `set_pixel(x, y, color)` / `clear_pixel(x, y)` / `clear_pixels()` | Half-block pixel canvas |
|
|
426
|
+
| `Button(text, row, col, width=, background=, color=, hover_background=, hover_color=, pressed_background=, pressed_color=, padding=1, styles=, trigger="release", on_click=)` | A clickable button |
|
|
427
|
+
| `Button.draw()` | (Re)draw the button in its current state |
|
|
428
|
+
| `Button.handle(event)` | Update state from a `MouseEvent`, redraw if needed, fire `on_click`; returns `True` on a completed click |
|
|
429
|
+
|
|
430
|
+
### Terminal — `terminal`
|
|
431
|
+
|
|
432
|
+
| Name | Description |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `app(mouse=False, cursor=False, clear=True, alt_screen=True, catch_interrupt=True)` | Context manager: sets up and tears down an interactive session |
|
|
435
|
+
| `MouseEvent` | `.type`, `.button`, `.x`, `.y`, `.shift`, `.alt`, `.ctrl`; `.inside(rect)` |
|
|
436
|
+
| `KeyEvent` | `.key`; compares equal to a plain string |
|
|
437
|
+
| `get_key(timeout=None)` | Block for a single keypress |
|
|
438
|
+
| `get_keys()` / `get_key_nonblocking()` | Drain all buffered keys without blocking |
|
|
439
|
+
| `get_mouse_event(timeout=None)` / `get_mouse_event_nonblocking()` | Get the next mouse event |
|
|
440
|
+
| `get_event(timeout=None)` | Get the next key **or** mouse event |
|
|
441
|
+
| `events(timeout=None)` | Infinite iterator over `get_event()` |
|
|
442
|
+
| `get_cursor_position()` / `gcp` | Query the cursor's current position (`gcp` is an alias) |
|
|
443
|
+
| `get_terminal_size()` | `(width, height)` in characters |
|
|
444
|
+
| `clear_screen(scrollback=True)` / `clear_line(mode="full")` | Clear the screen / current line |
|
|
445
|
+
| `set_cursor_pos(row, col)` / `set_cursor_row(row)` / `set_cursor_col(col)` | Absolute cursor positioning |
|
|
446
|
+
| `move_cursor(dx=0, dy=0)` | Relative cursor movement |
|
|
447
|
+
| `save_cursor_position()` / `restore_cursor_position()` | Push/pop cursor position |
|
|
448
|
+
| `hide_cursor()` / `show_cursor()` | Toggle cursor visibility |
|
|
449
|
+
| `enter_alt_screen()` / `leave_alt_screen()` | Toggle the alternate screen buffer |
|
|
450
|
+
| `enable_mouse_tracking()` / `disable_mouse_tracking()` | Toggle mouse event reporting |
|
|
451
|
+
| `print_at(row, col, text, clear_to_end=False)` | Print (possibly multi-line) text at a position, returns a `Rect` |
|
|
452
|
+
| `restore_terminal()` | Undo cursor/mouse/alt-screen/raw-mode state in one call |
|
|
453
|
+
|
|
454
|
+
### Output buffering — `_buffer` (re-exported)
|
|
455
|
+
|
|
456
|
+
| Name | Description |
|
|
457
|
+
|---|---|
|
|
458
|
+
| `write(*parts, sep="", flush=True)` | Like `print()`, but no newline and writes go through the frame buffer |
|
|
459
|
+
| `frame(sync=False)` | Context manager: batch writes into a single flush |
|
|
460
|
+
|
|
461
|
+
## Requirements
|
|
462
|
+
|
|
463
|
+
- Python 3.8+
|
|
464
|
+
- No third-party dependencies
|
|
465
|
+
|
|
466
|
+
## Examples
|
|
467
|
+
|
|
468
|
+
### Create a Colorful Banner
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
from codehs_utils import banner, clear_screen, GradientText
|
|
472
|
+
|
|
473
|
+
clear_screen()
|
|
474
|
+
print(banner("WELCOME", color="black", background="lightgreen"))
|
|
475
|
+
print()
|
|
476
|
+
print(GradientText.rainbow("- Terminal Toolkit Demo -"))
|
|
477
|
+
print(GradientText.rainbow("=" * 50))
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Simple Paint Program
|
|
481
|
+
|
|
482
|
+
```python
|
|
483
|
+
from codehs_utils import app, events, set_pixel
|
|
484
|
+
|
|
485
|
+
with app(mouse=True):
|
|
486
|
+
for event in events(timeout=None):
|
|
487
|
+
if event is None or (event.kind == "key" and event.key == "ESC"):
|
|
488
|
+
break
|
|
489
|
+
if event.kind == "mouse" and event.button == "left":
|
|
490
|
+
set_pixel(event.x, event.y, "red")
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Bordered Dialog Box
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from codehs_utils import Rect, print_at
|
|
497
|
+
|
|
498
|
+
box = Rect(row=3, col=5, width=30, height=6)
|
|
499
|
+
box.fill(color="black")
|
|
500
|
+
box.draw_border(color="white", style="rounded")
|
|
501
|
+
print_at(box.row + 2, box.col + 2, "Press any key to continue...")
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## License
|
|
505
|
+
|
|
506
|
+
MIT License - see LICENSE file for details.
|
|
507
|
+
|
|
508
|
+
## Contributing
|
|
509
|
+
|
|
510
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|