wijjit.core.app.Wijjit

class wijjit.core.app.Wijjit(initial_state=None, template_dir=None, enable_mouse=None, debug=None, backend=None, **config_overrides)[source]

Main Wijjit application class.

The Wijjit class is the central orchestrator for TUI applications. It provides a Flask-like API with view decorators and manages all framework components.

Parameters:
  • initial_state (dict[str, Any] or None) – Initial state dictionary (default: empty dict)

  • template_dir (str | None)

  • enable_mouse (bool | None)

  • debug (bool | None)

  • backend (TerminalBackend | None)

Variables:
  • config (Config) – Application configuration (Flask-like dict)

  • state (State) – Application state with reactive updates

  • renderer (Renderer) – Jinja2 template renderer

  • focus_manager (FocusManager) – Focus management for interactive elements

  • hover_manager (HoverManager) – Hover state management for mouse interaction

  • handler_registry (HandlerRegistry) – Event handler registry and dispatcher

  • screen_manager (ScreenManager) – Terminal screen management

  • input_handler (InputHandler) – Keyboard and mouse input handling

  • views (Dict[str, ViewConfig]) – Registered views

  • current_view (str or None) – Name of current view

  • running (bool) – Whether the app is currently running

  • needs_render (bool) – Whether a re-render is needed

Examples

Basic usage:

>>> app = Wijjit()
>>> app.config['ENABLE_MOUSE'] = False
>>> @app.view("main", default=True)
... def main_view():
...     return {"template": "Hello, World!"}
>>> app.run()

Load configuration from file:

>>> app = Wijjit()
>>> app.config.from_pyfile('config.py')
>>> app.run()

Configure via environment variables:

>>> # export WIJJIT_DEBUG=1
>>> # export WIJJIT_ENABLE_MOUSE=0
>>> app = Wijjit()  # Auto-loads WIJJIT_* env vars
>>> app.run()
__init__(initial_state=None, template_dir=None, enable_mouse=None, debug=None, backend=None, **config_overrides)[source]

Initialize Wijjit application.

Parameters:
  • initial_state (dict[str, Any] or None) – Initial state dictionary

  • template_dir (str or None) – Template directory path (convenience parameter for TEMPLATE_DIR config). When omitted, Wijjit auto-discovers a templates/ directory next to the module that constructs the app (Flask’s convention); pass this to point somewhere else. If neither is set and no templates/ directory exists, the app renders inline templates only and wijjit.render_template() is unavailable.

  • enable_mouse (bool or None) – Enable mouse support (convenience parameter for ENABLE_MOUSE config)

  • debug (bool or None) – Enable debug mode (convenience parameter for DEBUG config)

  • backend (TerminalBackend or None) – Terminal transport for the app. When None (default) a LocalTerminalBackend is used, running against the local console exactly as before. Supply a custom backend to drive the app over a different transport (for example an SSH channel). See TerminalBackend.

  • **config_overrides – Additional config overrides (e.g., quit_key=’q’, log_level=’DEBUG’)

Return type:

None

Notes

Configuration is managed via app.config. Use app.config.from_pyfile(), app.config.from_object(), or app.config.update() to configure the app. Environment variables with WIJJIT_ prefix are automatically loaded.

Convenience parameters (template_dir, enable_mouse, debug) and config_overrides take precedence over environment variables and defaults.

Methods

__init__([initial_state, template_dir, ...])

Initialize Wijjit application.

bind_focus_key(key, element_id[, priority])

Bind a key so that pressing it moves focus to a named element.

close_overlay([overlay])

Close an overlay opened by show_modal/show_dropdown/show_tooltip.

configure_focus([enabled])

Configure focus navigation behavior.

dismiss_notification(notification_id)

Manually dismiss a notification.

focus_element_by_id(element_id)

Move keyboard focus to a focusable element by its id.

get_element_by_id(element_id)

Find a rendered element by its id.

navigate(view_name, **params)

Navigate to a different view (delegates to ViewRouter).

notify(message[, severity, duration, ...])

Show a notification message.

on(event_type[, callback, scope, view_name, ...])

Register an event handler, directly or as a decorator.

on_action(action_id)

Decorator to register an action handler.

on_key(key[, allow_ctrl_q, priority])

Decorator to register a key press handler.

quit()

Quit the application (delegates to EventLoop).

refresh()

Force a re-render on the next loop iteration.

register_completer(name, completer)

Register an autocomplete completer by name.

register_element(type_name, element_cls, *)

Register a third-party element on this app, including a live one.

request_full_repaint()

Force the next render to repaint the entire screen.

run()

Run the application (delegates to EventLoop).

show_dropdown(element, x, y[, on_close, ...])

Show a dropdown menu or context menu overlay.

show_modal(element[, on_close, ...])

Show a modal dialog overlay.

show_tooltip(element, x, y[, ...])

Show a tooltip overlay.

unregister_key(key)

Unregister a key handler.

view(name[, default, on_enter, on_exit])

Decorator to register a view (delegates to ViewRouter).

Attributes

current_view

Get current view name (delegates to ViewRouter).

running

Whether the application's event loop is currently running.

views

Get registered views (delegates to ViewRouter).

property views: dict[str, ViewConfig]

Get registered views (delegates to ViewRouter).

Returns:

Dictionary of registered views

Return type:

dict[str, ViewConfig]

property current_view: str | None

Get current view name (delegates to ViewRouter).

Returns:

Name of current view

Return type:

str or None

view(name, default=False, on_enter=None, on_exit=None)[source]

Decorator to register a view (delegates to ViewRouter).

The decorated function returns what to render. Prefer the Flask-style helpers wijjit.render_template_string() / wijjit.render_template(), which package the template and its (live) context:

@app.view("main", default=True, on_enter=setup)
def main_view():
    return render_template_string(TEMPLATE, title="Home")

The view function is re-invoked on every render, so any context it computes stays live. The legacy {"template": ..., "data": {...}} dict return is still accepted; note that values placed in a static data dict are frozen at the first render (use the context kwargs above, or a data callable, for values that must update).

Parameters:
  • name (str) – Unique name for this view

  • default (bool) – Whether this is the default view (default: False)

  • on_enter (callable, optional) – Hook called when navigating to this view (sync or async). Takes precedence over an on_enter key in a legacy dict return.

  • on_exit (callable, optional) – Hook called when navigating away from this view (sync or async). Takes precedence over an on_exit key in a legacy dict return.

Returns:

Decorator function

Return type:

Callable

Examples

Inline template (preferred):

>>> @app.view("main", default=True)
... def main_view():
...     return render_template_string("Hello, {{ name }}!", name="World")

Load from a file:

>>> @app.view("dashboard")
... def dashboard_view():
...     return render_template("dashboard.wij.j2", stats=get_stats())
navigate(view_name, **params)[source]

Navigate to a different view (delegates to ViewRouter).

Calls on_exit hook of current view, switches to new view, and calls on_enter hook of new view.

Parameters:
  • view_name (str) – Name of view to navigate to

  • **params – Parameters to pass to the view’s data function

Raises:

ValueError – If view_name doesn’t exist

Return type:

None

on(event_type, callback=None, scope=HandlerScope.GLOBAL, view_name=None, element_id=None, priority=0)[source]

Register an event handler, directly or as a decorator.

Called with a callback it registers immediately and returns it; called without one it returns a decorator, matching the @app.on_key / @app.on_action decorator style.

Parameters:
  • event_type (EventType) – Type of event to handle

  • callback (Callable[[Event], None] or None) – Function to call when event occurs. Omit to use as a decorator.

  • scope (HandlerScope) – Scope at which handler operates (default: GLOBAL)

  • view_name (str or None) – View name for view-scoped handlers

  • element_id (str or None) – Element ID for element-scoped handlers

  • priority (int) – Handler priority (higher = earlier, default: 0)

Returns:

The registered callback (direct form), or a decorator that registers and returns the callback it wraps (decorator form).

Return type:

Callable

Examples

Direct registration:

app.on(EventType.KEY, handler)

As a decorator:

@app.on(EventType.KEY)
def handler(event):
    ...
on_key(key, allow_ctrl_q=False, priority=0, **kwargs)[source]

Decorator to register a key press handler.

Use this to handle specific key presses globally in your application. This is sugar over the event system - internally it registers a KEY event handler that filters by the specified key.

Parameters:
  • key (str) – The key to handle (e.g., “d”, “q”, “enter”, “space”)

  • allow_ctrl_q (bool, optional) – Allow binding to Ctrl+Q (default: False). Use with caution as Ctrl+Q is normally reserved for exiting the application.

  • priority (int, optional) – Handler priority (higher = earlier execution, default: 0)

Returns:

Decorator function

Return type:

Callable

Raises:

ValueError – If attempting to bind Ctrl+Q without allow_ctrl_q=True

Notes

This method is implemented as sugar over the event system. Internally, it calls self.on(EventType.KEY, …) with a wrapper that filters by key name. This centralizes event handling, simplifies the mental model, and ensures consistent priority/cancellation behavior.

Examples

>>> @app.on_key("d")
... def delete_handler(event):
...     print("Delete key pressed!")
>>> @app.on_key("ctrl+q", allow_ctrl_q=True)
... def custom_exit(event):
...     print("Custom Ctrl+Q handler")
>>> @app.on_key("s", priority=10)
... def save_handler(event):
...     # This handler runs before lower-priority handlers
...     print("Saving...")
unregister_key(key)[source]

Unregister a key handler.

Parameters:

key (str) – Key combination to unregister (e.g., ‘ctrl+s’, ‘f1’)

Returns:

True if handler was found and removed, False otherwise

Return type:

bool

Examples

>>> @app.on_key("ctrl+s")
... def save_handler(event):
...     pass
>>> app.unregister_key("ctrl+s")  # Returns True
True
get_element_by_id(element_id)[source]

Find a rendered element by its id.

Parameters:

element_id (str) – The id assigned to the element in the template or constructor.

Returns:

The matching element from the most recent render, or None if no positioned element has that id. Only available after the first render populates positioned_elements.

Return type:

Element or None

focus_element_by_id(element_id)[source]

Move keyboard focus to a focusable element by its id.

Parameters:

element_id (str) – The id of the element to focus.

Returns:

True if a focusable element with that id was found and focused, False otherwise (unknown id, not yet rendered, or the element is not focusable).

Return type:

bool

bind_focus_key(key, element_id, priority=0)[source]

Bind a key so that pressing it moves focus to a named element.

This is a convenience wrapper over on_key() and focus_element_by_id() for the common “jump to this field/panel” keyboard shortcut.

Parameters:
  • key (str) – The key to bind (e.g. "f", "ctrl+l"). Ctrl+Q is reserved.

  • element_id (str) – The id of the element to focus when the key is pressed.

  • priority (int, optional) – Handler priority (higher runs earlier, default: 0).

Returns:

The registered handler function (so it can be referenced if needed).

Return type:

Callable

Examples

>>> app.bind_focus_key("ctrl+l", "search_box")
on_action(action_id)[source]

Decorator to register an action handler.

Use this to handle actions from buttons and other interactive elements that have an ‘action’ attribute in templates.

Parameters:

action_id (str) – The action ID to handle

Returns:

Decorator function

Return type:

Callable

Examples

>>> @app.on_action("submit")
... def handle_submit(event):
...     print("Form submitted!")
configure_focus(enabled=True)[source]

Configure focus navigation behavior.

By default, Tab/Shift+Tab navigation is automatically enabled when template contains focusable elements. Use this method to disable or re-enable focus navigation.

Parameters:

enabled (bool) – Whether to enable Tab/Shift+Tab navigation (default: True)

Return type:

None

Examples

>>> app.configure_focus(enabled=False)  # Disable Tab navigation
property running: bool

Whether the application’s event loop is currently running.

Returns:

True from the moment the event loop enters its frame loop until it stops (quit, Ctrl+Q, or teardown). False before run() and after shutdown.

Return type:

bool

Notes

Delegates to EventLoop.running, so background worker threads can poll app.running to decide when to stop pushing updates. Note the startup race: a thread started before run() observes False until the loop actually starts, so gate worker loops on an explicit threading.Event rather than on this flag alone.

quit()[source]

Quit the application (delegates to EventLoop).

Sets the running flag to False, which will exit the event loop.

Return type:

None

refresh()[source]

Force a re-render on the next loop iteration.

Return type:

None

run()[source]

Run the application (delegates to EventLoop).

Enters the main event loop: 1. Render initial view 2. Loop: read input -> dispatch events -> re-render if needed 3. Exit on quit or Ctrl+Q

The loop continues until quit() is called or the user presses Ctrl+Q.

Return type:

None

show_modal(element, on_close=None, dim_background=True, close_on_escape=True, close_on_click_outside=False)[source]

Show a modal dialog overlay.

Modals trap focus and typically dim the background. They are used for important interactions that require user attention.

Parameters:
  • element (Element) – The element to display as a modal (typically a Frame with content)

  • on_close (Callable, optional) – Callback to invoke when modal is closed

  • dim_background (bool) – Whether to dim the background behind the modal (default: True)

  • close_on_escape (bool) – Whether ESC key closes the modal (default: True)

  • close_on_click_outside (bool) – Whether clicking outside closes the modal (default: False)

Returns:

The created overlay object. Can be used to manually close via overlay_manager.pop(overlay)

Return type:

Overlay

Examples

Show a confirmation dialog:

from wijjit import ConfirmDialog

def on_confirm():
    state.file_deleted = True

dialog = ConfirmDialog(
    title="Confirm Delete",
    message="Are you sure?",
    on_confirm=on_confirm,
)
app.show_modal(dialog)
show_dropdown(element, x, y, on_close=None, close_on_click_outside=True, close_on_escape=True)[source]

Show a dropdown menu or context menu overlay.

Dropdowns appear at a specific position (typically below a button or at cursor position for context menus). They close when clicking outside or pressing ESC.

Parameters:
  • element (Element) – The element to display as a dropdown (typically a Menu)

  • x (int) – X position for the dropdown

  • y (int) – Y position for the dropdown

  • on_close (Callable, optional) – Callback to invoke when dropdown is closed

  • close_on_click_outside (bool) – Whether clicking outside closes the dropdown (default: True)

  • close_on_escape (bool) – Whether ESC key closes the dropdown (default: True)

Returns:

The created overlay object

Return type:

Overlay

Examples

Show a dropdown menu below a button:

from wijjit.elements.menu import DropdownMenu, MenuItem

button = app.get_element_by_id("menu_button")
menu = DropdownMenu(items=[
    MenuItem(label="Open", action="open"),
    MenuItem(label="Save", action="save"),
])

app.show_dropdown(
    menu,
    x=button.bounds.x,
    y=button.bounds.y + button.bounds.height
)
show_tooltip(element, x, y, close_on_click_outside=True)[source]

Show a tooltip overlay.

Tooltips appear at a specific position (typically near the cursor or element being hovered). They don’t trap focus and typically close automatically when the mouse moves.

Parameters:
  • element (Element) – The element to display as a tooltip (typically a small Frame)

  • x (int) – X position for the tooltip

  • y (int) – Y position for the tooltip

  • close_on_click_outside (bool) – Whether clicking outside closes the tooltip (default: True)

Returns:

The created overlay object

Return type:

Overlay

Notes

Tooltips don’t close on ESC by default since they’re meant to be unobtrusive and auto-close on mouse movement.

Examples

Show a tooltip on hover:

from wijjit import Frame

tooltip = Frame(width=30, height=3)
app.show_tooltip(tooltip, x=mouse_x + 1, y=mouse_y + 1)
close_overlay(overlay=None)[source]

Close an overlay opened by show_modal/show_dropdown/show_tooltip.

The public counterpart to those show_* methods, so callers do not have to reach into overlay_manager to dismiss what they opened. With no argument, closes the topmost overlay.

Parameters:

overlay (Overlay or None) – The overlay returned by a show_* call, or None to close the topmost overlay.

Returns:

The closed overlay, or None if there was nothing to close.

Return type:

Overlay or None

notify(message, severity='info', duration=<object object>, action=None, dismiss_on_action=True, bell=False)[source]

Show a notification message.

Notifications are temporary messages that appear in a corner of the screen. They auto-dismiss after a duration and can optionally include an action button.

Parameters:
  • message (str) – Notification message text

  • severity (str, optional) – Severity level: “success”, “error”, “warning”, or “info” (default: “info”)

  • duration (float or None, optional) – Duration in seconds before auto-dismiss. If left unspecified, falls back to the NOTIFICATION_DURATION config value (default 3.0). Set to None explicitly for no auto-dismiss.

  • action (tuple of (str, callable), optional) – Optional action button as (label, callback) tuple

  • dismiss_on_action (bool, optional) – Whether to auto-dismiss when action is clicked (default: True)

  • bell (bool, optional) – Whether to play a terminal bell sound (default: False)

Returns:

Notification ID for manual dismissal via dismiss_notification()

Return type:

str

Examples

Show a success notification:

app.notify("File saved successfully!", severity="success")

Show an error with an action button:

def retry()::

    # Retry logic
    pass

app.notify(
    "Connection failed",
    severity="error",
    action=("Retry", retry),
    duration=5.0
)

Show a persistent notification with sound:

app.notify(
    "Update available",
    severity="info",
    duration=None,  # Won't auto-dismiss
    bell=True
)
dismiss_notification(notification_id)[source]

Manually dismiss a notification.

Parameters:

notification_id (str) – ID returned by notify()

Returns:

True if notification was dismissed, False if not found

Return type:

bool

Examples

>>> notification_id = app.notify("Processing...", duration=None)
>>> # ... do work ...
>>> app.dismiss_notification(notification_id)
register_completer(name, completer)[source]

Register an autocomplete completer by name.

Completers registered here can be referenced in templates using the autocomplete attribute on text inputs.

Parameters:
  • name (str) – Name to register the completer under. Use “#element_id” to auto-wire to specific elements with that ID.

  • completer (Completer) – The completer instance to register.

Return type:

None

Examples

Register a word completer for a specific element:

>>> from wijjit.autocomplete import WordCompleter
>>> app.register_completer("#search", WordCompleter(["apple", "banana"]))

Register a named completer that can be used by multiple inputs:

>>> app.register_completer("fruits", WordCompleter(["apple", "banana"]))

Then in template:

>>> {% textinput id="search" autocomplete=True %}{% endtextinput %}
>>> {% textinput id="fruit" autocomplete="fruits" %}{% endtextinput %}
register_element(type_name, element_cls, *, tag=None, aliases=(), extension=None, override=False)[source]

Register a third-party element on this app, including a live one.

This is the imperative escape hatch for the normal wijjit.register_element() seam. It records the plugin in the process-global registry and live-patches this app’s already-built renderer (its element registry and Jinja environment), so an element can be added after Wijjit(...) has constructed the renderer - which the module-level function alone cannot do, since each renderer only drains the global registry at construction.

For a plugin shipped as a separate package, prefer the module-level wijjit.register_element() (or the wijjit.plugins entry point); use this method for dynamic, in-process registration.

Parameters:
  • type_name (str) – The VNode type string for the element.

  • element_cls (type) – The Element subclass to instantiate.

  • tag (str, optional) – Template tag name to synthesize (see wijjit.plugins.register_element()).

  • aliases (sequence of str, optional) – Extra type-name aliases.

  • extension (type, optional) – A hand-written Jinja Extension class to use instead of a synthesized one.

  • override (bool, optional) – Allow shadowing a built-in / existing registration.

request_full_repaint()[source]

Force the next render to repaint the entire screen.

Diff rendering emits only the cells that changed since the last frame, so anything written to the terminal out-of-band - a foreign library’s stdout, a subprocess, a stray print - is invisible to the differ and persists until something forces a full redraw. Call this after knowingly writing to the terminal to overwrite those bytes on the next frame. Also drives the optional FULL_REPAINT_INTERVAL heartbeat.

Return type:

None