simvx.core.ui.popup_manager

PopupManager — manages popup overlays with automatic pause and input routing.

Only one popup is active at a time. Opening a popup pauses the scene tree (configurable). Input is routed exclusively to the active popup: mouse clicks first (three-state return), then keyboard.

PopupPanel and DrawCanvas provide widget-based popup creation with automatic clipping and scrolling.

Module Contents

Classes

Popup

Base class for popup overlays managed by PopupManager.

DrawCanvas

Control for custom rendering within the widget system.

PopupPanel

Widget-based popup overlay managed by PopupManager.

PopupManager

Manages popup overlays with automatic pause and input routing.

Functions

check_menu_click

Return the index of the clicked menu item, or None.

API

class simvx.core.ui.popup_manager.Popup[source]

Base class for popup overlays managed by PopupManager.

Subclass and override the popup_* methods. Popups are lightweight objects (not Nodes) managed entirely by PopupManager. They can be reused across multiple open/close cycles.

Initialization

popup_input(dt: float) bool[source]

Handle input. Return True to stay open, False to close.

popup_draw(renderer, screen_w: float, screen_h: float) None[source]

Draw the popup in screen space.

popup_click(mx: float, my: float) bool | None[source]

Handle mouse click. True=handled, False=close, None=pass-through.

on_open() None[source]

Called when this popup becomes active.

on_close() None[source]

Called when this popup is dismissed.

class simvx.core.ui.popup_manager.DrawCanvas(content_size=(0.0, 0.0), **kwargs)[source]

Bases: simvx.core.ui.core.Control

Control for custom rendering within the widget system.

Set content_size to declare the total drawable area. Override custom_draw() for raw rendering. When inside a ScrollContainer, content exceeding the viewport is clipped and scrollable.

Example::

class MyCanvas(DrawCanvas):
    content_size = Property((400.0, 800.0))

    def custom_draw(self, renderer):
        renderer.draw_filled_rect(10, 10, 100, 50, (1, 0, 0, 1))

Initialization

content_size

‘Property(…)’

custom_draw(renderer) None[source]

Override to draw custom content.

get_minimum_size() simvx.core.math.types.Vec2[source]
draw(renderer)[source]
size_x

‘Property(…)’

size_y

‘Property(…)’

property size: simvx.core.math.types.Vec2
touch_mode: str

‘mouse’

property theme: simvx.core.ui.types.Theme | None
property mouse_over: bool
property focused: bool
property disabled: bool
get_theme() simvx.core.ui.types.Theme
queue_redraw()
get_rect() tuple[float, float, float, float]
get_global_rect() tuple[float, float, float, float]
is_point_inside(point) bool
set_anchor_preset(preset: simvx.core.ui.enums.AnchorPreset)
set_focus()
grab_focus()
release_focus()
has_focus() bool
focus_next_control()
focus_previous_control()
grab_mouse()
release_mouse()
set_drag_preview(control: simvx.core.ui.core.Control)
draw_popup(renderer)
is_popup_point_inside(point) bool
popup_input(event)
dismiss_popup()
z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property position: simvx.core.math.types.Vec2
property rotation: float
property rotation_degrees: float
property scale: simvx.core.math.types.Vec2
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
strict_errors: ClassVar[bool]

True

script_error_raised

‘Signal(…)’

classmethod __init_subclass__(**kwargs)
property name: str
property process_mode: simvx.core.descriptors.ProcessMode
reset_error() None
add_child(node: simvx.core.node.Node) simvx.core.node.Node
remove_child(node: simvx.core.node.Node)
reparent(new_parent: simvx.core.node.Node)
get_node(path: str) simvx.core.node.Node
find_child(name: str, recursive: bool = False) simvx.core.node.Node | None
find(node_type: type, recursive: bool = True) simvx.core.node.Node | None
find_all(node_type: type, recursive: bool = True) list
property path: str
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
ready() None
enter_tree() None
exit_tree() None
process(dt: float) None
physics_process(dt: float) None
input_event(event: simvx.core.events.InputEvent) None
input(event: simvx.core.events.TreeInputEvent) None
unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
clear_children()
destroy()
property app
property tree: simvx.core.scene_tree.SceneTree
get_tree() simvx.core.scene_tree.SceneTree
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
class simvx.core.ui.popup_manager.PopupPanel(title='', panel_size=(400, 300), **kwargs)[source]

Bases: simvx.core.ui.core.Control, simvx.core.ui.popup_manager.Popup

Widget-based popup overlay managed by PopupManager.

Add widget children for simple layouts via add_content(). Content that exceeds panel_size is automatically clipped and scrollable.

Example (simple)::

panel = PopupPanel(title="Paused", panel_size=(280, 220))
vbox = panel.add_content(VBoxContainer())
vbox.add_child(Button("Resume", on_press=panel.request_close))
popup_manager.open(panel)

Example (custom draw)::

class TreeCanvas(DrawCanvas):
    content_size = Property((700, 1200))
    def custom_draw(self, renderer):
        # draw skill tree lines, icons, glows...

panel = PopupPanel(title="Skills", panel_size=(750, 550))
panel.add_content(TreeCanvas())
popup_manager.open(panel)

Initialization

title

‘Property(…)’

panel_size

‘Property(…)’

closable

‘Property(…)’

dim_colour

‘Property(…)’

add_content(widget: simvx.core.ui.core.Control) simvx.core.ui.core.Control[source]

Add a widget to the scrollable content area. Returns the widget.

request_close() None[source]

Close this popup via PopupManager.

popup_draw(renderer, sw, sh)[source]
popup_click(mx, my)[source]
popup_input(dt)[source]
on_open()[source]
draw(renderer)[source]
size_x

‘Property(…)’

size_y

‘Property(…)’

property size: simvx.core.math.types.Vec2
touch_mode: str

‘mouse’

property theme: simvx.core.ui.types.Theme | None
property mouse_over: bool
property focused: bool
property disabled: bool
get_theme() simvx.core.ui.types.Theme
queue_redraw()
get_minimum_size() simvx.core.math.types.Vec2
get_rect() tuple[float, float, float, float]
get_global_rect() tuple[float, float, float, float]
is_point_inside(point) bool
set_anchor_preset(preset: simvx.core.ui.enums.AnchorPreset)
set_focus()
grab_focus()
release_focus()
has_focus() bool
focus_next_control()
focus_previous_control()
grab_mouse()
release_mouse()
set_drag_preview(control: simvx.core.ui.core.Control)
draw_popup(renderer)
is_popup_point_inside(point) bool
dismiss_popup()
z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property position: simvx.core.math.types.Vec2
property rotation: float
property rotation_degrees: float
property scale: simvx.core.math.types.Vec2
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
strict_errors: ClassVar[bool]

True

script_error_raised

‘Signal(…)’

classmethod __init_subclass__(**kwargs)
property name: str
property process_mode: simvx.core.descriptors.ProcessMode
reset_error() None
add_child(node: simvx.core.node.Node) simvx.core.node.Node
remove_child(node: simvx.core.node.Node)
reparent(new_parent: simvx.core.node.Node)
get_node(path: str) simvx.core.node.Node
find_child(name: str, recursive: bool = False) simvx.core.node.Node | None
find(node_type: type, recursive: bool = True) simvx.core.node.Node | None
find_all(node_type: type, recursive: bool = True) list
property path: str
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
ready() None
enter_tree() None
exit_tree() None
process(dt: float) None
physics_process(dt: float) None
input_event(event: simvx.core.events.InputEvent) None
input(event: simvx.core.events.TreeInputEvent) None
unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
clear_children()
destroy()
property app
property tree: simvx.core.scene_tree.SceneTree
get_tree() simvx.core.scene_tree.SceneTree
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
on_close() None
class simvx.core.ui.popup_manager.PopupManager(**kwargs)[source]

Bases: simvx.core.nodes_2d.node2d.Node2D

Manages popup overlays with automatic pause and input routing.

Only one popup is active at a time. Opening a popup pauses the scene tree (configurable via pause_tree). Input is routed exclusively to the active popup: mouse clicks first (three-state return), then keyboard.

Lives inside a CanvasLayer for screen-space rendering. Sets process_mode to ALWAYS automatically so it runs even when paused.

Signals: on_opened(popup): Emitted after a popup opens and the tree is paused. on_closed(popup): Emitted after a popup closes and the tree is unpaused.

Example::

popups = PopupManager()
canvas_layer.add_child(popups)
popups.open(my_pause_menu)  # Pauses tree, routes input to menu
popups.close()              # Unpauses tree

Initialization

pause_tree

‘Property(…)’

input_skip_frames

‘Property(…)’

on_opened

‘Signal(…)’

on_closed

‘Signal(…)’

property is_open: bool

Whether a popup is currently active.

property active: simvx.core.ui.popup_manager.Popup | None

The currently active popup, or None.

open(popup: simvx.core.ui.popup_manager.Popup) None[source]

Open a popup, closing any currently active one first.

close() None[source]

Close the active popup and unpause.

process(dt: float)[source]
draw(renderer)[source]
z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property position: simvx.core.math.types.Vec2
property rotation: float
property rotation_degrees: float
property scale: simvx.core.math.types.Vec2
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
strict_errors: ClassVar[bool]

True

script_error_raised

‘Signal(…)’

classmethod __init_subclass__(**kwargs)
property name: str
property process_mode: simvx.core.descriptors.ProcessMode
reset_error() None
add_child(node: simvx.core.node.Node) simvx.core.node.Node
remove_child(node: simvx.core.node.Node)
reparent(new_parent: simvx.core.node.Node)
get_node(path: str) simvx.core.node.Node
find_child(name: str, recursive: bool = False) simvx.core.node.Node | None
find(node_type: type, recursive: bool = True) simvx.core.node.Node | None
find_all(node_type: type, recursive: bool = True) list
property path: str
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
ready() None
enter_tree() None
exit_tree() None
physics_process(dt: float) None
input_event(event: simvx.core.events.InputEvent) None
input(event: simvx.core.events.TreeInputEvent) None
unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
clear_children()
destroy()
property app
property tree: simvx.core.scene_tree.SceneTree
get_tree() simvx.core.scene_tree.SceneTree
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
simvx.core.ui.popup_manager.check_menu_click(mx: float, my: float, count: int, origin_x: float, origin_y: float, item_w: float, item_h: float, spacing: float) int | None[source]

Return the index of the clicked menu item, or None.

Hit-tests a vertical list of items. Each item spans [origin_x, origin_x + item_w] x [origin_y + ispacing - 4, origin_y + ispacing + item_h].