axto 0.0.2__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.
- axto-0.0.2/PKG-INFO +32 -0
- axto-0.0.2/README.md +24 -0
- axto-0.0.2/pyproject.toml +16 -0
- axto-0.0.2/setup.cfg +4 -0
- axto-0.0.2/src/axto/__init__.py +3 -0
- axto-0.0.2/src/axto/core.py +68 -0
- axto-0.0.2/src/axto/terminal.py +25 -0
- axto-0.0.2/src/axto/widgets/base.py +28 -0
- axto-0.0.2/src/axto/widgets/box.py +35 -0
- axto-0.0.2/src/axto.egg-info/PKG-INFO +32 -0
- axto-0.0.2/src/axto.egg-info/SOURCES.txt +11 -0
- axto-0.0.2/src/axto.egg-info/dependency_links.txt +1 -0
- axto-0.0.2/src/axto.egg-info/top_level.txt +1 -0
axto-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: axto
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Simple TUI library.
|
|
5
|
+
Author: Rioxpi
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# AXTO
|
|
10
|
+
|
|
11
|
+
1. Simple TUI library
|
|
12
|
+
|
|
13
|
+
## INSTALATION
|
|
14
|
+
1. `pip install axto`
|
|
15
|
+
|
|
16
|
+
## QUICK START
|
|
17
|
+
```
|
|
18
|
+
from axto import Engine
|
|
19
|
+
from axto.widgets.box import Box
|
|
20
|
+
|
|
21
|
+
app = Engine()
|
|
22
|
+
box1 = Box(x=5, y=5, width=20, height=10)
|
|
23
|
+
app.add_widget(box1)
|
|
24
|
+
app.run()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## AVAIABLE WIDGETS
|
|
28
|
+
1. BOX
|
|
29
|
+
|
|
30
|
+
## Control
|
|
31
|
+
1. `q` - exit engine
|
|
32
|
+
2. `TAB` - select next widget
|
axto-0.0.2/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# AXTO
|
|
2
|
+
|
|
3
|
+
1. Simple TUI library
|
|
4
|
+
|
|
5
|
+
## INSTALATION
|
|
6
|
+
1. `pip install axto`
|
|
7
|
+
|
|
8
|
+
## QUICK START
|
|
9
|
+
```
|
|
10
|
+
from axto import Engine
|
|
11
|
+
from axto.widgets.box import Box
|
|
12
|
+
|
|
13
|
+
app = Engine()
|
|
14
|
+
box1 = Box(x=5, y=5, width=20, height=10)
|
|
15
|
+
app.add_widget(box1)
|
|
16
|
+
app.run()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## AVAIABLE WIDGETS
|
|
20
|
+
1. BOX
|
|
21
|
+
|
|
22
|
+
## Control
|
|
23
|
+
1. `q` - exit engine
|
|
24
|
+
2. `TAB` - select next widget
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "axto"
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
description = "Simple TUI library."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Rioxpi" }
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.setuptools.packages.find]
|
|
16
|
+
where = ["src"]
|
axto-0.0.2/setup.cfg
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import tty
|
|
3
|
+
import termios
|
|
4
|
+
from .terminal import Terminal
|
|
5
|
+
|
|
6
|
+
class Engine:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.running = False
|
|
9
|
+
self.widgets = []
|
|
10
|
+
self._old_settings = None
|
|
11
|
+
self.focus_index = 0 # Index of the currently focused widget
|
|
12
|
+
|
|
13
|
+
def add_widget(self, widget):
|
|
14
|
+
self.widgets.append(widget)
|
|
15
|
+
|
|
16
|
+
def _enable_raw_mode(self):
|
|
17
|
+
"""Change terminal to raw mode to read input byte by byte."""
|
|
18
|
+
fd = sys.stdin.fileno()
|
|
19
|
+
self._old_settings = termios.tcgetattr(fd)
|
|
20
|
+
tty.setraw(fd)
|
|
21
|
+
|
|
22
|
+
def _disable_raw_mode(self):
|
|
23
|
+
"""Restore standard terminal settings."""
|
|
24
|
+
if self._old_settings:
|
|
25
|
+
termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, self._old_settings)
|
|
26
|
+
|
|
27
|
+
def run(self): # TODO: Hide cursor
|
|
28
|
+
self.running = True
|
|
29
|
+
self._enable_raw_mode()
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
Terminal.clear_screen()
|
|
33
|
+
|
|
34
|
+
# Focus the first widget if it exists
|
|
35
|
+
if self.widgets:
|
|
36
|
+
self.widgets[self.focus_index].select()
|
|
37
|
+
|
|
38
|
+
while self.running:
|
|
39
|
+
# Draw all widgets
|
|
40
|
+
self._render_all_widgets()
|
|
41
|
+
|
|
42
|
+
# 2. Handle input (reading 1 byte)
|
|
43
|
+
# This blocks the loop until a key is pressed
|
|
44
|
+
key = sys.stdin.read(1)
|
|
45
|
+
self._handle_input(key)
|
|
46
|
+
|
|
47
|
+
if self.widgets:
|
|
48
|
+
active_widget = self.widgets[self.focus_index]
|
|
49
|
+
active_widget.on_key(key)
|
|
50
|
+
finally:
|
|
51
|
+
# Restore terminal settings and clear screen on exit
|
|
52
|
+
self._disable_raw_mode()
|
|
53
|
+
Terminal.clear_screen()
|
|
54
|
+
|
|
55
|
+
def _render_all_widgets(self):
|
|
56
|
+
"""Helper method to render all widgets."""
|
|
57
|
+
for widget in self.widgets:
|
|
58
|
+
widget.draw(Terminal)
|
|
59
|
+
|
|
60
|
+
def _handle_input(self, key):
|
|
61
|
+
"""Handle user input"""
|
|
62
|
+
if key == 'q':
|
|
63
|
+
self.running = False
|
|
64
|
+
elif key == '\t': # Tab key to switch focus
|
|
65
|
+
self.focus_index = (self.focus_index + 1) % len(self.widgets)
|
|
66
|
+
index_before = (self.focus_index - 1) % len(self.widgets)
|
|
67
|
+
self.widgets[index_before].deselect() # Deselect previous widget
|
|
68
|
+
self.widgets[self.focus_index].select() # Select new widget
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
class Terminal:
|
|
4
|
+
# ANSI escape codes for terminal control
|
|
5
|
+
CLEAR = "\033[2J"
|
|
6
|
+
HOME = "\033[H"
|
|
7
|
+
RESET = "\033[0m"
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def move_cursor(x, y):
|
|
11
|
+
# ANSI uses format: \033[y;xH (first row, then column)
|
|
12
|
+
sys.stdout.write(f"\033[{y};{x}H")
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def clear_screen():
|
|
16
|
+
sys.stdout.write(Terminal.CLEAR + Terminal.HOME)
|
|
17
|
+
sys.stdout.flush()
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def write(text, color_code=None):
|
|
21
|
+
if color_code:
|
|
22
|
+
sys.stdout.write(f"\033[{color_code}m{text}{Terminal.RESET}")
|
|
23
|
+
else:
|
|
24
|
+
sys.stdout.write(text)
|
|
25
|
+
sys.stdout.flush()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class Widget:
|
|
2
|
+
def __init__(self, x, y, width, height):
|
|
3
|
+
self.x = x
|
|
4
|
+
self.y = y
|
|
5
|
+
self.width = width
|
|
6
|
+
self.height = height
|
|
7
|
+
self.handlers = {}
|
|
8
|
+
self.selected = False
|
|
9
|
+
|
|
10
|
+
def draw(self, term):
|
|
11
|
+
raise NotImplementedError("Base Widget does not implement draw method")
|
|
12
|
+
|
|
13
|
+
def on_key(self, key):
|
|
14
|
+
"""Default key handler, can be overridden by subclasses"""
|
|
15
|
+
if "key" in self.handlers:
|
|
16
|
+
self.handlers["key"](key)
|
|
17
|
+
|
|
18
|
+
def bind(self, event_type, callback):
|
|
19
|
+
"""Bind an event handler to a specific event type"""
|
|
20
|
+
self.handlers[event_type] = callback
|
|
21
|
+
|
|
22
|
+
def select(self):
|
|
23
|
+
"""Called when the widget is selected/focused"""
|
|
24
|
+
self.selected = True
|
|
25
|
+
|
|
26
|
+
def deselect(self):
|
|
27
|
+
"""Called when the widget is deselected/unfocused"""
|
|
28
|
+
self.selected = False
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from axto.widgets.base import Widget
|
|
2
|
+
|
|
3
|
+
class Box(Widget):
|
|
4
|
+
def __init__(self, x, y, width, height, default_color="37", selected_color="32", border_style="single"):
|
|
5
|
+
super().__init__(x, y, width, height)
|
|
6
|
+
self.default_color = default_color
|
|
7
|
+
self.selected_color = selected_color
|
|
8
|
+
self.border_style = border_style
|
|
9
|
+
if border_style not in ("single", "double", "none"):
|
|
10
|
+
raise ValueError("Unsupported border style. Use 'single', 'double' or 'none'.")
|
|
11
|
+
|
|
12
|
+
def draw(self, term):
|
|
13
|
+
color = self.selected_color if self.selected else self.default_color
|
|
14
|
+
|
|
15
|
+
# Draw top border
|
|
16
|
+
term.move_cursor(self.x, self.y)
|
|
17
|
+
if self.border_style == "single":
|
|
18
|
+
term.write("┌" + "─" * (self.width - 2) + "┐", color)
|
|
19
|
+
elif self.border_style == "double":
|
|
20
|
+
term.write("╔" + "═" * (self.width - 2) + "╗", color)
|
|
21
|
+
|
|
22
|
+
# Draw sides
|
|
23
|
+
for i in range(1, self.height - 1):
|
|
24
|
+
term.move_cursor(self.x, self.y + i)
|
|
25
|
+
if self.border_style == "single":
|
|
26
|
+
term.write("|" + " " * (self.width - 2) + "|", color)
|
|
27
|
+
elif self.border_style == "double":
|
|
28
|
+
term.write("║" + " " * (self.width - 2) + "║", color)
|
|
29
|
+
|
|
30
|
+
# Draw bottom border
|
|
31
|
+
term.move_cursor(self.x, self.y + self.height - 1)
|
|
32
|
+
if self.border_style == "single":
|
|
33
|
+
term.write("└" + "─" * (self.width - 2) + "┘ ", color)
|
|
34
|
+
elif self.border_style == "double":
|
|
35
|
+
term.write("╚" + "═" * (self.width - 2) + "╝ ", color)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: axto
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Simple TUI library.
|
|
5
|
+
Author: Rioxpi
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# AXTO
|
|
10
|
+
|
|
11
|
+
1. Simple TUI library
|
|
12
|
+
|
|
13
|
+
## INSTALATION
|
|
14
|
+
1. `pip install axto`
|
|
15
|
+
|
|
16
|
+
## QUICK START
|
|
17
|
+
```
|
|
18
|
+
from axto import Engine
|
|
19
|
+
from axto.widgets.box import Box
|
|
20
|
+
|
|
21
|
+
app = Engine()
|
|
22
|
+
box1 = Box(x=5, y=5, width=20, height=10)
|
|
23
|
+
app.add_widget(box1)
|
|
24
|
+
app.run()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## AVAIABLE WIDGETS
|
|
28
|
+
1. BOX
|
|
29
|
+
|
|
30
|
+
## Control
|
|
31
|
+
1. `q` - exit engine
|
|
32
|
+
2. `TAB` - select next widget
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/axto/__init__.py
|
|
4
|
+
src/axto/core.py
|
|
5
|
+
src/axto/terminal.py
|
|
6
|
+
src/axto.egg-info/PKG-INFO
|
|
7
|
+
src/axto.egg-info/SOURCES.txt
|
|
8
|
+
src/axto.egg-info/dependency_links.txt
|
|
9
|
+
src/axto.egg-info/top_level.txt
|
|
10
|
+
src/axto/widgets/base.py
|
|
11
|
+
src/axto/widgets/box.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
axto
|