Skip to content

scenex.app#

scenex.app #

Application and GUI framework abstraction layer.

This module provides a unified interface for managing GUI applications across different frameworks (Qt, WxPython, Jupyter) and rendering backends. It handles the event loop, window creation, and platform-specific details, allowing scenex to work seamlessly across desktop and web environments.

The app abstraction decouples scenex from specific GUI frameworks, making it possible to switch between Qt widgets, WxPython windows, or Jupyter notebook outputs without changing your visualization code.

Main Components
  • App: Abstract base class for GUI applications
  • GuiFrontend: Enumeration of supported GUI frameworks (Qt, WxPython, Jupyter)
  • app(): Factory function that returns the active application instance
  • determine_app(): Auto-detect which GUI framework to use
Supported Frontends

Qt (PyQt6, PySide6) WxPython Jupyter

Examples:

The app is typically created automatically by scenex.show() and/or scenex.run():

>>> import scenex as snx
>>> import numpy as np
>>> # Create a scenex scene
>>> my_array = np.random.rand(100, 100).astype(np.float32)
>>> my_scene = snx.Scene(children=[snx.Image(data=my_array)])
>>> # Showing the scene creates the app if needed
>>> snx.show(my_scene)
Canvas(...)
>>> snx.run()  # Starts the event loop

But it CAN be useful to access the app instance directly. For example, it can be useful to ask the app to process any pending events:

>>> from scenex.app import app
>>> app().process_events()
Notes

This module is designed to be cleanly extractable to a separate library if needed. It fully encapsulates GUI framework logic and event loop management.

See Also

scenex.run : Convenience function to start the event loop scenex.show : Creates and displays visualizations

Modules:

  • events

    Event system for handling user input and interaction.

Classes:

  • App

    Base class for GUI application wrappers.

  • CursorType

    Enumeration of standard cursor types for canvas interaction.

  • GuiFrontend

    Enum of available GUI frontends.

Functions:

  • app

    Get the active GUI application instance.

  • determine_app

    Determine which GUI backend to use for the application.

  • ensure_main_thread

    Decorator that ensures a function is called in the main GUI thread.

App #

Bases: ABC


              flowchart TD
              scenex.app.App[App]

              

              click scenex.app.App href "" "scenex.app.App"
            

Base class for GUI application wrappers.

App provides an abstract interface for integrating scenex with different GUI frameworks (Qt, WxPython, Jupyter). Each GUI backend implements this interface to provide framework-specific application lifecycle management, event handling, and threading operations.

The App class is typically accessed via the app() function, which automatically determines and initializes the appropriate backend based on the environment and available GUI frameworks.

Notes

This is an abstract base class. Concrete implementations are provided by backend-specific subclasses (QtAppWrap, WxAppWrap, JupyterAppWrap).

See Also

app : Function to get the active application instance GuiFrontend : Enumeration of available GUI backends determine_app : Function to determine which GUI backend to use

Methods:

  • call_in_main_thread

    Schedule a function to be called in the main GUI thread.

  • call_later

    Schedule a function to be called after a delay.

  • create_app

    Create the application instance, if not already created.

  • get_executor

    Return an executor for running tasks in background threads.

  • install_event_filter

    Install an event filter on a native widget to forward events to a handler.

  • process_events

    Yields the current thread to process all pending GUI events.

  • run

    Start the application event loop.

  • set_cursor

    Set the cursor on a native canvas widget.

  • show

    Show or hide a native canvas widget.

call_in_main_thread #

call_in_main_thread(func: Callable[P, T], *args: args, **kwargs: kwargs) -> Future[T]

Schedule a function to be called in the main GUI thread.

Many GUI frameworks require that widget operations occur on the main thread. This method safely schedules a function call on the main thread and returns a Future that will contain the result.

Parameters:

  • func #

    (Callable[P, T]) –

    The function to call.

  • *args #

    (args, default: () ) –

    Positional arguments to pass to func.

  • **kwargs #

    (kwargs, default: {} ) –

    Keyword arguments to pass to func.

Returns:

  • Future[T]

    A Future object that will contain the function's return value once the call completes.

Notes

The base implementation executes the function immediately and returns a completed Future. Subclasses should override this to provide thread-safe execution.

Source code in src/scenex/app/_auto.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
def call_in_main_thread(
    self, func: Callable[P, T], *args: P.args, **kwargs: P.kwargs
) -> Future[T]:
    """Schedule a function to be called in the main GUI thread.

    Many GUI frameworks require that widget operations occur on the main
    thread. This method safely schedules a function call on the main thread
    and returns a Future that will contain the result.

    Parameters
    ----------
    func : Callable[P, T]
        The function to call.
    *args : P.args
        Positional arguments to pass to func.
    **kwargs : P.kwargs
        Keyword arguments to pass to func.

    Returns
    -------
    Future[T]
        A Future object that will contain the function's return value once
        the call completes.

    Notes
    -----
    The base implementation executes the function immediately and returns
    a completed Future. Subclasses should override this to provide
    thread-safe execution.
    """
    future: Future[T] = Future()
    future.set_result(func(*args, **kwargs))
    return future

call_later abstractmethod #

call_later(msec: int, func: Callable[[], None]) -> None

Schedule a function to be called after a delay.

Parameters:

  • msec #

    (int) –

    Delay in milliseconds before calling the function.

  • func #

    (Callable[[], None]) –

    The function to call. Must take no arguments.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
@abstractmethod
def call_later(self, msec: int, func: Callable[[], None]) -> None:
    """Schedule a function to be called after a delay.

    Parameters
    ----------
    msec : int
        Delay in milliseconds before calling the function.
    func : Callable[[], None]
        The function to call. Must take no arguments.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

create_app abstractmethod #

create_app() -> Any

Create the application instance, if not already created.

This method initializes the underlying GUI framework's application object (e.g., QApplication for Qt). If an application instance already exists, this method should return the existing instance.

Returns:

  • Any

    The backend-specific application object.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
@abstractmethod
def create_app(self) -> Any:
    """Create the application instance, if not already created.

    This method initializes the underlying GUI framework's application object
    (e.g., QApplication for Qt). If an application instance already exists,
    this method should return the existing instance.

    Returns
    -------
    Any
        The backend-specific application object.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

get_executor #

get_executor() -> Executor

Return an executor for running tasks in background threads.

Returns:

  • Executor

    A concurrent.futures.Executor instance (typically a ThreadPoolExecutor) for running background tasks.

Notes

The default implementation returns a shared ThreadPoolExecutor with 2 workers. Subclasses can override this to provide framework-specific executors.

Source code in src/scenex/app/_auto.py
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
def get_executor(self) -> Executor:
    """Return an executor for running tasks in background threads.

    Returns
    -------
    Executor
        A concurrent.futures.Executor instance (typically a ThreadPoolExecutor)
        for running background tasks.

    Notes
    -----
    The default implementation returns a shared ThreadPoolExecutor with 2
    workers. Subclasses can override this to provide framework-specific
    executors.
    """
    return _thread_pool_executor()

install_event_filter abstractmethod #

install_event_filter(widget: Any, handler: Callable[[Event], bool]) -> EventFilter

Install an event filter on a native widget to forward events to a handler.

Implementations of this method will capture all events given to the native widget, translate them into scenex events, and call handler with each one.

Parameters:

  • widget #

    (Any) –

    The backend-specific native canvas widget.

  • handler #

    (Callable[[Event], bool]) –

    A callable that receives each translated scenex event and returns True if the event was handled (stopping further propagation), False otherwise. Typically model_canvas.handle.

Returns:

  • EventFilter

    A handle that can be used to uninstall the event filter.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
@abstractmethod
def install_event_filter(
    self, widget: Any, handler: Callable[[Event], bool]
) -> EventFilter:
    """Install an event filter on a native widget to forward events to a handler.

    Implementations of this method will capture all events given to the native
    widget, translate them into scenex events, and call ``handler`` with each one.

    Parameters
    ----------
    widget : Any
        The backend-specific native canvas widget.
    handler : Callable[[Event], bool]
        A callable that receives each translated scenex event and returns True if
        the event was handled (stopping further propagation), False otherwise.
        Typically ``model_canvas.handle``.

    Returns
    -------
    EventFilter
        A handle that can be used to uninstall the event filter.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

process_events abstractmethod #

process_events() -> None

Yields the current thread to process all pending GUI events.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
296
297
298
299
300
301
302
303
304
@abstractmethod
def process_events(self) -> None:
    """Yields the current thread to process all pending GUI events.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

run abstractmethod #

run() -> None

Start the application event loop.

This method enters the GUI framework's main event loop, which processes user input, window events, and other GUI operations. The method blocks until the application is closed.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
236
237
238
239
240
241
242
243
244
245
246
247
248
@abstractmethod
def run(self) -> None:
    """Start the application event loop.

    This method enters the GUI framework's main event loop, which processes
    user input, window events, and other GUI operations. The method blocks
    until the application is closed.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

set_cursor abstractmethod #

set_cursor(native_widget: Any, cursor: CursorType) -> None

Set the cursor on a native canvas widget.

Backends override this to translate the abstract cursor into native form.

Parameters:

  • native_widget #

    (Any) –

    The backend-specific native canvas widget.

  • cursor #

    (CursorType) –

    The type of cursor to set.

Source code in src/scenex/app/_auto.py
389
390
391
392
393
394
395
396
397
398
399
400
401
402
@abstractmethod
def set_cursor(self, native_widget: Any, cursor: CursorType) -> None:
    """Set the cursor on a native canvas widget.

    Backends override this to translate the abstract cursor into native form.

    Parameters
    ----------
    native_widget : Any
        The backend-specific native canvas widget.
    cursor : CursorType
        The type of cursor to set.
    """
    ...

show abstractmethod #

show(native_widget: Any, visible: bool) -> None

Show or hide a native canvas widget.

Parameters:

  • native_widget #

    (Any) –

    The backend-specific native canvas widget to show or hide.

  • visible #

    (bool) –

    True to show the canvas window, False to hide it.

Notes

Must be implemented by subclasses.

Source code in src/scenex/app/_auto.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
@abstractmethod
def show(self, native_widget: Any, visible: bool) -> None:
    """Show or hide a native canvas widget.

    Parameters
    ----------
    native_widget : Any
        The backend-specific native canvas widget to show or hide.
    visible : bool
        True to show the canvas window, False to hide it.

    Notes
    -----
    Must be implemented by subclasses.
    """
    ...

CursorType #

Bases: Enum


              flowchart TD
              scenex.app.CursorType[CursorType]

              

              click scenex.app.CursorType href "" "scenex.app.CursorType"
            

Enumeration of standard cursor types for canvas interaction.

CursorType provides platform-independent cursor shapes that can be set on canvases to indicate different interaction modes or states. Each cursor type is mapped to the appropriate platform-specific cursor by the GUI backend.

Attributes:

  • DEFAULT (int) –

    The standard arrow cursor, typically used for normal selection and interaction.

  • CROSS (int) –

    A crosshair cursor, useful for precise positioning or drawing operations.

  • V_ARROW (int) –

    A vertical resize arrow cursor, indicating vertical resizing capability.

  • H_ARROW (int) –

    A horizontal resize arrow cursor, indicating horizontal resizing capability.

  • ALL_ARROW (int) –

    A multi-directional arrow cursor, indicating omnidirectional movement.

  • BDIAG_ARROW (int) –

    A diagonal resize arrow cursor (backward diagonal), for diagonal resizing.

  • FDIAG_ARROW (int) –

    A diagonal resize arrow cursor (forward diagonal), for diagonal resizing.

Examples:

Set a crosshair cursor during drawing mode:

>>> import scenex as snx
>>> canvas = snx.Canvas()
>>> snx.set_cursor(canvas, CursorType.CROSS)

Restore default cursor after operation:

>>> snx.set_cursor(canvas, CursorType.DEFAULT)
See Also

snx.set_cursor : Function to set cursor on a canvas

GuiFrontend #

Bases: str, Enum


              flowchart TD
              scenex.app.GuiFrontend[GuiFrontend]

              

              click scenex.app.GuiFrontend href "" "scenex.app.GuiFrontend"
            

Enum of available GUI frontends.

Attributes:

app #

app() -> App

Get the active GUI application instance.

Returns the singleton App instance for the current process, creating and initializing it if necessary. The GUI backend is determined automatically using determine_app().

This function should be used whenever you need to interact with the GUI application, such as running the event loop, showing windows, or scheduling thread-safe operations.

Returns:

  • App

    The active App instance wrapping the GUI backend.

Examples:

Get the app and run the event loop:

>>> app().run()
See Also

determine_app : Function that selects which GUI backend to use GuiFrontend : Enumeration of available backends App : Base class defining the application interface

Source code in src/scenex/app/_auto.py
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
def app() -> App:
    """Get the active GUI application instance.

    Returns the singleton App instance for the current process, creating and
    initializing it if necessary. The GUI backend is determined automatically
    using `determine_app()`.

    This function should be used whenever you need to interact with the GUI
    application, such as running the event loop, showing windows, or scheduling
    thread-safe operations.

    Returns
    -------
    App
        The active App instance wrapping the GUI backend.

    Examples
    --------
    Get the app and run the event loop:

    >>> app().run()

    See Also
    --------
    determine_app : Function that selects which GUI backend to use
    GuiFrontend : Enumeration of available backends
    App : Base class defining the application interface
    """
    global _APP
    if _APP is not None:
        return _APP

    # ensure the app is created for explicitly requested frontends
    _APP = _load_app(*GUI_PROVIDERS[determine_app()])
    _APP.create_app()
    return _APP

determine_app #

determine_app() -> GuiFrontend

Determine which GUI backend to use for the application.

This function selects the appropriate GUI framework backend through a three-tier strategy:

  1. Explicit request: If the SCENEX_APP_BACKEND environment variable is set, that backend is used (e.g., "qt", "wx", "jupyter").
  2. Running application: If a GUI application is already running in the process (detected via framework imports), that backend is used.
  3. Available backend: Try importing each backend in a predefined order until one succeeds.

Returns:

Raises:

  • ValueError

    If the SCENEX_APP_BACKEND environment variable is set to an invalid value.

  • RuntimeError

    If no GUI backend can be found or loaded.

Examples:

Let the function auto-detect the backend:

>>> backend = determine_app()

Force a specific backend via environment variable:

>>> import os
>>> os.environ["SCENEX_APP_BACKEND"] = "qt"
>>> backend = determine_app()  # Will use Qt
See Also

app : Get the active App instance using the determined backend GuiFrontend : Enumeration of available backends

Source code in src/scenex/app/_auto.py
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
def determine_app() -> GuiFrontend:
    """Determine which GUI backend to use for the application.

    This function selects the appropriate GUI framework backend through a
    three-tier strategy:

    1. **Explicit request**: If the SCENEX_APP_BACKEND environment variable
       is set, that backend is used (e.g., "qt", "wx", "jupyter").
    2. **Running application**: If a GUI application is already running in the
       process (detected via framework imports), that backend is used.
    3. **Available backend**: Try importing each backend in a predefined order until one
       succeeds.

    Returns
    -------
    GuiFrontend
        The determined GUI backend to use.

    Raises
    ------
    ValueError
        If the SCENEX_APP_BACKEND environment variable is set to an invalid
        value.
    RuntimeError
        If no GUI backend can be found or loaded.

    Examples
    --------
    Let the function auto-detect the backend:

    >>> backend = determine_app()

    Force a specific backend via environment variable:

    >>> import os
    >>> os.environ["SCENEX_APP_BACKEND"] = "qt"  # doctest: +SKIP
    >>> backend = determine_app()  # Will use Qt

    See Also
    --------
    app : Get the active App instance using the determined backend
    GuiFrontend : Enumeration of available backends
    """
    running = list(_running_apps())

    # Try 1: Load a frontend explicitly requested by the user
    requested = os.getenv(GUI_ENV_VAR, "").lower()
    valid = {x.value for x in GuiFrontend}
    if requested:
        if requested not in valid:
            raise ValueError(
                f"Invalid GUI frontend: {requested!r}. Valid options: {valid}"
            )
        return GuiFrontend(requested)

    # Try 2: Utilize an existing, running app
    for key in GUI_PROVIDERS.keys():
        if key in running:
            return key

    # Try 3: Load an existing app
    errors: list[tuple[str, BaseException]] = []
    for key, provider in GUI_PROVIDERS.items():
        try:
            _load_app(*provider)
            return key
        except Exception as e:
            errors.append((key, e))

    raise RuntimeError(  # pragma: no cover
        f"Could not find an appropriate GUI frontend: {valid!r}. Tried:\n\n"
        + "\n".join(f"- {key}: {err}" for key, err in errors)
    )

ensure_main_thread #

ensure_main_thread(func: Callable[P, T]) -> Callable[P, Future[T]]

Decorator that ensures a function is called in the main GUI thread.

This decorator wraps a function so that when called, it is automatically scheduled to run on the main GUI thread rather than the caller's thread. This is essential for GUI operations that must occur on the main thread.

Parameters:

  • func #

    (Callable[P, T]) –

    The function to wrap. Can have any signature.

Returns:

  • Callable[P, Future[T]]

    A wrapped version of func that returns a Future instead of the direct result. The Future will contain the function's return value once the call completes on the main thread.

Examples:

Ensure a GUI operation runs on the main thread:

>>> @ensure_main_thread
... def update_widget(value: int) -> None:
...     # Update some GUI widget with the given value
...     pass
>>> future = update_widget(42)  # Returns immediately with Future
>>> result = future.result()  # Block until completion if needed
See Also

App.call_in_main_thread : Underlying method for thread-safe calls

Source code in src/scenex/app/_auto.py
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
def ensure_main_thread(func: Callable[P, T]) -> Callable[P, Future[T]]:
    """Decorator that ensures a function is called in the main GUI thread.

    This decorator wraps a function so that when called, it is automatically
    scheduled to run on the main GUI thread rather than the caller's thread.
    This is essential for GUI operations that must occur on the main thread.

    Parameters
    ----------
    func : Callable[P, T]
        The function to wrap. Can have any signature.

    Returns
    -------
    Callable[P, Future[T]]
        A wrapped version of func that returns a Future instead of the direct
        result. The Future will contain the function's return value once the
        call completes on the main thread.

    Examples
    --------
    Ensure a GUI operation runs on the main thread:

    >>> @ensure_main_thread
    ... def update_widget(value: int) -> None:
    ...     # Update some GUI widget with the given value
    ...     pass
    >>> future = update_widget(42)  # Returns immediately with Future
    >>> result = future.result()  # Block until completion if needed

    See Also
    --------
    App.call_in_main_thread : Underlying method for thread-safe calls
    """

    @wraps(func)
    def _wrapper(*args: P.args, **kwargs: P.kwargs) -> Future[T]:
        return app().call_in_main_thread(func, *args, **kwargs)

    return _wrapper