batgrl Documentation

repository·main·Indexed 20 days ago

https://github.com/salt-die/batgrl

A terminal graphics library for creating visually rich terminal applications, including games and simulations. It features a hierarchical gadget tree structure for managing UI components, supporting responsive layouts via size and position hints, and an asynchronous event loop for handling input and continuous game logic. The library includes specialized gadget types such as Pane, Graphics, and Text, and provides built-in collision detection and event dispatching.

Tokens
14.7K
Snippets
59
Records
78
Agent score
69%

What's inside batgrl

  1. Overview of batgrl

    main
    batgrl is a 'badass terminal graphics library' designed for creating high-fidelity terminal-based experiences. It is suitable for building games (like Tetris), simulations, and feature-filled terminal applications.
  2. Control Gadget Rendering order

    main

    Gadgets are drawn based on their position in the tree.

    • Layering: Children are drawn on top of their parents.
    • Sibling Order: Siblings are drawn in the order they appear in the children list.
    • Z-Index Management: To move a gadget to the front (ensuring it is drawn after all its siblings), use the pull_to_front() method.
  3. Use Size and Position hints for responsive layouts

    main

    Size and position hints allow gadgets to scale or reposition themselves relative to their parent if the parent is resized.

    • size_hint: A dictionary containing keys like height_hint or width_hint (values from 0.0 to 1.0).
    • pos_hint: A dictionary containing keys like x_hint or y_hint (values from 0.0 to 1.0).

    Example: A divider that always takes up the full height of its parent and stays centered horizontally.

    divider = Pane(
        size=(1, 1),
        size_hint={"height_hint": 1.0},
        pos_hint={"x_hint": 0.5},
        bg_color=BLUE,
    )
  4. Add gadgets to an application

    main

    An application in batgrl is a tree of gadgets. The root gadget automatically matches the terminal's current size. Use self.add_gadget(gadget) within an App instance to add a gadget to the root. To add multiple gadgets to a specific parent gadget, use the add_gadgets(*gadgets) method.

    # Adding to the app root
    self.add_gadget(game_field)
    
    # Adding multiple gadgets to a parent
    game_field.add_gadgets(left_paddle, right_paddle, divider)
  5. Understand Gadget attributes and types

    main

    Gadgets are the fundamental building blocks of a batgrl application. Every gadget possesses a size, a position, and a tree of children.

    Key attributes that control gadget behavior include:

    • is_transparent: Determines if gadgets positioned beneath this gadget are rendered.
    • is_visible: Determines if the gadget itself is rendered.
    • is_enabled: Determines if input events are dispatched to this gadget.

    Common gadget types include:

    • Pane: A gadget with a background color. Use the alpha attribute to control its transparency.
    • Graphics: A gadget used for rendering arbitrary RGBA textures.
    • Text: A general-purpose gadget representing state as an array of cells. Each cell contains terminal attributes like character, bold status, and foreground color.
  6. Handle Input Dispatching and Event Handlers

    main

    Input events are dispatched across the entire gadget tree using a specific traversal order.

    Dispatch Order: Events are dispatched to children in reversed order first. For a tree where Root has children A and B, and A has children a and b, and B has child c, the dispatch order is: c $\rightarrow$ B $\rightarrow$ b $\rightarrow$ a $\rightarrow$ A $\rightarrow$ Root.

    Event Bubbling/Stopping: If any gadget's event handler returns True, the dispatching process stops immediately, preventing the event from reaching other gadgets or ancestors.

    Available Event Handlers: Implement these methods in your gadget to respond to input:

    • on_key(): Handles key events.
    • on_mouse(): Handles mouse events.
    • on_paste(): Handles paste events.
    • on_terminal_focus(): Handles terminal focus events.
  7. Understand the Gadget Tree structure

    main

    In batgrl, application components (gadgets) are organized in a hierarchical tree structure.

    • The Root: A special gadget that automatically matches the terminal's size. It serves as the base of the tree.
    • Hierarchy: Gadgets can have children, forming a parent-child relationship. Any gadget within the tree can access the top-level gadget via its root property.
    • Lifecycle Hooks: When a gadget is added to the tree (ensuring a path to the root exists), the on_add() method is triggered. When a gadget is removed, on_remove() is triggered.
  8. Configure Gadget size and position hints

    main

    You can control how gadgets scale and position themselves relative to their parents using hints:

    • Size Hints: If a gadget has a non-None size hint, its size will be a proportion of its parent's size. The gadget automatically updates when the parent is resized.
    • Position Hints: If a gadget has a non-None position hint, it will position itself at a specific proportion of its parent's size.
    • Anchoring: Use the anchor attribute to specify which point within the gadget aligns with the position hint. The default value is "center".
  9. Initialize a batgrl application

    main

    To create a basic application, subclass batgrl.app.App and implement the on_start async method. The on_start method is the entry point for adding gadgets to your application and scheduling tasks. Use the .run() method on your app instance to start the event loop.

    from batgrl.app import App
    
    
    class Pong(App):
        async def on_start(self):
            # Add gadgets and schedule tasks here
            pass
    
    
    if __name__ == "__main__":
        Pong().run()
  10. Run continuous game loops using Async

    main

    Because on_start() is an async method, you can implement a continuous game loop directly within it using a while True loop. To prevent blocking the rest of the application's event loop (like input handling), you must await asyncio.sleep(delay) inside the loop.

    import asyncio
    
    class Pong(App):
        async def on_start(self):
            # ... setup gadgets ...
            speed = 0.04
            while True:
                # ... update logic ...
                await asyncio.sleep(speed)
  11. Controls for the 3-D Rubik's Cube example

    main

    When running the Rubik's Cube example, use the following keyboard and mouse inputs to interact with the cube:

    • Plane Rotation:
      • r: Rotate the selected plane counter-clockwise.
      • R: Rotate the selected plane clockwise.
    • Navigation:
      • up / down: Change the selected planes.
      • left / right: Change the selected axis.
    • Cube Actions:
      • s: Scramble the cube.
    • Camera/View:
      • Drag mouse: Rotate the entire cube view.
  12. Create a terminal application by subclassing App

    main

    To build a terminal application, subclass batgrl.App and implement the on_start() coroutine. The on_start() method is called when the application begins running and is the place to initialize your gadget tree. Use self.add_gadget(gadget) or self.add_gadgets(*gadgets) to populate the application's UI.

    Key configuration options for the App constructor include:

    • fg_color: Foreground color of the root gadget. If None, it queries the terminal.
    • bg_color: Background color of the root gadget. If None, it queries the terminal.
    • title: The terminal's title.
    • inline: If True, renders the app inline in the current buffer; otherwise, uses the alternate screen.
    • inline_height: Height of the app if inline is True.
    • color_theme: A ColorTheme for gadgets implementing Themable.
    • double_click_timeout: Max duration (seconds) for a double-click.
    • render_interval: Duration (seconds) between consecutive frame renders (set to 0.0 for default behavior).
    from batgrl import App, Gadget
    
    class MyApp(App):
        async def on_start(self):
            # Initialize your gadgets here
            self.add_gadget(MyGadget())
    
    if __name__ == "__main__":
        app = MyApp(title="My Awesome App", inline=True)
        app.run()