871 lines
19 KiB
Markdown
871 lines
19 KiB
Markdown
# 🐍 Snakia Framework
|
||
|
||
**Snakia** is a modern Python framework for creating applications with Entity-Component-System (ECS) architecture, event system, and reactive programming. Built with performance (maybe) and modularity in mind, Snakia provides a clean API for developing complex applications ranging from games to terminal user interfaces.
|
||
|
||
## 📋 Table of Contents
|
||
|
||
- [🎯 Roadmap & TODO](#-roadmap--todo)
|
||
- [🚀 Installation](#-installation)
|
||
- [🚀 Quick Start](#-quick-start)
|
||
- [🏗️ Architecture](#️-architecture)
|
||
- [⚙️ Core](#️-core)
|
||
- [🎯 ECS System](#-ecs-system)
|
||
- [📡 Event System (ES)](#-event-system-es)
|
||
- [🔌 Plugin System](#-plugin-system)
|
||
- [🎨 TUI System](#-tui-system)
|
||
- [⚡ Reactive Programming (RX)](#-reactive-programming-rx)
|
||
- [🛠️ Utilities](#️-utilities)
|
||
- [🎭 Decorators](#-decorators)
|
||
- [🏷️ Properties](#-properties)
|
||
- [🌐 Platform Abstraction](#-platform-abstraction)
|
||
- [📦 Examples](#-examples)
|
||
- [🤝 Contributing](#-contributing)
|
||
- [🆘 Support](#-support)
|
||
- [📄 License](#-license)
|
||
|
||
### ✨ Key Features
|
||
|
||
- 🏗️ **ECS Architecture** - Flexible entity-component-system for scalable game/app logic
|
||
- 📡 **Event System** - Asynchronous event handling with filters and priorities
|
||
- 🔌 **Plugin System** - Modular plugin architecture for extensibility
|
||
- 🎨 **TUI Framework** - Rich terminal user interface with reactive widgets
|
||
- ⚡ **Reactive Programming** - Observable data streams and reactive bindings
|
||
- 🛠️ **Rich Utilities** - Decorators, properties, platform abstraction, and more
|
||
- 🎯 **Type Safety** - Full type hints and Pydantic integration
|
||
|
||
> ⚠️ **Experimental Framework**
|
||
> This framework is currently in **beta/experimental stage**. Not all features are fully implemented, there might be bugs, and the API is subject to change. Use at your own risk! 🚧
|
||
|
||
## 🚀 Installation
|
||
|
||
### Prerequisites
|
||
|
||
- **Python** >= 3.12
|
||
- **pip** or **uv** (recommended) package manager
|
||
|
||
### Install from PyPi (recommended)
|
||
|
||
```bash
|
||
pip install snakia
|
||
```
|
||
|
||
### Install from Source
|
||
|
||
```bash
|
||
# Clone the repository
|
||
git clone https://github.com/RuJect/Snakia.git
|
||
cd Snakia
|
||
|
||
# Install with pip
|
||
pip install -e .
|
||
|
||
# Or with uv (recommended)
|
||
uv sync
|
||
```
|
||
|
||
## 🎯 Roadmap & TODO
|
||
|
||
Here's what we're working on to make Snakia even better:
|
||
|
||
- [ ] Plugin Isolation: restrict plugin access to only events and components statically defined in manifest
|
||
- [ ] Async & Multithreading: implement proper async/await support and multithreading capabilities
|
||
- [ ] Platform Support: expand platform abstraction to support more operating systems
|
||
- [ ] Random Implementations: add various random generations implementations
|
||
- [ ] TUI Widgets: create more ready-to-use TUI widgets and components
|
||
- [ ] Code Documentation: add comprehensive docstrings and inline comments
|
||
- [ ] Documentation: create detailed API documentation and tutorials
|
||
|
||
## 🚀 Quick Start
|
||
|
||
```python
|
||
from snakia.core.engine import Engine
|
||
from snakia.core.loader import Meta, Plugin, PluginProcessor
|
||
from snakia.core.ecs import Component
|
||
from snakia.types import Version
|
||
|
||
# Creating a component
|
||
class HealthComponent(Component):
|
||
value: int = 100
|
||
max_value: int = 100
|
||
|
||
# Creating a processor
|
||
class HealthProcessor(PluginProcessor):
|
||
def process(self, system):
|
||
for entity, (health,) in system.get_components(HealthComponent):
|
||
if health.value <= 0:
|
||
print(f"Entity {entity} died!")
|
||
|
||
# Creating a plugin
|
||
class HealthPlugin(Plugin, meta=Meta(
|
||
name="health",
|
||
version=Version.from_args(1, 0, 0),
|
||
processors=(HealthProcessor,)
|
||
)):
|
||
def on_load(self): pass
|
||
def on_unload(self): pass
|
||
|
||
# Starting the engine
|
||
engine = Engine()
|
||
engine.loader.register(HealthPlugin)
|
||
engine.loader.load_all()
|
||
engine.start()
|
||
```
|
||
|
||
## 🏗️ Architecture
|
||
|
||
Snakia is built on a modular architecture with clear separation of concerns:
|
||
|
||
```plaintext
|
||
Snakia/
|
||
├── core/ # Framework core
|
||
│ ├── engine.py # Main engine
|
||
│ ├── ecs/ # Entity-Component-System
|
||
│ ├── es/ # Event System
|
||
│ ├── loader/ # Plugin loading system
|
||
│ ├── rx/ # Reactive programming
|
||
│ └── tui/ # Terminal User Interface
|
||
├── decorators/ # Decorators
|
||
├── property/ # Property system
|
||
├── platform/ # Platform abstraction
|
||
├── utils/ # Utilities
|
||
├── random/ # Random number generation
|
||
├── field/ # Typed fields
|
||
└── types/ # Special types
|
||
```
|
||
|
||
## ⚙️ Core
|
||
|
||
### Engine
|
||
|
||
The central component of the framework that coordinates all systems:
|
||
|
||
```python
|
||
from snakia.core.engine import Engine
|
||
|
||
engine = Engine()
|
||
# Systems:
|
||
# - engine.system - ECS system
|
||
# - engine.dispatcher - Event system
|
||
# - engine.loader - Plugin loader
|
||
|
||
engine.start() # Start all systems
|
||
engine.stop() # Stop all systems
|
||
engine.update() # Update systems
|
||
```
|
||
|
||
## 🎯 ECS System
|
||
|
||
Entity-Component-System architecture for creating flexible and performant applications.
|
||
|
||
### Component
|
||
|
||
Base class for all components:
|
||
|
||
```python
|
||
from snakia.core.ecs import Component
|
||
from pydantic import Field
|
||
|
||
class PositionComponent(Component):
|
||
x: float = Field(default=0.0)
|
||
y: float = Field(default=0.0)
|
||
|
||
class VelocityComponent(Component):
|
||
vx: float = Field(default=0.0)
|
||
vy: float = Field(default=0.0)
|
||
```
|
||
|
||
### Processor
|
||
|
||
Processors handle components in the system:
|
||
|
||
```python
|
||
from snakia.core.ecs import Processor, System
|
||
|
||
class MovementProcessor(Processor):
|
||
def process(self, system: System) -> None:
|
||
# Get all entities with Position and Velocity
|
||
for entity, (pos, vel) in system.get_components(
|
||
PositionComponent, VelocityComponent
|
||
):
|
||
pos.x += vel.vx
|
||
pos.y += vel.vy
|
||
```
|
||
|
||
### System
|
||
|
||
Entity and component management:
|
||
|
||
```python
|
||
# Creating an entity with components
|
||
entity = system.create_entity(
|
||
PositionComponent(x=10, y=20),
|
||
VelocityComponent(vx=1, vy=0)
|
||
)
|
||
|
||
# Adding a component to an existing entity
|
||
system.add_component(entity, HealthComponent(value=100))
|
||
|
||
# Getting entity components
|
||
pos, vel = system.get_components_of_entity(
|
||
entity, PositionComponent, VelocityComponent
|
||
)
|
||
|
||
# Checking for components
|
||
if system.has_components(entity, PositionComponent, VelocityComponent):
|
||
print("Entity has position and velocity")
|
||
|
||
# Removing a component
|
||
system.remove_component(entity, VelocityComponent)
|
||
|
||
# Deleting an entity
|
||
system.delete_entity(entity)
|
||
```
|
||
|
||
## 📡 Event System (ES)
|
||
|
||
Asynchronous event system with filter and priority support.
|
||
|
||
### Event
|
||
|
||
Base class for events:
|
||
|
||
```python
|
||
from snakia.core.es import Event
|
||
from pydantic import Field
|
||
|
||
class PlayerDiedEvent(Event):
|
||
player_id: int = Field()
|
||
cause: str = Field(default="unknown")
|
||
ttl: int = Field(default=10) # Event lifetime
|
||
```
|
||
|
||
### Handler
|
||
|
||
Event handlers:
|
||
|
||
```python
|
||
from snakia.core.es import Handler, Action
|
||
|
||
def on_player_died(event: PlayerDiedEvent) -> Action | None:
|
||
print(f"Player {event.player_id} died from {event.cause}")
|
||
return Action.move(1) # Move to next handler
|
||
```
|
||
|
||
### Filter
|
||
|
||
Event filters:
|
||
|
||
```python
|
||
from snakia.core.es import Filter
|
||
|
||
def only_important_deaths(event: PlayerDiedEvent) -> bool:
|
||
return event.cause in ["boss", "pvp"]
|
||
|
||
# Using a filter
|
||
@dispatcher.on(PlayerDiedEvent, filter=only_important_deaths)
|
||
def handle_important_death(event: PlayerDiedEvent):
|
||
print("Important death occurred!")
|
||
```
|
||
|
||
### Dispatcher
|
||
|
||
Central event dispatcher:
|
||
|
||
```python
|
||
from snakia.core.es import Dispatcher, Subscriber
|
||
|
||
dispatcher = Dispatcher()
|
||
|
||
# Subscribing to an event
|
||
dispatcher.subscribe(PlayerDiedEvent, Subscriber(
|
||
handler=on_player_died,
|
||
filter=only_important_deaths,
|
||
priority=10
|
||
))
|
||
|
||
# Decorator for subscription
|
||
@dispatcher.on(PlayerDiedEvent, priority=5)
|
||
def handle_death(event: PlayerDiedEvent):
|
||
print("Death handled!")
|
||
|
||
# Publishing an event
|
||
dispatcher.publish(PlayerDiedEvent(player_id=123, cause="boss"))
|
||
```
|
||
|
||
## 🔌 Plugin System
|
||
|
||
Modular system for loading and managing plugins.
|
||
|
||
### Plugin
|
||
|
||
Base class for plugins:
|
||
|
||
```python
|
||
from snakia.core.loader import Meta, Plugin, PluginProcessor
|
||
from snakia.types import Version
|
||
|
||
class MyProcessor(PluginProcessor):
|
||
def process(self, system):
|
||
# Processor logic
|
||
pass
|
||
|
||
class MyPlugin(Plugin, meta=Meta(
|
||
name="my_plugin",
|
||
author="developer",
|
||
version=Version.from_args(1, 0, 0),
|
||
processors=(MyProcessor,),
|
||
subscribers=()
|
||
)):
|
||
def on_load(self):
|
||
print("Plugin loaded!")
|
||
|
||
def on_unload(self):
|
||
print("Plugin unloaded!")
|
||
```
|
||
|
||
### Meta
|
||
|
||
Plugin metadata:
|
||
|
||
```python
|
||
from snakia.core.loader import Meta
|
||
from snakia.core.es import Subscriber
|
||
|
||
meta = Meta(
|
||
name="plugin_name",
|
||
author="author_name",
|
||
version=Version.from_args(1, 0, 0),
|
||
processors=(Processor1, Processor2),
|
||
subscribers=(
|
||
(EventType, Subscriber(handler, filter, priority)),
|
||
)
|
||
)
|
||
```
|
||
|
||
### Loader
|
||
|
||
Plugin loader:
|
||
|
||
```python
|
||
from snakia.core.loader import Loader
|
||
|
||
loader = Loader(engine)
|
||
|
||
# Registering a plugin
|
||
loader.register(MyPlugin)
|
||
|
||
# Loading all plugins
|
||
loader.load_all()
|
||
|
||
# Unloading all plugins
|
||
loader.unload_all()
|
||
```
|
||
|
||
## 🎨 TUI System
|
||
|
||
System for creating text-based user interfaces.
|
||
|
||
### Widget
|
||
|
||
Base class for widgets:
|
||
|
||
```python
|
||
from snakia.core.tui import Widget, Canvas, CanvasChar
|
||
from snakia.core.rx import Bindable
|
||
|
||
class MyWidget(Widget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.text = self.state("Hello World")
|
||
self.color = self.state(CanvasChar(fg_color="red"))
|
||
|
||
def on_render(self) -> Canvas:
|
||
canvas = Canvas(20, 5)
|
||
canvas.draw_text(0, 0, self.text.value, self.color.value)
|
||
return canvas
|
||
```
|
||
|
||
### Canvas
|
||
|
||
Drawing canvas:
|
||
|
||
```python
|
||
from snakia.core.tui import Canvas, CanvasChar
|
||
|
||
canvas = Canvas(80, 24)
|
||
|
||
# Drawing text
|
||
canvas.draw_text(10, 5, "Hello", CanvasChar(fg_color="blue"))
|
||
|
||
# Drawing rectangle
|
||
canvas.draw_rect(0, 0, 20, 10, CanvasChar("█", fg_color="green"))
|
||
|
||
# Filling area
|
||
canvas.draw_filled_rect(5, 5, 10, 5, CanvasChar(" ", bg_color="red"))
|
||
|
||
# Lines
|
||
canvas.draw_line_h(0, 0, 20, CanvasChar("-"))
|
||
canvas.draw_line_v(0, 0, 10, CanvasChar("|"))
|
||
```
|
||
|
||
### CanvasChar
|
||
|
||
Character with attributes:
|
||
|
||
```python
|
||
from snakia.core.tui import CanvasChar
|
||
|
||
char = CanvasChar(
|
||
char="A",
|
||
fg_color="red", # Text color
|
||
bg_color="blue", # Background color
|
||
bold=True, # Bold
|
||
italic=False, # Italic
|
||
underline=True # Underline
|
||
)
|
||
```
|
||
|
||
### Renderer
|
||
|
||
Screen rendering:
|
||
|
||
```python
|
||
from snakia.core.tui import RenderContext
|
||
from snakia.core.tui.render import ANSIRenderer
|
||
import sys
|
||
|
||
class StdoutTarget:
|
||
def write(self, text: str): sys.stdout.write(text)
|
||
def flush(self): sys.stdout.flush()
|
||
|
||
renderer = ANSIRenderer(StdoutTarget())
|
||
|
||
with RenderContext(renderer) as ctx:
|
||
ctx.render(widget.render())
|
||
```
|
||
|
||
### Ready-made Widgets
|
||
|
||
```python
|
||
from snakia.core.tui.widgets import (
|
||
TextWidget, BoxWidget,
|
||
HorizontalSplitWidget, VerticalSplitWidget
|
||
)
|
||
|
||
# Text widget
|
||
text = TextWidget("Hello", CanvasChar(fg_color="red", bold=True))
|
||
|
||
# Box widget
|
||
box = BoxWidget(10, 5, CanvasChar("█", fg_color="yellow"))
|
||
|
||
# Splitters
|
||
h_split = HorizontalSplitWidget([text1, text2], "|")
|
||
v_split = VerticalSplitWidget([h_split, box], "-")
|
||
```
|
||
|
||
## ⚡ Reactive Programming (RX)
|
||
|
||
Reactive programming system for creating responsive interfaces.
|
||
|
||
### Bindable
|
||
|
||
Reactive variables:
|
||
|
||
```python
|
||
from snakia.core.rx import Bindable, ValueChanged
|
||
|
||
# Creating a reactive variable
|
||
counter = Bindable(0)
|
||
|
||
# Subscribing to changes
|
||
def on_change(event: ValueChanged[int]):
|
||
print(f"Counter changed from {event.old_value} to {event.new_value}")
|
||
|
||
counter.subscribe(on_change)
|
||
|
||
# Changing value
|
||
counter.set(5) # Will call on_change
|
||
counter(10) # Alternative syntax
|
||
```
|
||
|
||
### AsyncBindable
|
||
|
||
Asynchronous reactive variables:
|
||
|
||
```python
|
||
from snakia.core.rx import AsyncBindable
|
||
|
||
async_counter = AsyncBindable(0)
|
||
|
||
async def async_handler(event: ValueChanged[int]):
|
||
print(f"Async counter: {event.new_value}")
|
||
|
||
await async_counter.subscribe(async_handler, run_now=True)
|
||
await async_counter.set(42)
|
||
```
|
||
|
||
### Operators
|
||
|
||
```python
|
||
from snakia.core.rx import map, filter, combine, merge
|
||
|
||
# Transformation
|
||
doubled = map(counter, lambda x: x * 2)
|
||
|
||
# Filtering
|
||
even_only = filter(counter, lambda x: x % 2 == 0)
|
||
|
||
# Combining
|
||
combined = combine(counter, doubled, lambda a, b: a + b)
|
||
|
||
# Merging streams
|
||
merged = merge(counter, async_counter)
|
||
```
|
||
|
||
## 🛠️ Utilities
|
||
|
||
### to_async
|
||
|
||
Converting synchronous functions to asynchronous:
|
||
|
||
```python
|
||
from snakia.utils import to_async
|
||
|
||
def sync_function(x):
|
||
return x * 2
|
||
|
||
async_function = to_async(sync_function)
|
||
result = await async_function(5)
|
||
```
|
||
|
||
### nolock
|
||
|
||
Performance optimization:
|
||
|
||
```python
|
||
from snakia.utils import nolock
|
||
|
||
def busy_loop():
|
||
while running:
|
||
# Work
|
||
nolock() # Release GIL
|
||
```
|
||
|
||
### inherit
|
||
|
||
Simplified inheritance:
|
||
|
||
```python
|
||
from snakia.utils import inherit
|
||
|
||
class Base:
|
||
def method(self): pass
|
||
|
||
class Derived(inherit(Base)):
|
||
def method(self):
|
||
super().method()
|
||
# Additional logic
|
||
```
|
||
|
||
### this
|
||
|
||
Reference to current object:
|
||
|
||
```python
|
||
from snakia.utils import this
|
||
|
||
def func():
|
||
return this() # Returns `<function func at ...>`
|
||
```
|
||
|
||
### throw
|
||
|
||
Throwing exceptions:
|
||
|
||
```python
|
||
from snakia.utils import throw
|
||
|
||
def validate(value):
|
||
if value < 0:
|
||
throw(ValueError("Value must be positive"))
|
||
```
|
||
|
||
### frame
|
||
|
||
Working with frames:
|
||
|
||
```python
|
||
from snakia.utils import frame
|
||
|
||
def process_frame():
|
||
current_frame = frame()
|
||
# Process frame
|
||
```
|
||
|
||
## 🎭 Decorators
|
||
|
||
### inject_replace
|
||
|
||
Method replacement:
|
||
|
||
```python
|
||
from snakia.decorators import inject_replace
|
||
|
||
class Original:
|
||
def method(self): return "original"
|
||
|
||
@inject_replace(Original, "method")
|
||
def new_method(self): return "replaced"
|
||
```
|
||
|
||
### inject_before / inject_after
|
||
|
||
Hooks before and after execution:
|
||
|
||
```python
|
||
from snakia.decorators import inject_before, inject_after
|
||
|
||
@inject_before(MyClass, "method")
|
||
def before_hook(self): print("Before method")
|
||
|
||
@inject_after(MyClass, "method")
|
||
def after_hook(self): print("After method")
|
||
```
|
||
|
||
### singleton
|
||
|
||
Singleton pattern:
|
||
|
||
```python
|
||
from snakia.decorators import singleton
|
||
|
||
@singleton
|
||
class Database:
|
||
def __init__(self):
|
||
self.connection = "connected"
|
||
```
|
||
|
||
### pass_exceptions
|
||
|
||
Exception handling:
|
||
|
||
```python
|
||
from snakia.decorators import pass_exceptions
|
||
|
||
@pass_exceptions(ValueError, TypeError)
|
||
def risky_function():
|
||
# Code that might throw exceptions
|
||
pass
|
||
```
|
||
|
||
## 🏷️ Properties
|
||
|
||
### readonly
|
||
|
||
Read-only property:
|
||
|
||
```python
|
||
from snakia.property import readonly
|
||
|
||
|
||
class Currency:
|
||
@readonly
|
||
def rate(self) -> int:
|
||
return 100
|
||
|
||
|
||
currency = Currency()
|
||
currency.rate = 200
|
||
print(currency.rate) # Output: 100
|
||
```
|
||
|
||
### initonly
|
||
|
||
Initialization-only property:
|
||
|
||
```python
|
||
from snakia.property import initonly
|
||
|
||
|
||
class Person:
|
||
name = initonly[str]("name")
|
||
|
||
|
||
bob = Person()
|
||
bob.name = "Bob"
|
||
print(bob.name) # Output: "Bob"
|
||
bob.name = "not bob"
|
||
print(bob.name) # Output: "Bob"
|
||
```
|
||
|
||
### 🏛️ classproperty
|
||
|
||
Class property:
|
||
|
||
```python
|
||
from snakia.property import classproperty
|
||
|
||
class MyClass:
|
||
@classproperty
|
||
def class_value(cls):
|
||
return "class_value"
|
||
```
|
||
|
||
## 🌐 Platform Abstraction
|
||
|
||
### 🖥️ PlatformOS
|
||
|
||
Operating system abstraction:
|
||
|
||
```python
|
||
from snakia.platform import PlatformOS, OS
|
||
|
||
# Detecting current OS
|
||
current_os = OS.current()
|
||
|
||
if current_os == PlatformOS.LINUX:
|
||
print("Running on Linux")
|
||
elif current_os == PlatformOS.ANDROID:
|
||
print("Running on Android")
|
||
```
|
||
|
||
### 🏗️ PlatformLayer
|
||
|
||
Platform layers:
|
||
|
||
```python
|
||
from snakia.platform import LinuxLayer, AndroidLayer
|
||
|
||
# Linux layer
|
||
linux_layer = LinuxLayer()
|
||
|
||
# Android layer
|
||
android_layer = AndroidLayer()
|
||
```
|
||
|
||
## 📦 Examples
|
||
|
||
### Health System
|
||
|
||
```python
|
||
from snakia.core.engine import Engine
|
||
from snakia.core.ecs import Component
|
||
from snakia.core.es import Event
|
||
from snakia.core.loader import Meta, Plugin, PluginProcessor
|
||
from snakia.types import Version
|
||
from pydantic import Field
|
||
|
||
class HealthComponent(Component):
|
||
max_value: int = Field(default=100, ge=0)
|
||
value: int = Field(default=100, ge=0)
|
||
|
||
class DamageComponent(Component):
|
||
damage: int = Field(ge=0)
|
||
ticks: int = Field(default=1, ge=0)
|
||
|
||
class DeathEvent(Event):
|
||
entity: int = Field()
|
||
|
||
class HealthProcessor(PluginProcessor):
|
||
def process(self, system):
|
||
# Processing damage
|
||
for entity, (damage, health) in system.get_components(
|
||
DamageComponent, HealthComponent
|
||
):
|
||
health.value -= damage.damage
|
||
damage.ticks -= 1
|
||
|
||
if damage.ticks <= 0:
|
||
system.remove_component(entity, DamageComponent)
|
||
|
||
if health.value <= 0:
|
||
system.remove_component(entity, HealthComponent)
|
||
self.plugin.dispatcher.publish(DeathEvent(entity=entity))
|
||
|
||
class HealthPlugin(Plugin, meta=Meta(
|
||
name="health",
|
||
version=Version.from_args(1, 0, 0),
|
||
processors=(HealthProcessor,)
|
||
)):
|
||
def on_load(self): pass
|
||
def on_unload(self): pass
|
||
|
||
# Usage
|
||
engine = Engine()
|
||
engine.loader.register(HealthPlugin)
|
||
engine.loader.load_all()
|
||
|
||
# Creating a player
|
||
player = engine.system.create_entity(
|
||
HealthComponent(value=100, max_value=100)
|
||
)
|
||
|
||
# Dealing damage
|
||
engine.system.add_component(player, DamageComponent(damage=25, ticks=1))
|
||
|
||
engine.start()
|
||
```
|
||
|
||
### TUI Application
|
||
|
||
```python
|
||
from snakia.core.tui import CanvasChar, RenderContext
|
||
from snakia.core.tui.render import ANSIRenderer
|
||
from snakia.core.tui.widgets import TextWidget, BoxWidget, VerticalSplitWidget
|
||
import sys
|
||
|
||
class StdoutTarget:
|
||
def write(self, text: str): sys.stdout.write(text)
|
||
def flush(self): sys.stdout.flush()
|
||
|
||
def main():
|
||
# Creating widgets
|
||
title = TextWidget("Snakia TUI", CanvasChar(fg_color="cyan", bold=True))
|
||
content = TextWidget("Welcome to Snakia!", CanvasChar(fg_color="white"))
|
||
box = BoxWidget(20, 5, CanvasChar("█", fg_color="green"))
|
||
|
||
# Layout
|
||
layout = VerticalSplitWidget([title, content, box], "-")
|
||
|
||
# Rendering
|
||
renderer = ANSIRenderer(StdoutTarget())
|
||
|
||
with RenderContext(renderer) as ctx:
|
||
ctx.render(layout.render())
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
## 🤝 Contributing
|
||
|
||
We welcome contributions to Snakia development! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
|
||
|
||
### How to Contribute
|
||
|
||
1. **Fork** the repository
|
||
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
||
3. **Make** your changes
|
||
4. **Add** tests if applicable
|
||
5. **Commit** your changes (`git commit -m 'Add amazing feature'`)
|
||
6. **Push** to the branch (`git push origin feature/amazing-feature`)
|
||
7. **Open** a Pull Request
|
||
|
||
### Development Guidelines
|
||
|
||
- Add type hints to all new code
|
||
- Write clear commit messages
|
||
- Update documentation for new features
|
||
- Test your changes thoroughly
|
||
|
||
## 🆘 Support
|
||
|
||
Need help? We're here to assist you!
|
||
|
||
- 🐛 **Bug Reports** - [GitHub Issues](https://github.com/RuJect/Snakia/issues)
|
||
- 💬 **Community Chat** - [RuJect Community Telegram](https://t.me/RuJect_Community)
|
||
- 📧 **Direct Contact** - mailto:rus07tam.uwu@gmail.com
|
||
|
||
## 📄 License
|
||
|
||
See the `LICENSE` file for details.
|