Skip to content

Commit 81ef06d

Browse files
committed
Initial Commit
0 parents  commit 81ef06d

File tree

13 files changed

+858
-0
lines changed

13 files changed

+858
-0
lines changed

.flake8

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[flake8]
2+
max-line-length = 88
3+
extend-ignore = E203
4+
exclude =
5+
.git,
6+
__pycache__,
7+
build,
8+
dist,
9+
venv

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# Testing
7+
.pytest_cache
8+
9+
# Environments
10+
.env
11+
venv
12+
venv*
13+
14+
# Packaging
15+
dist
16+
build
17+
*.egg-info/
18+
19+
# Other
20+
.vscode
21+
*.log

LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright 2021 Tom Saunders
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a
4+
copy of this software and associated documentation files (the "Software"),
5+
to deal in the Software without restriction, including without limitation
6+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
7+
and/or sell copies of the Software, and to permit persons to whom the
8+
Software is furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included
11+
in all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
17+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19+
OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Textual Inputs 🔡
2+
3+
[![Python Versions](https://shields.io/pypi/pyversions/textual-inputs)](https://www.python.org/downloads/)
4+
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5+
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
6+
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
7+
8+
Textual Inputs is a collection of input widgets for the [Textual](https://github.com/willmcgugan/textual) TUI framework.
9+
10+
⚠️ This library is experimental and its interfaces are likely
11+
to change, much like the underlying Textual library.
12+
13+
## Supported Widgets
14+
15+
### TextInput 🔡
16+
17+
- value - string
18+
- one line of text
19+
- placeholder and title support
20+
- password mode to hide input
21+
- support for ASCII printable characters
22+
- controls: arrow right/left, home, end, delete, backspace/ctrl+h, escape
23+
- emits - InputOnChange, InputOnFocus messages
24+
25+
### IntegerInput 🔢
26+
27+
- value - integer or None
28+
- placeholder and title support
29+
- type a number or arrow up/down to increment/decrement the integer.
30+
- controls: arrow right/left, home, end, delete, backspace/ctrl+h, escape
31+
- emits - InputOnChange, InputOnFocus messages
32+
33+
## Quick Start
34+
35+
```bash
36+
python -m pip install textual-inputs
37+
```
38+
39+
Checkout the [examples](https://github.com/sirfuzzalot/textual-inputs/tree/main/examples) for reference.
40+
41+
```bash
42+
git clone https://github.com/sirfuzzalot/textual-inputs.git
43+
cd textual-inputs
44+
python3 -m venv venv
45+
source venv/bin/activate
46+
python -m pip install -r requirements.txt
47+
python examples/simple_form.py
48+
```
49+
50+
To use Textual Inputs
51+
52+
```python
53+
from textual_inputs import TextInput, IntegerInput
54+
```

examples/simple_form.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# app.py
2+
import rich.box
3+
from rich.panel import Panel
4+
from rich.style import Style
5+
from rich.table import Table
6+
from rich.text import Text
7+
from textual import events
8+
from textual.app import App
9+
from textual.reactive import Reactive
10+
from textual.widgets import Footer, Header, Static
11+
12+
from textual_inputs import IntegerInput, TextInput
13+
14+
15+
class CustomHeader(Header):
16+
"""Override the default Header for Styling"""
17+
18+
def __init__(self) -> None:
19+
super().__init__()
20+
self.tall = False
21+
22+
def render(self) -> Table:
23+
header_table = Table.grid(padding=(0, 1), expand=True)
24+
header_table.style = Style(color="white", bgcolor="rgb(98,98,98)")
25+
header_table.add_column(justify="left", ratio=0, width=8)
26+
header_table.add_column("title", justify="center", ratio=1)
27+
header_table.add_column("clock", justify="right", width=8)
28+
header_table.add_row(
29+
"🔤", self.full_title, self.get_clock() if self.clock else ""
30+
)
31+
return header_table
32+
33+
async def on_click(self, event: events.Click) -> None:
34+
return await super().on_click(event)
35+
36+
37+
class CustomFooter(Footer):
38+
"""Override the default Footer for Styling"""
39+
40+
def make_key_text(self) -> Text:
41+
"""Create text containing all the keys."""
42+
text = Text(
43+
style="white on rgb(98,98,98)",
44+
no_wrap=True,
45+
overflow="ellipsis",
46+
justify="left",
47+
end="",
48+
)
49+
for binding in self.app.bindings.shown_keys:
50+
key_display = (
51+
binding.key.upper()
52+
if binding.key_display is None
53+
else binding.key_display
54+
)
55+
hovered = self.highlight_key == binding.key
56+
key_text = Text.assemble(
57+
(f" {key_display} ", "reverse" if hovered else "default on default"),
58+
f" {binding.description} ",
59+
meta={"@click": f"app.press('{binding.key}')", "key": binding.key},
60+
)
61+
text.append_text(key_text)
62+
return text
63+
64+
65+
class Demo(App):
66+
67+
current_index: Reactive[int] = Reactive(-1)
68+
69+
def __init__(self, **kwargs) -> None:
70+
super().__init__(**kwargs)
71+
self.tab_index = ["username", "password", "age"]
72+
73+
async def on_load(self) -> None:
74+
await self.bind("q", "quit", "Quit")
75+
await self.bind("enter", "submit", "Submit")
76+
await self.bind("escape", "reset_focus", show=False)
77+
await self.bind("ctrl+i", "next_tab_index", show=False)
78+
await self.bind("shift+tab", "previous_tab_index", show=False)
79+
80+
async def on_mount(self) -> None:
81+
82+
self.header = CustomHeader()
83+
await self.view.dock(self.header, edge="top")
84+
await self.view.dock(CustomFooter(), edge="bottom")
85+
86+
self.username = TextInput(
87+
name="username",
88+
placeholder="enter your username...",
89+
title="Username",
90+
)
91+
self.password = TextInput(
92+
name="password",
93+
placeholder="enter your password...",
94+
title="Password",
95+
password=True,
96+
)
97+
self.age = IntegerInput(
98+
name="age",
99+
placeholder="enter your age...",
100+
title="Age",
101+
)
102+
self.output = Static(
103+
renderable=Panel(
104+
"",
105+
title="Report",
106+
border_style="blue",
107+
box=rich.box.SQUARE
108+
)
109+
)
110+
111+
await self.view.dock(self.output, edge="left", size=40)
112+
await self.view.dock(self.username, self.password, self.age, edge="top")
113+
114+
async def action_next_tab_index(self) -> None:
115+
"""Changes the focus to the next form field"""
116+
if self.current_index < len(self.tab_index) - 1:
117+
self.current_index += 1
118+
await getattr(self, self.tab_index[self.current_index]).focus()
119+
120+
async def action_previous_tab_index(self) -> None:
121+
"""Changes the focus to the previous form field"""
122+
self.log(f"PREVIOUS {self.current_index}")
123+
if self.current_index > 0:
124+
self.current_index -= 1
125+
await getattr(self, self.tab_index[self.current_index]).focus()
126+
127+
async def action_submit(self) -> None:
128+
formatted = f"""
129+
username: {self.username.value}
130+
password: {"".join("•" for _ in self.password.value)}
131+
age: {self.age.value}
132+
"""
133+
await self.output.update(
134+
Panel(
135+
formatted,
136+
title="Report",
137+
border_style="blue",
138+
box=rich.box.SQUARE
139+
)
140+
)
141+
142+
async def action_reset_focus(self) -> None:
143+
self.current_index = -1
144+
await self.header.focus()
145+
146+
async def message_input_on_change(self, message) -> None:
147+
self.log(f"Input: {message.sender.name} changed")
148+
149+
async def message_input_on_focus(self, message) -> None:
150+
self.current_index = self.tab_index.index(message.sender.name)
151+
152+
153+
if __name__ == "__main__":
154+
Demo.run(title="Textual-Inputs Demo", log="textual.log")

pyproject.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[build-system]
2+
requires = [
3+
"setuptools>=42",
4+
"wheel"
5+
]
6+
build-backend = "setuptools.build_meta"
7+
8+
[tool.black]
9+
exclude = 'venv'
10+
11+
[tool.isort]
12+
profile = "black"
13+
multi_line_output = 3
14+
skip = ["venv"]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
textual==0.1.10

setup.cfg

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
[metadata]
2+
name = textual-inputs
3+
version = attr: textual_inputs.__version__
4+
author = Tom Saunders
5+
license = MIT License
6+
license_files = LICENSE
7+
description = textual-inputs is a collection of input widgets for the Textual TUI framework
8+
long_description = file: README.md
9+
long_description_content_type = text/markdown
10+
url = https://github.com/sirfuzzalot/textual-inputs
11+
project_urls =
12+
Tracker = https://github.com/sirfuzzalot/textual-inputs/issues
13+
Documentation = https://github.com/sirfuzzalot/textual-inputs
14+
Source = https://github.com/sirfuzzalot/textual-inputs
15+
classifiers =
16+
Development Status :: 2 - Pre-Alpha
17+
License :: OSI Approved :: MIT License
18+
Environment :: Console
19+
Intended Audience :: Developers
20+
Operating System :: MacOS
21+
Operating System :: POSIX :: Linux
22+
Programming Language :: Python
23+
Programming Language :: Python :: 3
24+
Programming Language :: Python :: 3 :: Only
25+
Programming Language :: Python :: 3.8
26+
Programming Language :: Python :: 3.9
27+
Programming Language :: Python :: 3.10
28+
29+
[options]
30+
package_dir =
31+
= src
32+
packages = find:
33+
python_requires = >=3.8
34+
install_requires =
35+
textual >= 0.1.10
36+
37+
[options.packages.find]
38+
where = src

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from setuptools import setup
2+
3+
setup()

src/textual_inputs/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .integer_input import IntegerInput
2+
from .text_input import TextInput
3+
4+
__version__ = "0.1.2"
5+
6+
__all__ = ["IntegerInput", "TextInput"]

0 commit comments

Comments
 (0)