Metadata-Version: 2.4
Name: winup
Version: 1.2.1
Summary: A powerful, .exe Desktop Application Python GUI framework built on top of PySide6.
Author-email: ebaadwaheed <ebaadalt@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://github.com/mebaadwaheed/winup
Project-URL: Documentation, https://github.com/mebaadwaheed/winup/wiki
Project-URL: Source, https://github.com/mebaadwaheed/winup
Project-URL: Issues, https://github.com/mebaadwaheed/winup/issues
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: PySide6>=6.6.0
Requires-Dist: pymysql
Requires-Dist: psycopg2-binary
Requires-Dist: pymongo
Requires-Dist: firebase-admin
Requires-Dist: watchdog
Requires-Dist: click
Requires-Dist: questionary
Requires-Dist: websockets

# WinUp 🚀

**A ridiculously Pythonic and powerful framework for building beautiful desktop applications.**

WinUp is a modern UI framework for Python that wraps the power of PySide6 (Qt) in a simple, declarative, and developer-friendly API. It's designed to let you build applications faster, write cleaner code, and enjoy the development process.

---

## Why WinUp? (Instead of raw PySide6 or Tkinter)

Desktop development in Python can feel clunky. WinUp was built to fix that.

| Feature                 | WinUp Way ✨                                                                   | Raw PySide6 / Tkinter Way 😟                                                                |
| ----------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| **Layouts**             | `ui.Column(children=[...])`, `ui.Row(children=[...])`                          | `QVBoxLayout()`, `QHBoxLayout()`, `layout.addWidget()`, `pack()`, `grid()`                  |
| **Styling**             | `props={"background-color": "blue", "font-size": "16px"}`                      | Manual QSS strings, `widget.setStyleSheet(...)`, complex style objects.                     |
| **State Management**    | `state.bind(widget, "prop", "key")`                                            | Manual callback functions, getters/setters, `StringVar()`, boilerplate everywhere.          |
| **Two-Way Binding**     | `state.bind_two_way(input_widget, "key")`                                      | Non-existent. Requires manual `on_change` handlers to update state and UI.                  |
| **Developer Tools**     | **Built-in Hot Reloading**, code profiler, and window tools out of the box.    | Non-existent. Restart the entire app for every single UI change.                            |
| **Code Structure**      | Reusable, self-contained components with `@component`.                         | Often leads to large, monolithic classes or procedural scripts.                             |

**In short, WinUp provides the "killer features" of modern web frameworks (like React or Vue) for the desktop, saving you time and letting you focus on what matters: your application's logic.**

---

## Core Features

*   **Declarative & Pythonic UI:** Build complex layouts with simple `Row` and `Column` objects instead of clunky box layouts.
*   **Component-Based Architecture:** Use the `@component` decorator to create modular and reusable UI widgets from simple functions.
*   **Powerful Styling System:** Style your widgets with simple Python dictionaries using `props`. Create global "CSS-like" classes with `style.add_style_dict`.
*   **Full Application Shell:** Build professional applications with a declarative API for `MenuBar`, `ToolBar`, `StatusBar`, and `SystemTrayIcon`.
*   **Asynchronous Task Runner:** Run long-running operations in the background without freezing your UI using the simple `@tasks.run` decorator.
*   **Performance by Default:** Includes an opt-in `@memo` decorator to cache component renders and prevent needless re-computation.
*   **Advanced Extensibility:**
    *   **Widget Factory:** Replace any default widget with your own custom implementation (e.g., C++ based) using `ui.register_widget()`.
    *   **Multiple Windows:** Create and manage multiple independent windows for complex applications like tool palettes or music players.
*   **Reactive State Management:**
    *   **One-Way Binding:** Automatically update your UI when your data changes with `state.bind()`.
    *   **Two-Way Binding:** Effortlessly sync input widgets with your state using `state.bind_two_way()`.
    *   **Subscriptions:** Trigger any function in response to state changes with `state.subscribe()`.
*   **Developer-Friendly Tooling:**
    *   **Hot Reloading:** See your UI changes instantly without restarting your app.
    *   **Profiler:** Easily measure the performance of any function with the `@profiler.measure()` decorator.
    *   **Window Tools:** Center, flash, or manage your application window with ease.
*   **Flexible Data Layer:** Includes simple, consistent connectors for SQLite, PostgreSQL, MySQL, MongoDB, and Firebase.

---

## Installation

```bash
pip install winup watchdog
```
*The `watchdog` library is required for the Hot Reloading feature.*

```bash
winup init
```

*This makes an app template ready for use, if LoadUp doesn't work, use PyInstaller instead.*

---

## Getting Started: Hello, WinUp!

Creating an application is as simple as defining a main component and running it.

```python
# hello_world.py
import winup
from winup import ui

# The @component decorator is optional for the main component, but good practice.
@winup.component
def App():
    """This is our main application component."""
    return ui.Column(
        props={
            "alignment": "AlignCenter", 
            "spacing": 20
        },
        children=[
            ui.Label("👋 Hello, WinUp!", props={"font-size": "24px"}),
            ui.Button("Click Me!", on_click=lambda: print("Button clicked!"))
        ]
    )

if __name__ == "__main__":
    winup.run(main_component=App, title="My First WinUp App")
```

---

## Core Concepts

### UI & Layouts

WinUp abstracts away Qt's manual layout system. You build UIs by composing `Row` and `Column` components.

```python
def App():
    return ui.Column(  # Arranges children vertically
        children=[
            ui.Label("Top"),
            ui.Row(    # Arranges children horizontally
                children=[
                    ui.Button("Left"),
                    ui.Button("Right")
                ],
                props={"spacing": 10}
            ),
            ui.Label("Bottom")
        ],
        props={"spacing": 15, "margin": "20px"}
    )
```

### Styling

You can style any widget by passing a `props` dictionary. Props can be CSS-like properties, or special keywords like `class` and `id` for use with a global stylesheet.

```python
# Define global styles
winup.style.add_style_dict({
    ".btn-primary": {
        "background-color": "#007bff",
        "color": "white",
        "border-radius": "5px",
        "padding": "10px"
    },
    ".btn-primary:hover": {
        "background-color": "#0056b3"
    }
})

# Use the class in a component
def App():
    return ui.Button("Primary Button", props={"class": "btn-primary"})
```

### Extending Widgets

WinUp allows you to replace any default widget with your own custom class. This is perfect for creating highly specialized components or for integrating widgets written in C++.

To do this, simply create a class that inherits from the widget you want to replace (or from a base Qt class) and then register it with the framework before you run your app.

```python
# To subclass a default widget, you must import it directly
from winup.ui.widgets.button import Button as DefaultButton

# 1. Create your custom widget class
class BigRedButton(DefaultButton):
    def __init__(self, text: str, on_click: callable = None):
        # Define some custom props to make it unique
        custom_props = {
            "background-color": "red",
            "color": "white",
            "font-size": "20px",
            "font-weight": "bold",
            "padding": "15px",
        }
        super().__init__(text=text, on_click=on_click, props=custom_props)

# In your main script:
if __name__ == "__main__":
    # 2. Register your custom class to override the default "Button"
    ui.register_widget("Button", BigRedButton)
    
    # 3. Now, every call to ui.Button() will create a BigRedButton instead!
    def App():
        return ui.Button("I am a custom button!")

    winup.run(main_component=App)
```

### Traits System: Adding Behavior without Subclassing

While subclassing is great for creating new *kinds* of widgets, sometimes you just want to add a small, reusable piece of behavior to an *existing* widget—like making it draggable or giving it a right-click menu. This is where Traits come in.

Traits are modular behaviors that can be dynamically attached to any widget instance. WinUp comes with several built-in traits:

*   `draggable`: Makes a widget draggable within its parent.
*   `context_menu`: Adds a custom right-click context menu.
*   `tooltip`: A simple way to add a hover tooltip.
*   `hover_effect`: Applies a `[hover="true"]` style property on mouse-over, which you can target in your stylesheets (e.g., `QPushButton[hover="true"]`).
*   `highlightable`: Makes the text of a widget (like `ui.Label`) selectable by the user.

You can add a trait to any widget using `winup.traits.add_trait()`.

```python
# traits_demo.py
import winup
from winup import ui, traits

def App():
    # Let's create a simple label that we want to make interactive
    my_label = ui.Label(
        "I'm a draggable label with a context menu!",
        props={
            "padding": "15px",
            "background-color": "#f0f0f0",
            "border": "1px solid #ccc",
            "border-radius": "5px"
        }
    )

    # Add the draggable trait
    traits.add_trait(my_label, "draggable")

    # Add a context menu with a dictionary of actions
    traits.add_trait(my_label, "context_menu", items={
        "Say Hello": lambda: print("Hello from the context menu!"),
        "---": None, # This creates a separator
        "Reset Position": lambda: my_label.move(10, 10)
    })

    # The container needs a null layout for dragging to work relative to it
    return ui.Frame(
        children=[my_label],
        props={"layout": "null"}
    )

if __name__ == "__main__":
    winup.run(main_component=App, title="Traits Demo")
```

### State Management

WinUp's global `state` object is the single source of truth for your application's data.

**1. One-Way Binding (`bind`)**

The UI property updates automatically when `state.set()` is called.

```python
# one_way_demo.py
import winup
from winup import ui

winup.state.set("counter", 0)

def App():
    # The label's 'text' property will be kept in sync with the 'counter' state key.
    label = ui.Label(f"Initial Value: {winup.state.get('counter')}")
    winup.state.bind(label, "text", "counter")

    def increment():
        winup.state.set("counter", winup.state.get("counter") + 1)

    return ui.Column(children=[
        label,
        ui.Button("Increment", on_click=increment)
    ])
```

**2. Two-Way Binding (`bind_two_way`)**

The UI updates the state, and the state updates the UI. This is perfect for forms.

```python
# two_way_demo.py
import winup
from winup import ui

winup.state.set("username", "Guest")

def App():
    # This input is two-way bound to 'username'. Typing in the field
    # immediately updates the state.
    name_input = ui.Input()
    winup.state.bind_two_way(name_input, "username")
    
    # This label is one-way bound and will update as you type.
    greeting = ui.Label()
    winup.state.bind(greeting, "text", "username")

    return ui.Column(children=[ui.Label("Enter your name:"), name_input, greeting])
```

**3. Subscriptions (`subscribe`)**

For more complex reactions to state changes, like formatting data or triggering other logic, use `subscribe`.

```python
# subscribe_demo.py
import winup
from winup import ui

winup.state.set("username", "Guest")

def App():
    greeting = ui.Label()

    # This function runs every time the 'username' state changes.
    def update_greeting(new_name):
        greeting.set_text(f"Hello, {new_name.upper()}!")
    
    winup.state.subscribe("username", update_greeting)
    
    # We still need a way to change the state.
    name_input = ui.Input()
    winup.state.bind_two_way(name_input, "username")

    return ui.Column(children=[name_input, greeting])
```

### Multiple Windows

You are not limited to a single window. The `winup.Window` class lets you spawn new, independent windows at any time. This is ideal for things like settings dialogs, tool palettes, or mini-player controls.

The new window will have its own component and run in the same application event loop.

```python
import winup
from winup import ui

def MiniPlayerComponent():
    """A simple component for the new window."""
    return ui.Label("I'm a mini-player window!")

def open_mini_player():
    """Event handler to create and show the new window."""
    player_component = MiniPlayerComponent()
    # This creates and shows the new window instantly
    winup.Window(
        component=player_component, 
        title="Mini Player", 
        width=250, 
        height=100
    )

def App():
    """The main app component."""
    return ui.Button("Open Player", on_click=open_mini_player)

if __name__ == "__main__":
    winup.run(main_component=App)
```

### Application Shell

WinUp provides simple, declarative classes to build the shell of a professional application. You can define menus, toolbars, and status bars and pass them directly to the `winup.run()` function.

```python
import winup
from winup import ui, shell

# 1. Define handlers for your actions
def on_new(): print("Action: New")
def on_quit(): winup.core.window._winup_app.app.quit()
def on_about(): winup.ui.dialogs.show_message("About", "WinUp Shell Demo")

# 2. Define the shell components
app_menu = shell.MenuBar({
    "&File": { "New": on_new, "---": None, "Quit": on_quit },
    "&Help": { "About": on_about }
})

app_toolbar = shell.ToolBar({ "New": on_new }) # Add icons via the icon_dir argument
app_statusbar = shell.StatusBar()

# 3. Create your main component
def App():
    # The status bar is globally accessible after creation
    shell.StatusBar.show_message("Welcome to WinUp!", 5000)
    return ui.Label("App with a full shell!")

# 4. Pass the shell components to the run function
if __name__ == "__main__":
    winup.run(
        main_component=App,
        title="App Shell Demo",
        menu_bar=app_menu,
        tool_bar=app_toolbar,
        status_bar=app_statusbar
    )
```
*You can also add a `shell.SystemTrayIcon` for applications that need to run in the background.*

### Asynchronous Tasks

Never freeze your UI again. The `@tasks.run` decorator makes it trivial to run any function on a background thread, with callbacks for success or failure.

```python
import time
from winup import shell, tasks

def on_task_complete(result):
    """This function is called on the main UI thread when the task succeeds."""
    print(f"Success! Result: {result}")
    shell.StatusBar.show_message(f"Task finished: {result}", 4000)

def on_task_error(error_details):
    """This function is called on the main UI thread if the task fails."""
    exception, trace = error_details
    print(f"Error in background task: {exception}")
    shell.StatusBar.show_message(f"Error: {exception}", 4000)

@tasks.run(on_finish=on_task_complete, on_error=on_task_error)
def fetch_data_from_server(url: str):
    """
    A simulated long-running task. This will not block the UI.
    The decorator will pass its return value to 'on_finish'.
    """
    print("Starting background task...")
    shell.StatusBar.show_message("Fetching data...")
    time.sleep(2) # Simulate network latency
    if "fail" in url:
        raise ConnectionError("Could not connect to server.")
    return f"Data from {url}"

# You can now call this function from any event handler (e.g., a button click)
# fetch_data_from_server("my-api.com/data")
# fetch_data_from_server("my-api.com/fail")
```

### Developer Tools

**Hot Reloading:**
To enable hot reloading, you manually start a watcher that calls a reload function. This gives you precise control over what gets reloaded.

```python
# hot_reload_example.py
import winup
from winup import ui
from winup.core import hot_reload

# 1. Define your component(s) in a separate file (e.g., components.py)
#
# --- components.py ---
# from winup import ui
# def MyComponent():
#     return ui.Label("Version 1 of my component")
# ---------------------

# 2. In your main app file, create a placeholder and a reload function
app_container = ui.Frame() # A container to hold the component

def reload_ui():
    """This function clears the container and re-imports the component."""
    hot_reload.clear_layout(app_container.layout())
    # The reloader invalidates Python's import cache
    from components import MyComponent 
    app_container.add_child(MyComponent())
    print("UI Reloaded!")

if __name__ == "__main__":
    # 3. Start the hot reloader before running the app
    # It will watch 'components.py' and call 'reload_ui' when it changes.
    reloader = hot_reload.FileChangeReloader('components.py', reload_ui)
    reloader.start()

    # 4. Run the app with the container, and load the initial UI
    reload_ui() # Initial load
    winup.run(main_component=lambda: app_container, title="Hot Reload App")
```
*This setup allows you to see UI changes instantly just by saving your component file.*

**Performance & Memoization:**
For UIs that render large amounts of data, you can significantly improve performance by caching component results. The `@winup.memo` decorator automatically caches the widget created by a component. If the component is called again with the same arguments, the cached widget is returned instantly instead of being re-created.

```python
import winup
from winup import ui

# By adding @winup.memo, this component will only be re-created
# if the 'color' argument changes.
@winup.memo
def ColorBlock(color: str):
    return ui.Frame(props={"background-color": color, "min-height": "20px"})

def App():
    # In this list, ColorBlock('#AABBCC') will only be called once.
    # The framework will then reuse the cached widget for the other two instances.
    return ui.Column(children=[
        ColorBlock(color="#AABBCC"),
        ColorBlock(color="#EEEEEE"),
        ColorBlock(color="#AABBCC"),
        ColorBlock(color="#AABBCC"),
    ])
```

**Profiler:**
Simply add the `@profiler.measure()` decorator to any function to measure its execution time. Results are printed to the console when the application closes.
The profiler also automatically tracks the performance of the memoization cache, showing you hits, misses, and the overall hit ratio.

```python
from winup.tools import profiler

@profiler.measure
def some_expensive_function():
    # ... code to measure ...
    import time
    time.sleep(1)
```

---

## Contributing

WinUp is an open-source project. Contributions are welcome!

## License

This project is licensed under the Apache 2.0 License. 
