Metadata-Version: 2.4
Name: pycrome
Version: 1.3.0
Summary: Python UI language for desktop applications
Home-page: https://github.com/BenEwoku/Pycrome
Author: Centra Holdings SMC Ltd
Author-email: bewoku14@outlook.com
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: User Interfaces
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pywebview>=4.0.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# PyCrome

**PyCrome** is a Python-powered UI language for building modern desktop applications. Write declarative `.pycrome` files, connect a Python backend, and get a fully functional, beautifully styled desktop app — no HTML, CSS, or JavaScript required.

Built by **Centra Holdings SMC Ltd**, Uganda.

---

## Why PyCrome

Most desktop UI frameworks require either verbose boilerplate (PySide6, Tkinter) or a full web stack (Electron). PyCrome gives you a clean declarative syntax, a modern Chromium-based renderer, and direct access to Python for all business logic.

```
Window title="My App" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Dashboard" icon="home" route="dashboard"
        NavItem label="Students" icon="users" route="students"
        ThemeSwitcher
    MainPanel
        Screen id="dashboard" default="true"
            Header title="Dashboard" subtitle="Welcome"
            Row
                StatCard label="Total Students" value="0" source="get_count()" icon="users" color="primary"
                StatCard label="Fees Collected" value="0" source="get_fees()" icon="fees" color="success"
```

---

## Installation

```bash
pip install pywebview
cd F:/PROJECTS/PyCrome
pip install -e .
```

---

## Quick Start

**1. Create a new project**

```bash
pycrome new myapp
cd myapp
```

**2. Edit `app.pycrome`**

```
Window title="My App" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Home" icon="home" route="home"
        ThemeSwitcher
    MainPanel
        Screen id="home" default="true"
            Header title="Hello PyCrome"
            Button label="Click Me" style="primary" onclick="say_hello()"
```

**3. Edit `backend.py`**

```python
class Backend:
    def say_hello(self):
        return {"success": True, "message": "Hello from Python!"}
```

**4. Run**

```bash
pycrome run app.pycrome
```

---

## Requirements

- Python 3.12+
- pywebview 6.x
- Windows 10/11 (primary), macOS, Linux

---

## Project Structure

```
myapp/
    app.pycrome           # UI definition
    backend.py            # Python business logic
    main.py               # Entry point
    pycrome.config.json   # Project configuration
```

---

## CLI Commands

| Command | Description |
|---|---|
| `pycrome run app.pycrome` | Launch the app |
| `pycrome run app.pycrome --debug` | Launch with dev tools |
| `pycrome new myapp` | Scaffold a new project |
| `pycrome check app.pycrome` | Validate syntax |
| `pycrome preview app.pycrome` | Preview in browser |
| `pycrome watch app.pycrome` | Run with hot reload |
| `pycrome build app.pycrome` | Package as .exe |
| `pycrome list` | List all components |
| `pycrome version` | Show version |

---

## Themes

PyCrome ships with five built-in themes: `dark`, `light`, `blue`, `green`, `purple`.

Set in your Window tag:

```
Window theme="dark"
Window theme="light"
Window theme="blue"
```

Or override individual colours:

```
Window theme="dark" primary="#e11d48"
```

Users can switch themes at runtime using the `ThemeSwitcher` component.

---

## License

Proprietary. Built and maintained by Centra Holdings SMC Ltd.




# ======================================PyCrome Architecture =======================================================

This document describes how PyCrome works internally. It is intended for developers extending or maintaining the PyCrome engine.

---

## Overview

PyCrome is a three-stage pipeline:

```
.pycrome source file
        |
   [1] Lexer          tokenises raw text into tokens
        |
   [2] Parser         builds a component tree (AST)
        |
   [3] Renderer       generates HTML, CSS, and JS
        |
   pywebview          displays as a native desktop window
        |
   Python Backend     handles all business logic
```

---

## Project Structure

```
pycrome/
    __init__.py       package marker
    lexer.py          tokeniser
    parser.py         AST builder
    renderer.py       HTML/CSS/JS generator
    theme.py          theme definitions and utilities
    engine.py         orchestrator, pywebview launcher
    cli.py            command line interface
```

---

## Stage 1: Lexer (`lexer.py`)

The lexer reads the raw `.pycrome` source text and converts it into a flat list of tokens.

### Token Types

| Token | Description |
|---|---|
| `COMPONENT` | A known component name (Window, Button, etc.) |
| `SCREEN` | The Screen component specifically |
| `IDENTIFIER` | An unrecognised name, treated as a component |
| `ATTRIBUTE` | A key=value pair |
| `INDENT` | Indentation increase |
| `DEDENT` | Indentation decrease |
| `NEWLINE` | End of line |
| `EOF` | End of file |

### Indentation Tracking

The lexer maintains an indent stack. When indentation increases, an INDENT token is emitted. When it decreases, one or more DEDENT tokens are emitted to match.

### Attribute Parsing

Attributes are parsed using a regex that handles three value types:

```
key="string value"    -> ATTRIBUTE (key, "string value")
key=123               -> ATTRIBUTE (key, 123)
key=true              -> ATTRIBUTE (key, True)
```

### COMPONENTS Set

All valid component names are defined in the `COMPONENTS` set. Names not in this set are tokenised as `IDENTIFIER`. The parser treats IDENTIFIER tokens the same as COMPONENT tokens when building children, so custom or unknown components degrade gracefully.

---

## Stage 2: Parser (`parser.py`)

The parser takes the flat token list and builds a tree of `UINode` objects.

### UINode

```python
@dataclass
class UINode:
    component: str                    # Component name
    attrs: dict[str, Any]             # Attribute key-value pairs
    children: list["UINode"]          # Child components
```

### Parsing Algorithm

The parser uses a recursive descent approach:

1. Read the current token
2. If it is a COMPONENT, SCREEN, or IDENTIFIER, create a UINode
3. Collect all following ATTRIBUTE tokens as the node's attributes
4. Skip the NEWLINE
5. If the next token is INDENT, recurse into `_parse_children`
6. `_parse_children` runs until it hits a DEDENT or EOF

### Tree Structure

A typical tree for a simple app:

```
Window (title="My App")
├── Sidebar (width=220)
│   ├── NavItem (label="Dashboard", route="dashboard")
│   └── ThemeSwitcher
├── MainPanel
│   └── Screen (id="dashboard", default=True)
│       ├── Header (title="Dashboard")
│       └── Button (label="Add", onclick="add()")
└── Modal (id="add-form", title="Add Record")
    └── Form (onsubmit="save")
        ├── FormField (id="name", label="Name")
        └── FormActions
            └── Button (label="Save", type="submit")
```

---

## Stage 3: Renderer (`renderer.py`)

The renderer walks the UINode tree and generates a complete HTML page string.

### Dispatch Pattern

For each node, the renderer calls a method named `_render_{component_lower}`:

```python
def _render_node(self, node: UINode) -> str:
    method = f"_render_{node.component.lower()}"
    if hasattr(self, method):
        return getattr(self, method)(node)
    return self._render_generic(node)
```

Unknown components fall through to `_render_generic`, which wraps children in a `<div class="pc-{component}">`.

### Adding a New Component

1. Add the component name to `COMPONENTS` in `lexer.py`
2. Add a `_render_componentname` method to `Renderer` in `renderer.py`
3. Add CSS for `.pc-componentname` in `_base_css`
4. Add any JS the component needs in `_html_shell`
5. Document in `COMPONENT_DOCS` in `cli.py`

### CSS Architecture

All CSS is generated in `_base_css` as a single f-string. CSS custom properties (variables) are generated from the active theme:

```python
def generate_css_variables(theme: dict) -> str:
    vars_ = "\n".join(
        f"    --{key.replace('_', '-')}: {value};"
        for key, value in theme.items()
    )
    return f":root {{\n{vars_}\n}}"
```

Every component uses these variables rather than hard-coded colours, enabling runtime theme switching by simply updating the `:root` variables via JavaScript.

### JavaScript Architecture

All JavaScript lives in `_html_shell` inside a single `<script>` block. Key subsystems:

| System | Functions |
|---|---|
| Theme | `pycromeSetTheme`, `PYCROME_THEMES` |
| Navigation | `navigateTo`, `pycromeNavigate`, `navigate` |
| Bridge | `pycromeCall` |
| Modals | `pycromeOpenModal`, `pycromeCloseModal`, `pycromePopulateModal` |
| Toasts | `pycromeToast`, `toast.success`, `toast.error` |
| Forms | `pycromeHandleSubmit`, `pycromeValidateForm` |
| Tables | `pycromeFilterTable`, `pycromeHandleRowClick` |
| Pagination | `pycromeInitPagination`, `pycromeRenderPage`, `pycromeGotoPage` |
| Context menu | `pycromeShowContext`, `pycromeCloseContext` |
| Dropdown | `pycromeToggleDropdown`, `pycromeCloseDropdown` |
| Confirmation | `pycromeConfirm`, `pycromeConfirmCancel` |
| Charts | `pycromeRenderChart` |
| DataGrid | `pycromeGridEdit`, `pycromeGridSave` |
| Keyboard | `document.addEventListener('keydown', ...)` |

### Python-JavaScript Bridge

pywebview exposes the backend instance as `window.pywebview.api`. Every public method on the backend class becomes callable from JavaScript.

The `pycromeCall` function handles:

- Stripping parentheses from function name strings
- Checking `window.pywebview.api` is ready
- Returning parsed JSON if result is a string
- Showing a toast if the function is not found
- Catching and reporting exceptions

```javascript
async function pycromeCall(func, ...args) {
    const fn = func.replace(/[(][^)]*[)]/, '').trim();
    if (window.pywebview && window.pywebview.api) {
        if (typeof window.pywebview.api[fn] === 'function') {
            const result = await window.pywebview.api[fn](...args);
            return typeof result === 'string' ? JSON.parse(result) : result;
        }
    }
    return null;
}
```

### Safe Backend Wrapper

`engine.py` wraps the backend in a `SafeBackend` proxy:

```python
def _make_safe_backend(self):
    for name in dir(backend):
        if name.startswith("_"):
            continue
        method = getattr(backend, name)
        if callable(method):
            setattr(safe, name, make_wrapper(method))
```

Each wrapper catches exceptions and returns a JSON error response rather than crashing the app.

---

## Theme System (`theme.py`)

### THEMES Dictionary

All built-in themes are defined as nested dictionaries keyed by theme name. Each theme maps snake_case variable names to CSS colour values.

### Runtime Switching

`generate_runtime_theme_js` serialises all themes to a JavaScript object embedded in the HTML:

```javascript
const PYCROME_THEMES = {
    "dark": { "--bg-primary": "#0f0f1a", ... },
    "light": { "--bg-primary": "#f8fafc", ... },
    ...
};
```

`pycromeSetTheme` updates all CSS variables on `:root` at runtime:

```javascript
function pycromeSetTheme(themeName) {
    Object.entries(PYCROME_THEMES[themeName]).forEach(([key, value]) => {
        document.documentElement.style.setProperty(key, value);
    });
}
```

### Colour Overrides

`apply_overrides` merges Window attribute overrides into the selected theme before rendering:

```python
COLOUR_OVERRIDES = {
    "primary":    "primary",
    "background": "bg_primary",
    "surface":    "bg_surface",
    ...
}

def apply_overrides(theme, attrs):
    for attr_key, theme_key in COLOUR_OVERRIDES.items():
        if attr_key in attrs:
            theme[theme_key] = attrs[attr_key]
    return theme
```

---

## Engine (`engine.py`)

The engine orchestrates the full pipeline:

1. Opens the `.pycrome` file
2. Runs Lexer, Parser, Renderer
3. Extracts window title and dimensions from the root node attrs
4. Creates a pywebview window with the generated HTML
5. Passes the safe backend proxy as `js_api`

Error handling: if any stage fails, a readable error HTML page is shown instead of crashing.

---

## CLI (`cli.py`)

The CLI uses Python's `argparse` with subcommands. Each command is a standalone function prefixed with `cmd_`. The `main()` function wires commands to parsers and dispatches.

Commands load PyCrome by inserting the project root into `sys.path` and importing dynamically, so the CLI works from any directory.

---

## Extending PyCrome

### Adding a Component

Minimum steps to add a new component called `InfoPanel`:

**lexer.py**
```python
COMPONENTS = {
    ...,
    "InfoPanel",
}
```

**renderer.py**
```python
def _render_infopanel(self, node: UINode) -> str:
    title   = node.attrs.get("title", "")
    content = node.attrs.get("content", "")
    children_html = "".join(self._render_node(c) for c in node.children)
    return f"""
    <div class="pc-infopanel">
        <div class="pc-infopanel-title">{title}</div>
        <div class="pc-infopanel-body">{content}{children_html}</div>
    </div>"""
```

**renderer.py `_base_css`**
```python
        .pc-infopanel {{
            background: var(--bg-surface);
            border-left: 4px solid var(--primary);
            border-radius: var(--radius);
            padding: 16px 20px;
            margin-bottom: 16px;
        }}
        .pc-infopanel-title {{
            font-weight: 600;
            color: var(--text-primary);
            margin-bottom: 8px;
        }}
        .pc-infopanel-body {{
            color: var(--text-secondary);
            font-size: 14px;
        }}
```

**cli.py `COMPONENT_DOCS`**
```python
"InfoPanel": "Information panel with coloured border. Attrs: title, content",
```

Usage in `.pycrome`:
```
InfoPanel title="Notice" content="Term 2 fees are due by 15th March."
```

---

## Performance Notes

- The HTML generation happens once at startup. There is no virtual DOM or diff system.
- All data loading is asynchronous via the Python bridge.
- Table filtering is done client-side in JS for instant response.
- Pagination loads all data once then slices client-side for small to medium datasets. For very large datasets, implement server-side pagination in the backend.
- Column resizing state is not persisted between sessions.


# =================================PyCrome Examples============================================

Real-world usage examples for common application patterns.

---

## School Management System

A complete school management screen with students, fees, and dashboard.

### app.pycrome

```
Window title="School Manager" width=1400 height=900 theme="dark"
    Sidebar width=240
        NavItem label="Dashboard" icon="home" route="dashboard"
        NavItem label="Students" icon="users" route="students"
        NavItem label="Fees" icon="fees" route="fees"
        NavItem label="Reports" icon="reports" route="reports"
        NavItem label="Settings" icon="settings" route="settings"
        ThemeSwitcher
    MainPanel
        Screen id="dashboard" default="true"
            Header title="Dashboard" subtitle="School overview"
            Row
                StatCard label="Total Students" value="0" icon="users" color="primary" source="get_student_count()"
                StatCard label="Fees Collected" value="0" icon="fees" color="success" source="get_fees_collected()"
                StatCard label="Outstanding" value="0" icon="warning" color="danger" source="get_outstanding()"
                StatCard label="Active" value="0" icon="check" color="success" source="get_active_count()"
            Spacer size=24
            Row
                Col span=2
                    Card
                        Label text="Fee Collection Progress"
                        ProgressBar label="Term 1" value="0" source="get_term1_progress()" color="success"
                        ProgressBar label="Term 2" value="0" source="get_term2_progress()" color="warning"
                        ProgressBar label="Term 3" value="0" source="get_term3_progress()" color="danger"
                Col span=1
                    Card
                        Label text="Quick Actions"
                        Button label="Add Student" style="primary" icon="add" onclick="pycromeOpenModal('add-student')"
                        Button label="Record Payment" style="primary" icon="fees" onclick="pycromeOpenModal('add-payment')"

        Screen id="students"
            Header title="Students" subtitle="Manage enrolled students"
            Toolbar
                ToolbarItem label="Add Student" icon="add" style="primary" onclick="pycromeOpenModal('add-student')"
                ToolbarItem label="Export CSV" icon="download" style="primary" onclick="export_students()"
                ToolbarItem separator=true
                ToolbarItem label="Refresh" icon="refresh" style="primary" onclick="refresh_students()"
            SearchBar id="student_search" placeholder="Search by name, class, phone..." target="students_table"
            Table source="get_students()" id="students_table" selectable="true" on_row_click="on_student_selected" paginate="true" page_size="25"
                Column field="id" label="ID"
                Column field="name" label="Full Name"
                Column field="class" label="Class"
                Column field="phone" label="Phone"
                Column field="fees_balance" label="Balance"
                Column field="status" label="Status"
            ContextMenu id="student_ctx" target="students_table"
                ContextMenuItem label="Edit Student" icon="edit" onclick="pycromeOpenModal('edit-student')"
                ContextMenuItem label="View Fees" icon="fees" onclick="navigate('fees')"
                ContextMenuItem separator=true
                ContextMenuItem label="Delete" icon="delete" style="danger" onclick="pycromeDeleteStudent()"

        Screen id="fees"
            Header title="Fee Management" subtitle="Track payments"
            Toolbar
                ToolbarItem label="Record Payment" icon="add" style="primary" onclick="pycromeOpenModal('add-payment')"
                ToolbarItem label="Export" icon="download" style="primary" onclick="export_fees()"
            SearchBar id="fees_search" placeholder="Search transactions..." target="fees_table"
            Table source="get_fee_transactions()" id="fees_table" selectable="true" paginate="true" page_size="20"
                Column field="id" label="Receipt"
                Column field="student_name" label="Student"
                Column field="amount" label="Amount (UGX)"
                Column field="payment_date" label="Date"
                Column field="payment_method" label="Method"
                Column field="status" label="Status"

    Modal id="add-student" title="Add New Student" width=600
        Form onsubmit="add_student"
            Row
                Col span=1
                    FormField id="student_name" label="Full Name" type="text" placeholder="Enter full name" required=true
                    FormField id="student_class" label="Class" type="text" placeholder="S1, S2, S3..." required=true
                    FormField id="student_phone" label="Phone Number" type="tel" placeholder="07XXXXXXXX"
                    FormField id="parent_name" label="Parent/Guardian" type="text" placeholder="Parent full name"
                Col span=1
                    FormField id="student_gender" label="Gender" type="text" placeholder="Male / Female"
                    FormField id="student_dob" label="Date of Birth" type="date"
                    FormField id="student_email" label="Email" type="email" placeholder="student@example.com"
                    FormField id="student_fees" label="Annual Fees (UGX)" type="number" value=500000
            FormActions
                Button label="Cancel" onclick="pycromeCloseModal('add-student')"
                Button label="Save Student" style="primary" type="submit"

    Modal id="add-payment" title="Record Fee Payment" width=500
        Form onsubmit="add_payment"
            FormField id="payment_student_id" label="Student ID" type="text" placeholder="STU001" required=true
            FormField id="payment_amount" label="Amount (UGX)" type="number" placeholder="0" required=true
            FormField id="payment_date" label="Payment Date" type="date" required=true
            FormField id="payment_method" label="Payment Method" type="text" placeholder="Cash / MTN / Airtel / Bank" required=true
            FormField id="payment_receipt" label="Receipt Number" type="text" placeholder="RCP001"
            FormActions
                Button label="Cancel" onclick="pycromeCloseModal('add-payment')"
                Button label="Record Payment" style="primary" type="submit"
```

---

## Inventory System

```
Window title="Inventory" width=1200 height=800 theme="dark"
    Sidebar width=220
        NavItem label="Stock" icon="package" route="stock"
        NavItem label="Orders" icon="list" route="orders"
        ThemeSwitcher
    MainPanel
        Screen id="stock" default="true"
            Header title="Stock" subtitle="Current inventory"
            Row
                StatCard label="Total Items" value="0" source="get_item_count()" icon="package" color="primary"
                StatCard label="Low Stock" value="0" source="get_low_stock_count()" icon="warning" color="danger"
                StatCard label="Stock Value" value="0" source="get_stock_value()" icon="money" color="success"
            Spacer size=16
            Toolbar
                ToolbarItem label="Add Item" icon="add" style="primary" onclick="pycromeOpenModal('add-item')"
                ToolbarItem label="Export" icon="download" style="primary" onclick="export_stock()"
            SearchBar id="stock_search" placeholder="Search items..." target="stock_table"
            Table source="get_stock()" id="stock_table" selectable="true" paginate="true" page_size="20"
                Column field="sku" label="SKU"
                Column field="name" label="Item Name"
                Column field="quantity" label="Qty"
                Column field="unit_price" label="Unit Price"
                Column field="total_value" label="Total Value"
                Column field="status" label="Status"
```

---

## Settings Screen

```
Screen id="settings"
    Header title="Settings" subtitle="Application configuration"
    Tabs id="settings_tabs"
        Tab id="general" label="General" icon="settings"
            Card
                Label text="School Information"
                FormField id="school_name" label="School Name" type="text" value="CBCentra Academy"
                FormField id="school_address" label="Address" type="text"
                FormField id="academic_year" label="Academic Year" type="text" value="2024-2025"
                FormField id="current_term" label="Current Term" type="text" value="Term 1"
                Button label="Save General Settings" style="primary" icon="save" onclick="save_general_settings()"
        Tab id="fees" label="Fee Settings" icon="fees"
            Card
                Label text="Fee Structure"
                FormField id="tuition_fee" label="Tuition Fee (UGX)" type="number" value=500000
                FormField id="late_fee" label="Late Payment Fee (UGX)" type="number" value=50000
                FormField id="exam_fee" label="Exam Fee (UGX)" type="number" value=75000
                Button label="Save Fee Settings" style="primary" icon="save" onclick="save_fee_settings()"
        Tab id="notifications" label="Notifications" icon="bell"
            Card
                Label text="Notification Preferences"
                Toggle id="email_notifications" label="Email Notifications" checked=true onchange="toggle_email_notif()"
                Toggle id="sms_notifications" label="SMS Notifications" checked=false onchange="toggle_sms_notif()"
                Toggle id="fee_reminders" label="Fee Payment Reminders" checked=true onchange="toggle_fee_reminders()"
```

---

## Multi-step Form

```
Screen id="enrollment"
    Header title="New Enrollment" subtitle="Complete all steps"
    Stepper active=0
        StepperItem label="Personal Info"
        StepperItem label="Academic Info"
        StepperItem label="Fee Setup"
        StepperItem label="Complete"
    Spacer size=24
    Card
        Label text="Step 1: Personal Information"
        FormField id="enroll_name" label="Full Name" type="text" required=true
        FormField id="enroll_dob" label="Date of Birth" type="date" required=true
        FormField id="enroll_gender" label="Gender" type="text" required=true
        FormField id="enroll_address" label="Home Address" type="text"
        Row
            Button label="Cancel" onclick="navigate('students')"
            Button label="Next: Academic Info" style="primary" icon="arrow-right" onclick="next_enrollment_step()"
```

---

## Dashboard with Charts

```
Screen id="analytics" default="true"
    Header title="Analytics" subtitle="School performance overview"
    Row
        StatCard label="Enrollment Rate" value="94%" icon="trending-up" color="success"
        StatCard label="Fee Collection" value="78%" icon="fees" color="warning"
        StatCard label="Attendance" value="89%" icon="check" color="primary"
    Spacer size=24
    Row
        Col span=2
            Chart title="Monthly Fee Collection (UGX)" type="bar" source="get_monthly_fees()" id="monthly_fees" height=280
        Col span=1
            Chart title="Student Distribution by Class" type="doughnut" source="get_class_distribution()" id="class_dist" height=280
    Spacer size=16
    Row
        Col span=1
            Card
                Label text="Fee Collection Progress by Term"
                ProgressBar label="Term 1" value="92" color="success"
                ProgressBar label="Term 2" value="78" color="warning"
                ProgressBar label="Term 3" value="45" color="danger"
        Col span=1
            Card
                Label text="Status Overview"
                Badge text="Active" style="success"
                Badge text="Pending Fees" style="warning"
                Badge text="Overdue" style="danger"
                Badge text="Graduated" style="muted"
```

---

## Backend for the Examples Above

```python
from datetime import datetime
import mysql.connector
from mysql.connector import pooling

class SchoolBackend:
    def __init__(self):
        self.pool = pooling.MySQLConnectionPool(
            pool_name="school_pool",
            pool_size=5,
            host="localhost",
            database="school_db",
            user="root",
            password="password"
        )
        self.current_student_id = None

    def _conn(self):
        return self.pool.get_connection()

    # Dashboard stats
    def get_student_count(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM students")
        count = cur.fetchone()[0]
        cur.close(); conn.close()
        return {"value": count}

    def get_fees_collected(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT SUM(amount) FROM fee_transactions WHERE status='Completed'")
        total = cur.fetchone()[0] or 0
        cur.close(); conn.close()
        return {"value": f"UGX {total:,.0f}"}

    def get_outstanding(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT SUM(fees_balance) FROM students WHERE fees_balance > 0")
        total = cur.fetchone()[0] or 0
        cur.close(); conn.close()
        return {"value": f"UGX {total:,.0f}"}

    def get_active_count(self):
        conn = self._conn()
        cur = conn.cursor()
        cur.execute("SELECT COUNT(*) FROM students WHERE status='Active'")
        count = cur.fetchone()[0]
        cur.close(); conn.close()
        return {"value": count}

    # Students
    def get_students(self):
        conn = self._conn()
        cur = conn.cursor(dictionary=True)
        cur.execute("SELECT * FROM students ORDER BY name")
        rows = cur.fetchall()
        cur.close(); conn.close()
        return rows

    def on_student_selected(self, row_data):
        self.current_student_id = row_data.get("id")
        return {"success": True, "data": row_data}

    def add_student(self, form_data):
        name = form_data.get("student_name", "").strip()
        if not name:
            return {"success": False, "message": "Student name is required."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute(
                "INSERT INTO students (name, class, phone, parent_name, fees_balance, status) VALUES (%s, %s, %s, %s, %s, 'Active')",
                (name, form_data.get("student_class"), form_data.get("student_phone"),
                 form_data.get("parent_name"), float(form_data.get("student_fees", 0)))
            )
            conn.commit()
            return {"success": True, "message": f"Student '{name}' enrolled.", "reload_table": "students_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    def delete_student(self, data=None):
        student_id = (data or {}).get("id") or self.current_student_id
        if not student_id:
            return {"success": False, "message": "No student selected."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute("DELETE FROM students WHERE id = %s", (student_id,))
            conn.commit()
            self.current_student_id = None
            return {"success": True, "message": "Student deleted.", "reload_table": "students_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    # Fees
    def get_fee_transactions(self):
        conn = self._conn()
        cur = conn.cursor(dictionary=True)
        cur.execute("SELECT * FROM fee_transactions ORDER BY payment_date DESC")
        rows = cur.fetchall()
        cur.close(); conn.close()
        return rows

    def add_payment(self, form_data):
        student_id = form_data.get("payment_student_id", "").strip()
        amount = float(form_data.get("payment_amount", 0))
        if not student_id:
            return {"success": False, "message": "Student ID is required."}
        if amount <= 0:
            return {"success": False, "message": "Amount must be greater than zero."}
        conn = self._conn()
        cur = conn.cursor()
        try:
            cur.execute(
                "INSERT INTO fee_transactions (student_id, amount, payment_date, payment_method, receipt_number, status) VALUES (%s, %s, %s, %s, %s, 'Completed')",
                (student_id, amount, form_data.get("payment_date"), form_data.get("payment_method"), form_data.get("payment_receipt"))
            )
            cur.execute("UPDATE students SET fees_paid = fees_paid + %s, fees_balance = GREATEST(0, fees_balance - %s) WHERE id = %s", (amount, amount, student_id))
            conn.commit()
            return {"success": True, "message": f"Payment of UGX {amount:,.0f} recorded.", "reload_table": "fees_table"}
        except Exception as e:
            conn.rollback()
            return {"success": False, "message": str(e)}
        finally:
            cur.close(); conn.close()

    # Charts
    def get_monthly_fees(self):
        return {
            "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
            "datasets": [{
                "label": "Fees Collected (UGX)",
                "data": [4500000, 3800000, 5200000, 4100000, 6000000, 5500000],
                "backgroundColor": "rgba(79,70,229,0.5)",
                "borderColor": "rgba(79,70,229,1)"
            }]
        }

    def get_class_distribution(self):
        return {
            "labels": ["S1", "S2", "S3", "S4", "S5", "S6"],
            "datasets": [{
                "data": [45, 42, 38, 36, 30, 28],
                "backgroundColor": [
                    "#4f46e5", "#10b981", "#f59e0b",
                    "#ef4444", "#8b5cf6", "#06b6d4"
                ]
            }]
        }
```


# ===========================PyCrome Changelog==================================================

---

## Version 1.0.0 — April 2026

Initial release. Built by Centra Holdings SMC Ltd, Uganda.

### Core Engine

- Lexer with indentation tracking and full attribute parsing
- Recursive descent parser building UINode component trees
- HTML/CSS/JS renderer with component dispatch pattern
- pywebview integration for native desktop windows using Chromium
- Python-JavaScript bridge via pywebview API
- Safe backend wrapper catching all exceptions
- Graceful error screen for parse and render failures
- Five built-in themes: dark, light, blue, green, purple
- Runtime theme switching with localStorage persistence
- Custom colour overrides directly in Window attributes

### Layout Components

- Window, Sidebar, MainPanel, Screen
- Row, Col, Card, Spacer, Divider

### Navigation Components

- NavItem, ThemeSwitcher, Navbar
- Breadcrumb, BreadcrumbItem
- Tabs, Tab

### Data Display Components

- Table with column resizing, row selection, sticky first column
- Column, Pagination
- DataGrid with inline cell editing
- StatCard with backend data loading and trend display
- Badge, ProgressBar
- Chart powered by Chart.js (bar, line, pie, doughnut)

### Input Components

- Input, SearchBar with live table filtering
- Select, Option, Textarea
- DatePicker, FileUpload with drag and drop
- Toggle

### Action Components

- Button, Toolbar, ToolbarItem
- Dropdown, DropdownItem
- ContextMenu, ContextMenuItem

### Feedback Components

- Modal with animation and form reset on close
- Toast notifications (success, error, warning, info)
- Confirmation dialog
- Form validation with inline error messages
- Loading states for buttons and containers
- Tooltip, NotificationBell
- Accordion, AccordionItem
- Stepper, StepperItem
- Spinner

### Form Components

- Form with submit handling and validation
- FormField, FormActions
- Select, Textarea

### Developer Experience

- CLI: run, new, check, preview, watch, build, init, list, version
- Hot reload via watch command
- Debug mode with browser dev tools
- PyInstaller build integration
- pycrome.config.json project configuration
- Keyboard shortcuts: Ctrl+F, Ctrl+N, Escape, Delete

---

## Roadmap

### Version 1.1.0 — Planned

- MySQL connection helper in the engine
- Export to CSV built-in backend utility
- Print support
- Notification bell dropdown panel
- Table column sorting
- Form field types: checkbox, radio group
- Sidebar brand area with logo support
- Window icon support

### Version 1.2.0 — Planned

- Multi-window support
- System tray integration
- Auto-update mechanism
- CBCentra School Management System as reference implementation
- PyCrome package on PyPI



@"
# PyCrome

[![PyPI version](https://badge.fury.io/py/pycrome.svg)](https://pypi.org/project/pycrome/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**Python DSL UI Framework for Desktop Applications**

Build beautiful, responsive desktop applications using a simple declarative language. No HTML/CSS/JavaScript knowledge required.

## 🚀 Quick Start

### Installation
```bash
pip install pycrome
