cmdgui 0.1.0__tar.gz → 0.2.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.
- cmdgui-0.2.0/LICENSE +21 -0
- cmdgui-0.2.0/PKG-INFO +97 -0
- cmdgui-0.2.0/README.md +80 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/pyproject.toml +2 -2
- cmdgui-0.2.0/src/cmdgui/__init__.py +14 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui/shorts.py +83 -7
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui/view.py +265 -85
- cmdgui-0.2.0/src/cmdgui/widgets/__init__.py +10 -0
- cmdgui-0.2.0/src/cmdgui/widgets/base.py +327 -0
- cmdgui-0.2.0/src/cmdgui/widgets/controls.py +287 -0
- cmdgui-0.2.0/src/cmdgui/widgets/lists.py +242 -0
- cmdgui-0.2.0/src/cmdgui/widgets/stdout.py +87 -0
- cmdgui-0.2.0/src/cmdgui/widgets/tabs.py +234 -0
- cmdgui-0.2.0/src/cmdgui/widgets/text.py +259 -0
- cmdgui-0.2.0/src/cmdgui.egg-info/PKG-INFO +97 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui.egg-info/SOURCES.txt +9 -2
- cmdgui-0.1.0/PKG-INFO +0 -263
- cmdgui-0.1.0/README.md +0 -248
- cmdgui-0.1.0/src/cmdgui/__init__.py +0 -14
- cmdgui-0.1.0/src/cmdgui/widgets.py +0 -577
- cmdgui-0.1.0/src/cmdgui.egg-info/PKG-INFO +0 -263
- {cmdgui-0.1.0 → cmdgui-0.2.0}/setup.cfg +0 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui/_platform.py +0 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui/inputs.py +0 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui/layout.py +0 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui.egg-info/dependency_links.txt +0 -0
- {cmdgui-0.1.0 → cmdgui-0.2.0}/src/cmdgui.egg-info/top_level.txt +0 -0
cmdgui-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 olliez-mods
|
|
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.
|
cmdgui-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmdgui
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Simple terminal GUIs: draw your layout as text, the view runs itself
|
|
5
|
+
Author: olliez-mods
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: homepage, https://github.com/olliez-mods/cmdgui
|
|
8
|
+
Keywords: terminal,tui,gui,cli,widgets
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Operating System :: MacOS
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# cmdgui
|
|
19
|
+
|
|
20
|
+
Simple terminal GUIs in Python. Draw your layout as text, fill in the widgets,
|
|
21
|
+
and keep writing your program — the view runs on its own thread, so there's no
|
|
22
|
+
event loop to hand control to and no draw calls to make.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌─ name ───────────────────┐
|
|
26
|
+
│quinn │[ Greet ] ON fast
|
|
27
|
+
├─ fruit ──────────────────┼─ scores ──────────────────────────────────┐
|
|
28
|
+
│ apple │name score city │
|
|
29
|
+
│ banana │Ada 98 London │
|
|
30
|
+
│ cherry │Linus 87 Helsinki │
|
|
31
|
+
│ ├─ log ─────────────────────────────────────┤
|
|
32
|
+
│ │Hello, quinn! 👋 │
|
|
33
|
+
│ │picked cherry │
|
|
34
|
+
└──────────────────────────┴───────────────────────────────────────────┘
|
|
35
|
+
████████████████████████████████░░░░░░░░░░░░░░░░ 67%[ ] done
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
No dependencies. macOS and Linux; Windows support is written but untested.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install cmdgui
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick start
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from cmdgui import View, Button, Stdout
|
|
50
|
+
import time
|
|
51
|
+
|
|
52
|
+
class App(View):
|
|
53
|
+
layout = """
|
|
54
|
+
start pause .
|
|
55
|
+
log - -
|
|
56
|
+
"""
|
|
57
|
+
start = Button("Start", on_click=lambda: print("started"))
|
|
58
|
+
pause = Button("Pause", on_click=lambda: print("paused"))
|
|
59
|
+
log = Stdout()
|
|
60
|
+
|
|
61
|
+
view = App()
|
|
62
|
+
|
|
63
|
+
for i in range(100):
|
|
64
|
+
print(f"tick {i}") # shows up in the log box
|
|
65
|
+
time.sleep(0.5)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Press `q` to quit (or Ctrl+C). Because the widgets are class attributes, your
|
|
69
|
+
editor knows `view.start` is a `Button` — autocomplete and type checking work.
|
|
70
|
+
|
|
71
|
+
## What's in it
|
|
72
|
+
|
|
73
|
+
- **Layouts drawn as text**: each word is a grid cell, `-` and `|` stretch a widget
|
|
74
|
+
across cells, and borders between neighbours join up.
|
|
75
|
+
- **Widgets**: text, labels, buttons, one-line and multi-line text boxes (with password
|
|
76
|
+
mode and history), checkboxes, toggles, radio buttons, dropdowns, sliders, progress
|
|
77
|
+
bars, menus, folding trees, tables, tabs, and a box that shows everything you print.
|
|
78
|
+
- **Popups**: dialogs, dropdowns and command menus that float over the layout.
|
|
79
|
+
- **Mouse and keyboard**: clicks, dragging, the scroll wheel, Tab between widgets, and
|
|
80
|
+
your own key bindings.
|
|
81
|
+
- **Timers**: `view.every(1, tick)` and `view.after(5, fn)`, no threads needed.
|
|
82
|
+
- **Themes**: restyle any part, with 73 named colors, the 256-color palette, or exact
|
|
83
|
+
hex colors.
|
|
84
|
+
- **Your own widgets**: subclass `Widget`, draw into a canvas, and it works in layouts.
|
|
85
|
+
|
|
86
|
+
## Documentation
|
|
87
|
+
|
|
88
|
+
- [Overview](https://github.com/olliez-mods/cmdgui/blob/main/docs/index.md): ways to build a view, changing widgets, timers, running and quitting
|
|
89
|
+
- [Layouts](https://github.com/olliez-mods/cmdgui/blob/main/docs/layouts.md)
|
|
90
|
+
- [Widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/widgets.md)
|
|
91
|
+
- [Popups](https://github.com/olliez-mods/cmdgui/blob/main/docs/popups.md)
|
|
92
|
+
- [Keys and focus](https://github.com/olliez-mods/cmdgui/blob/main/docs/keys-and-focus.md)
|
|
93
|
+
- [Themes and colors](https://github.com/olliez-mods/cmdgui/blob/main/docs/themes.md)
|
|
94
|
+
- [Your own widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/custom-widgets.md)
|
|
95
|
+
|
|
96
|
+
The [examples](https://github.com/olliez-mods/cmdgui/tree/main/examples) folder has a
|
|
97
|
+
widget demo, a dashboard, popups, tabs, a console and a file browser.
|
cmdgui-0.2.0/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# cmdgui
|
|
2
|
+
|
|
3
|
+
Simple terminal GUIs in Python. Draw your layout as text, fill in the widgets,
|
|
4
|
+
and keep writing your program — the view runs on its own thread, so there's no
|
|
5
|
+
event loop to hand control to and no draw calls to make.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─ name ───────────────────┐
|
|
9
|
+
│quinn │[ Greet ] ON fast
|
|
10
|
+
├─ fruit ──────────────────┼─ scores ──────────────────────────────────┐
|
|
11
|
+
│ apple │name score city │
|
|
12
|
+
│ banana │Ada 98 London │
|
|
13
|
+
│ cherry │Linus 87 Helsinki │
|
|
14
|
+
│ ├─ log ─────────────────────────────────────┤
|
|
15
|
+
│ │Hello, quinn! 👋 │
|
|
16
|
+
│ │picked cherry │
|
|
17
|
+
└──────────────────────────┴───────────────────────────────────────────┘
|
|
18
|
+
████████████████████████████████░░░░░░░░░░░░░░░░ 67%[ ] done
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
No dependencies. macOS and Linux; Windows support is written but untested.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install cmdgui
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from cmdgui import View, Button, Stdout
|
|
33
|
+
import time
|
|
34
|
+
|
|
35
|
+
class App(View):
|
|
36
|
+
layout = """
|
|
37
|
+
start pause .
|
|
38
|
+
log - -
|
|
39
|
+
"""
|
|
40
|
+
start = Button("Start", on_click=lambda: print("started"))
|
|
41
|
+
pause = Button("Pause", on_click=lambda: print("paused"))
|
|
42
|
+
log = Stdout()
|
|
43
|
+
|
|
44
|
+
view = App()
|
|
45
|
+
|
|
46
|
+
for i in range(100):
|
|
47
|
+
print(f"tick {i}") # shows up in the log box
|
|
48
|
+
time.sleep(0.5)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Press `q` to quit (or Ctrl+C). Because the widgets are class attributes, your
|
|
52
|
+
editor knows `view.start` is a `Button` — autocomplete and type checking work.
|
|
53
|
+
|
|
54
|
+
## What's in it
|
|
55
|
+
|
|
56
|
+
- **Layouts drawn as text**: each word is a grid cell, `-` and `|` stretch a widget
|
|
57
|
+
across cells, and borders between neighbours join up.
|
|
58
|
+
- **Widgets**: text, labels, buttons, one-line and multi-line text boxes (with password
|
|
59
|
+
mode and history), checkboxes, toggles, radio buttons, dropdowns, sliders, progress
|
|
60
|
+
bars, menus, folding trees, tables, tabs, and a box that shows everything you print.
|
|
61
|
+
- **Popups**: dialogs, dropdowns and command menus that float over the layout.
|
|
62
|
+
- **Mouse and keyboard**: clicks, dragging, the scroll wheel, Tab between widgets, and
|
|
63
|
+
your own key bindings.
|
|
64
|
+
- **Timers**: `view.every(1, tick)` and `view.after(5, fn)`, no threads needed.
|
|
65
|
+
- **Themes**: restyle any part, with 73 named colors, the 256-color palette, or exact
|
|
66
|
+
hex colors.
|
|
67
|
+
- **Your own widgets**: subclass `Widget`, draw into a canvas, and it works in layouts.
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
- [Overview](https://github.com/olliez-mods/cmdgui/blob/main/docs/index.md): ways to build a view, changing widgets, timers, running and quitting
|
|
72
|
+
- [Layouts](https://github.com/olliez-mods/cmdgui/blob/main/docs/layouts.md)
|
|
73
|
+
- [Widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/widgets.md)
|
|
74
|
+
- [Popups](https://github.com/olliez-mods/cmdgui/blob/main/docs/popups.md)
|
|
75
|
+
- [Keys and focus](https://github.com/olliez-mods/cmdgui/blob/main/docs/keys-and-focus.md)
|
|
76
|
+
- [Themes and colors](https://github.com/olliez-mods/cmdgui/blob/main/docs/themes.md)
|
|
77
|
+
- [Your own widgets](https://github.com/olliez-mods/cmdgui/blob/main/docs/custom-widgets.md)
|
|
78
|
+
|
|
79
|
+
The [examples](https://github.com/olliez-mods/cmdgui/tree/main/examples) folder has a
|
|
80
|
+
widget demo, a dashboard, popups, tabs, a console and a file browser.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cmdgui"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Simple terminal GUIs: draw your layout as text, the view runs itself"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .view import View, Popup, Panel, Timer
|
|
2
|
+
from .widgets import (
|
|
3
|
+
Widget, Text, Label, Button, TextInput, TextArea, ProgressBar, Slider, Checkbox, Toggle,
|
|
4
|
+
RadioGroup, Select, Menu, Tree, Table, Tabs, Stdout, DrawMouse, DEFAULT_THEME, field,
|
|
5
|
+
)
|
|
6
|
+
from .inputs import Input, mouse
|
|
7
|
+
from .layout import LayoutError
|
|
8
|
+
from .shorts import Canvas, style, styled, Color, ColorName
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"View", "Popup", "Panel", "Timer", "Widget", "Text", "Label", "Button", "TextInput", "TextArea", "ProgressBar",
|
|
12
|
+
"Slider", "Checkbox", "Toggle", "RadioGroup", "Select", "Menu", "Tree", "Table", "Tabs", "Stdout", "DrawMouse", "DEFAULT_THEME", "field",
|
|
13
|
+
"Input", "mouse", "LayoutError", "Canvas", "style", "styled", "Color", "ColorName",
|
|
14
|
+
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
import unicodedata
|
|
4
|
+
from typing import Literal, Optional, Tuple, Union, get_args
|
|
4
5
|
|
|
5
6
|
ESC = "\x1b["
|
|
6
7
|
RESET = ESC + "0m"
|
|
@@ -26,11 +27,58 @@ LINE_CHARS = {
|
|
|
26
27
|
UP | DOWN | LEFT | RIGHT: "┼",
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
# The basic colors (and 'bright_' versions) use the terminal's own palette, so
|
|
31
|
+
# they match the user's theme
|
|
29
32
|
COLORS = {
|
|
30
33
|
"black": 0, "red": 1, "green": 2, "yellow": 3,
|
|
31
34
|
"blue": 4, "magenta": 5, "cyan": 6, "white": 7,
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
# More colors, as numbers in the 256-color palette (supported by nearly every terminal).
|
|
38
|
+
# "grey" works wherever "gray" does.
|
|
39
|
+
EXTRA_COLORS = {
|
|
40
|
+
# reds and pinks
|
|
41
|
+
"dark_red": 88, "maroon": 52, "crimson": 161, "scarlet": 196, "coral": 203,
|
|
42
|
+
"salmon": 209, "rose": 211, "pink": 218, "hot_pink": 205, "deep_pink": 198,
|
|
43
|
+
# oranges, yellows and browns
|
|
44
|
+
"orange": 208, "dark_orange": 166, "amber": 214, "gold": 220, "lemon": 227,
|
|
45
|
+
"cream": 229, "peach": 216, "tan": 180, "khaki": 186, "brown": 94,
|
|
46
|
+
"rust": 130, "copper": 173,
|
|
47
|
+
# greens
|
|
48
|
+
"lime": 118, "chartreuse": 112, "olive": 100, "dark_green": 28, "forest": 22,
|
|
49
|
+
"emerald": 35, "sea_green": 72, "mint": 121, "pale_green": 157,
|
|
50
|
+
# blues and cyans
|
|
51
|
+
"teal": 30, "turquoise": 44, "aqua": 51, "sky": 117, "light_blue": 153,
|
|
52
|
+
"steel_blue": 67, "cornflower": 69, "royal_blue": 63, "dodger_blue": 33,
|
|
53
|
+
"dark_blue": 19, "navy": 17, "slate": 60,
|
|
54
|
+
# purples
|
|
55
|
+
"indigo": 54, "purple": 93, "dark_purple": 53, "violet": 177, "lavender": 183,
|
|
56
|
+
"plum": 176, "orchid": 170, "fuchsia": 201,
|
|
57
|
+
# grays
|
|
58
|
+
"charcoal": 236, "dark_gray": 238, "gray": 244, "silver": 249,
|
|
59
|
+
"light_gray": 252, "snow": 255,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Every color name, so editors can autocomplete style(fg="...") and catch typos.
|
|
63
|
+
# Keep in step with COLORS and EXTRA_COLORS (checked below).
|
|
64
|
+
ColorName = Literal[
|
|
65
|
+
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "bright_black",
|
|
66
|
+
"bright_red", "bright_green", "bright_yellow", "bright_blue", "bright_magenta",
|
|
67
|
+
"bright_cyan", "bright_white",
|
|
68
|
+
"dark_red", "maroon", "crimson", "scarlet", "coral", "salmon", "rose", "pink",
|
|
69
|
+
"hot_pink", "deep_pink", "orange", "dark_orange", "amber", "gold", "lemon", "cream",
|
|
70
|
+
"peach", "tan", "khaki", "brown", "rust", "copper", "lime", "chartreuse", "olive",
|
|
71
|
+
"dark_green", "forest", "emerald", "sea_green", "mint", "pale_green", "teal",
|
|
72
|
+
"turquoise", "aqua", "sky", "light_blue", "steel_blue", "cornflower", "royal_blue",
|
|
73
|
+
"dodger_blue", "dark_blue", "navy", "slate", "indigo", "purple", "dark_purple",
|
|
74
|
+
"violet", "lavender", "plum", "orchid", "fuchsia", "charcoal", "dark_gray", "gray",
|
|
75
|
+
"silver", "light_gray", "snow",
|
|
76
|
+
]
|
|
77
|
+
# A color: a name, a 256-color palette number, "#rrggbb", or (r, g, b)
|
|
78
|
+
Color = Union[ColorName, str, int, Tuple[int, int, int]]
|
|
79
|
+
assert set(get_args(ColorName)) == set(COLORS) | {"bright_" + c for c in COLORS} | set(EXTRA_COLORS), \
|
|
80
|
+
"ColorName is out of date with COLORS / EXTRA_COLORS"
|
|
81
|
+
|
|
34
82
|
# --- Screen / cursor ------------------------------------------------------
|
|
35
83
|
|
|
36
84
|
def screen_size():
|
|
@@ -53,16 +101,21 @@ def write(s):
|
|
|
53
101
|
|
|
54
102
|
# --- Styling ----------------------------------------------------------------
|
|
55
103
|
|
|
56
|
-
def style(fg=None, bg=None, bold=False, dim
|
|
57
|
-
|
|
104
|
+
def style(fg: Optional[Color] = None, bg: Optional[Color] = None, bold: bool = False, dim: bool = False,
|
|
105
|
+
italic: bool = False, underline: bool = False, reverse: bool = False) -> str:
|
|
106
|
+
"""Escape code for a style. A color can be:
|
|
107
|
+
a name from COLORS, or 'bright_' + one of those: "red", "bright_red"
|
|
108
|
+
a name from EXTRA_COLORS: "orange", "teal", "lavender"
|
|
109
|
+
a number in the 256-color palette: 208
|
|
110
|
+
a hex string or (r, g, b) for exact colors, if the terminal has true color: "#ff8800" """
|
|
58
111
|
codes = []
|
|
59
112
|
if bold: codes.append("1")
|
|
60
113
|
if dim: codes.append("2")
|
|
61
114
|
if italic: codes.append("3")
|
|
62
115
|
if underline: codes.append("4")
|
|
63
116
|
if reverse: codes.append("7")
|
|
64
|
-
if fg: codes.append(
|
|
65
|
-
if bg: codes.append(
|
|
117
|
+
if fg is not None: codes.append(_color_code(fg, 30))
|
|
118
|
+
if bg is not None: codes.append(_color_code(bg, 40))
|
|
66
119
|
return f"{ESC}{';'.join(codes)}m" if codes else ""
|
|
67
120
|
|
|
68
121
|
def styled(text, **kwargs):
|
|
@@ -70,9 +123,28 @@ def styled(text, **kwargs):
|
|
|
70
123
|
s = style(**kwargs)
|
|
71
124
|
return f"{s}{text}{RESET}" if s else text
|
|
72
125
|
|
|
73
|
-
def _color_code(
|
|
74
|
-
|
|
75
|
-
|
|
126
|
+
def _color_code(color, base):
|
|
127
|
+
"""The SGR code for a color, base is 30 for the foreground or 40 for the background."""
|
|
128
|
+
extended = base + 8 # 38 / 48 start a 256-color or true color code
|
|
129
|
+
if isinstance(color, int) and not isinstance(color, bool) and 0 <= color <= 255:
|
|
130
|
+
return f"{extended};5;{color}"
|
|
131
|
+
if isinstance(color, (tuple, list)) and len(color) == 3:
|
|
132
|
+
return f"{extended};2;" + ";".join(str(max(0, min(255, int(v)))) for v in color)
|
|
133
|
+
if isinstance(color, str):
|
|
134
|
+
name = color.lower().replace("grey", "gray").replace(" ", "_")
|
|
135
|
+
if name.startswith("#") and len(name) == 7:
|
|
136
|
+
try:
|
|
137
|
+
return f"{extended};2;{int(name[1:3], 16)};{int(name[3:5], 16)};{int(name[5:7], 16)}"
|
|
138
|
+
except ValueError:
|
|
139
|
+
pass
|
|
140
|
+
elif name in COLORS:
|
|
141
|
+
return str(COLORS[name] + base)
|
|
142
|
+
elif name.startswith("bright_") and name[7:] in COLORS:
|
|
143
|
+
return str(COLORS[name[7:]] + base + 60)
|
|
144
|
+
elif name in EXTRA_COLORS:
|
|
145
|
+
return f"{extended};5;{EXTRA_COLORS[name]}"
|
|
146
|
+
raise ValueError(f"unknown color {color!r}: use a name from COLORS or EXTRA_COLORS "
|
|
147
|
+
f"(with 'bright_' for the basic ones), 0-255, '#rrggbb' or (r, g, b)")
|
|
76
148
|
|
|
77
149
|
# --- Text width -------------------------------------------------------------
|
|
78
150
|
# Emoji and CJK characters take two columns, combining accents take none.
|
|
@@ -190,6 +262,10 @@ class Canvas:
|
|
|
190
262
|
x += self.put(x, y, char, style)
|
|
191
263
|
return x
|
|
192
264
|
|
|
265
|
+
def restyle(self, style=""):
|
|
266
|
+
"""Give every cell the same style, keeping the characters."""
|
|
267
|
+
self.styles = [[style] * self.width for _ in range(self.height)]
|
|
268
|
+
|
|
193
269
|
def fill(self, x=0, y=0, w=None, h=None, char=" ", style=""):
|
|
194
270
|
"""Fill a rectangle (the whole canvas by default)."""
|
|
195
271
|
w = self.width if w is None else w
|